diff --git a/client/src/components/Widgets/LlmAlChangesQuadPlot.jsx b/client/src/components/Widgets/LlmAlChangesQuadPlot.jsx
new file mode 100644
index 0000000..f8ff839
--- /dev/null
+++ b/client/src/components/Widgets/LlmAlChangesQuadPlot.jsx
@@ -0,0 +1,84 @@
+import VdapApi from '../../hooks/useVdapApi.js';
+import {Flex, Spinner, Text, Box, Center} from "@chakra-ui/react"; // Adjust path as needed
+
+
+export default function LlmAlChangesQuadPlot(props) {
+ console.log("3. Quad Plot loaded");
+ let tp_percent = 0;
+ let tn_percent = 0;
+ let fp_percent = 0;
+ let fn_percent = 0;
+
+ // Clean, single-line data fetching
+ const { data: apiData, isLoading, error } = VdapApi('/api/v2/llm_quadrant_plot');
+
+ if (isLoading) return (
+
+
+
+ );
+
+ if (error) return (
+
+ {error}
+
+ );
+
+ if (apiData.length === 0) return (
+
+ No alert level quad plot data available.
+
+ );
+
+ if (apiData) {
+ console.log(apiData);
+ tp_percent = ((apiData[0]['tp_count']/apiData[0]['tot_count'])*100).toFixed(2);
+ tn_percent = ((apiData[0]['tn_count']/apiData[0]['tot_count'])*100).toFixed(2);
+ fp_percent = ((apiData[0]['fp_count']/apiData[0]['tot_count'])*100).toFixed(2);
+ fn_percent = ((apiData[0]['fn_count']/apiData[0]['tot_count'])*100).toFixed(2);
+ }
+
+ return (
+
+
+ {/*Alert Level Changes Quad Plot*/}
+ All time
+
+
+
+
+
+ True Positive
+ {tp_percent}%
+ {apiData[0]['tp_count']}/{apiData[0]['tot_count']}
+
+
+
+
+ True Negative
+ {tn_percent}%
+ {apiData[0]['tn_count']}/{apiData[0]['tot_count']}
+
+
+
+
+
+
+ False Positive
+ {fp_percent}%
+ {apiData[0]['fp_count']}/{apiData[0]['tot_count']}
+
+
+
+
+ False Negative
+ {fn_percent}%
+ {apiData[0]['fn_count']}/{apiData[0]['tot_count']}
+
+
+
+
+
+ )
+
+}
\ No newline at end of file
diff --git a/client/src/components/Widgets/LlmAlChangesQuadPlot.stories.jsx b/client/src/components/Widgets/LlmAlChangesQuadPlot.stories.jsx
new file mode 100644
index 0000000..42ba8fc
--- /dev/null
+++ b/client/src/components/Widgets/LlmAlChangesQuadPlot.stories.jsx
@@ -0,0 +1,73 @@
+import React from 'react';
+import { Box } from '@chakra-ui/react';
+import { http, HttpResponse } from 'msw'; // Import MSW
+import LlmAlChangesQuadPlot from './LlmAlChangesQuadPlot';
+
+export default {
+ title: 'Dashboard Widgets/LLM AL Changes Quad Plot',
+ component: LlmAlChangesQuadPlot,
+ decorators: [
+ (Story) => (
+
+
+
+ ),
+ ],
+};
+
+const mockData = [
+ { tp: 50 },
+ { tn: 250 },
+ { fp: 10 },
+ { fn: 5 },
+ { tot_count: 315}
+];
+
+// Scenario LIVE: call our API for real
+export const LivePopulated = {};
+
+// Scenario 1: Successful API Call
+export const Populated = {
+ parameters: {
+ msw: {
+ handlers: [
+ // Intercept the exact URL your component is trying to fetch
+ http.get('*/api/v2/llm_quadrant_plot', () => {
+ // Return a 200 OK with the mock JSON
+ return HttpResponse.json(mockData);
+ }),
+ ],
+ },
+ },
+};
+
+// Scenario 2: The 500 Server Crash
+export const ServerCrash = {
+ parameters: {
+ msw: {
+ handlers: [
+ http.get('*/api/v2/llm_quad_plot', () => {
+ // Return a 500 Error
+ return new HttpResponse(null, { status: 500 });
+ }),
+ ],
+ },
+ },
+};
+
+// Scenario 3: Empty State (API works, but no data returned)
+export const EmptyData = {
+ parameters: {
+ msw: {
+ handlers: [
+ http.get('*/api/v2/llm_quad_plot', () => {
+ return HttpResponse.json([]);
+ }),
+ ],
+ },
+ },
+};
\ No newline at end of file
diff --git a/client/src/constants/widgetRegistry.jsx b/client/src/constants/widgetRegistry.jsx
index 3e763da..5b547bd 100644
--- a/client/src/constants/widgetRegistry.jsx
+++ b/client/src/constants/widgetRegistry.jsx
@@ -85,4 +85,15 @@ export const WIDGET_REGISTRY = {
}
},
+ "llm_al_changes_quad_plot": {
+ name: "Alert Level Changes Quad Plot",
+ category: "Computer Science",
+ description: "Percentage of True-Positive, True-Negative, False-Positive, and False-Negative changes in all " +
+ "alert levels.",
+ component: lazy(() => import('../components/Widgets/LlmAlChangesQuadPlot.jsx')),
+ allowedSizes: {
+ small: { w: 3, h: 12 }
+ }
+ }
+
};
\ No newline at end of file