@@ -10,6 +10,7 @@ import {
1010import * as React from 'react' ;
1111import { Cell , Label , Pie , PieChart } from 'recharts' ;
1212import type { DeviceWithChecks , Host } from '../types' ;
13+ import { computeSourceComplianceVerdict } from '../lib/device-source' ;
1314
1415interface DeviceComplianceChartProps {
1516 fleetDevices : Host [ ] ;
@@ -23,14 +24,15 @@ interface DeviceComplianceChartProps {
2324const CHART_COLORS = {
2425 compliant : 'var(--primary)' , // brand green
2526 nonCompliant : 'var(--destructive)' , // red
26- notTracked : 'var(--muted-foreground)' , // gray — no compliance data available
27+ unverified : 'var(--muted-foreground)' , // gray — canonical checks not (fully) reported
2728} ;
2829
2930export function DeviceComplianceChart ( { fleetDevices, agentDevices } : DeviceComplianceChartProps ) {
3031 // ALL devices count — the chart total must match the table so the page never
31- // looks self-contradictory. Imported devices use the SOURCE's verdict when
32- // it reports one; imported devices with no verdict land in a gray
33- // "Not tracked" segment rather than being fabricated into a verdict.
32+ // looks self-contradictory. Imported devices are judged by CompAI's OWN
33+ // standard (the four canonical checks, computed from source-reported data);
34+ // partially/un-reported devices land in a gray "Unverified" segment rather
35+ // than being fabricated into a verdict.
3436 const devices = [ ...( agentDevices ?? [ ] ) , ...( fleetDevices ?? [ ] ) ] ;
3537
3638 const { pieDisplayData, legendDisplayData } = React . useMemo ( ( ) => {
@@ -39,15 +41,18 @@ export function DeviceComplianceChart({ fleetDevices, agentDevices }: DeviceComp
3941 }
4042 let compliantCount = 0 ;
4143 let nonCompliantCount = 0 ;
42- let notTrackedCount = 0 ;
44+ let unverifiedCount = 0 ;
4345
4446 for ( const device of agentDevices ?? [ ] ) {
4547 if ( device . source === 'integration' ) {
46- // Source-reported verdict (e.g. Intune complianceState), if any.
47- const verdict = device . sourceCompliance ?. isCompliant ;
48- if ( verdict === true ) compliantCount ++ ;
49- else if ( verdict === false ) nonCompliantCount ++ ;
50- else notTrackedCount ++ ;
48+ // CompAI's verdict computed from the canonical source-reported checks
49+ // (vendor's own verdict is informational-only and never counted).
50+ const verdict = computeSourceComplianceVerdict ( device ) ;
51+ if ( verdict ?. kind === 'compliant' ) compliantCount ++ ;
52+ else if ( verdict ?. kind === 'non_compliant' ) nonCompliantCount ++ ;
53+ // Both partial ('unverified') and zero-data ('not_tracked') devices
54+ // land in the gray segment: neither has a defensible verdict.
55+ else unverifiedCount ++ ;
5156 continue ;
5257 }
5358 // Device-agent devices. Stale devices (no check-in for >= 7 days)
@@ -81,17 +86,17 @@ export function DeviceComplianceChart({ fleetDevices, agentDevices }: DeviceComp
8186 fill : CHART_COLORS . nonCompliant ,
8287 } ,
8388 {
84- name : 'Not Tracked ' ,
85- value : notTrackedCount ,
86- fill : CHART_COLORS . notTracked ,
89+ name : 'Unverified ' ,
90+ value : unverifiedCount ,
91+ fill : CHART_COLORS . unverified ,
8792 } ,
8893 ] ;
8994 return {
9095 pieDisplayData : allItems . filter ( ( item ) => item . value > 0 ) ,
91- // "Not Tracked " only appears in the legend when it exists — orgs with no
92- // verdict-less imported devices keep the familiar two-entry legend.
96+ // "Unverified " only appears in the legend when it exists — orgs with no
97+ // partially-verified imported devices keep the familiar two-entry legend.
9398 legendDisplayData : allItems . filter (
94- ( item ) => item . name !== 'Not Tracked ' || item . value > 0 ,
99+ ( item ) => item . name !== 'Unverified ' || item . value > 0 ,
95100 ) ,
96101 } ;
97102 } , [ agentDevices , fleetDevices ] ) ;
0 commit comments