@@ -442,8 +442,10 @@ If you see a hard hat AND a safety vest, safe=true. Otherwise safe=false.`;
442442 }
443443 }
444444
445- // Step 3: Draw visual overlays
445+ // Step 3: Draw visual overlays + update summary
446446 drawPersonOverlays ( people , imageDataUrl ) ;
447+ drawCanvasSummaryBadge ( people ) ;
448+ updatePPESummary ( people ) ;
447449
448450 // Step 4: Derive overall safety rating
449451 const unsafeCount = people . filter ( p => ! p . safe ) . length ;
@@ -629,6 +631,83 @@ If you see a hard hat AND a safety vest, safe=true. Otherwise safe=false.`;
629631 }
630632 }
631633
634+ // ── PPE Summary Panel + On-canvas badge ──
635+ function updatePPESummary ( people ) {
636+ const panel = document . getElementById ( 'ppeSummary' ) ;
637+ if ( ! panel ) return ;
638+
639+ const total = people . length ;
640+ const hasHelmet = people . filter ( p => p . wearing . some ( w => / h a t | h e l m e t / i. test ( w ) ) ) . length ;
641+ const hasVest = people . filter ( p => p . wearing . some ( w => / v e s t | h i - v i s / i. test ( w ) ) ) . length ;
642+ const noHelmet = total - hasHelmet ;
643+
644+ document . getElementById ( 'ppeTotalPeople' ) . textContent = total ;
645+ document . getElementById ( 'ppeSafeCount' ) . textContent = hasHelmet ;
646+ document . getElementById ( 'ppeUnsafeCount' ) . textContent = noHelmet ;
647+ document . getElementById ( 'ppeVestCount' ) . textContent = hasVest ;
648+
649+ // Per-person detail rows
650+ const details = document . getElementById ( 'ppeDetails' ) ;
651+ if ( total === 0 ) {
652+ details . innerHTML = '<div style="text-align:center;opacity:0.6;">No people detected</div>' ;
653+ } else {
654+ details . innerHTML = people . map ( ( p , i ) => {
655+ const icon = p . safe ? '\u2705' : '\u274c' ;
656+ const cls = p . safe ? 'ppe-detail-safe' : 'ppe-detail-unsafe' ;
657+ const wearing = p . wearing . length > 0 ? p . wearing . join ( ', ' ) : 'none detected' ;
658+ const missing = p . missing . length > 0 ? p . missing . join ( ', ' ) : '' ;
659+ return `<div class="ppe-detail-row ${ cls } ">
660+ ${ icon } <strong>Person ${ i + 1 } </strong>: ${ wearing } ${ missing ? ' — <span style="color:#E63946;">Missing: ' + missing + '</span>' : '' }
661+ </div>` ;
662+ } ) . join ( '' ) ;
663+ }
664+ }
665+
666+ function drawCanvasSummaryBadge ( people ) {
667+ if ( people . length === 0 ) return ;
668+
669+ const total = people . length ;
670+ const safe = people . filter ( p => p . safe ) . length ;
671+ const unsafe = total - safe ;
672+ const hasHelmet = people . filter ( p => p . wearing . some ( w => / h a t | h e l m e t / i. test ( w ) ) ) . length ;
673+ const hasVest = people . filter ( p => p . wearing . some ( w => / v e s t | h i - v i s / i. test ( w ) ) ) . length ;
674+
675+ // Draw summary badge in top-right corner
676+ const badgeX = overlayCanvas . width - 220 ;
677+ const badgeY = 10 ;
678+ const badgeW = 210 ;
679+ const badgeH = 80 ;
680+
681+ // Background
682+ overlayCtx . fillStyle = 'rgba(0, 0, 0, 0.8)' ;
683+ overlayCtx . beginPath ( ) ;
684+ overlayCtx . roundRect ( badgeX , badgeY , badgeW , badgeH , 8 ) ;
685+ overlayCtx . fill ( ) ;
686+
687+ // Title
688+ overlayCtx . fillStyle = '#fff' ;
689+ overlayCtx . font = 'bold 13px sans-serif' ;
690+ overlayCtx . fillText ( 'PPE Summary' , badgeX + 10 , badgeY + 18 ) ;
691+
692+ // Stats line 1: People count
693+ overlayCtx . font = '12px sans-serif' ;
694+ overlayCtx . fillStyle = '#ccc' ;
695+ overlayCtx . fillText ( `\ud83d\udc64 ${ total } people detected` , badgeX + 10 , badgeY + 36 ) ;
696+
697+ // Stats line 2: Helmets
698+ overlayCtx . fillStyle = hasHelmet === total ? '#2A9D8F' : '#E63946' ;
699+ overlayCtx . fillText ( `\u26d1 Helmets: ${ hasHelmet } /${ total } ` , badgeX + 10 , badgeY + 54 ) ;
700+
701+ // Stats line 3: Vests
702+ overlayCtx . fillStyle = hasVest === total ? '#2A9D8F' : '#E76F51' ;
703+ overlayCtx . fillText ( `\ud83e\udda6 Vests: ${ hasVest } /${ total } ` , badgeX + 10 , badgeY + 72 ) ;
704+
705+ // Safe/unsafe indicator
706+ overlayCtx . fillStyle = unsafe === 0 ? '#2A9D8F' : '#E63946' ;
707+ overlayCtx . font = 'bold 12px sans-serif' ;
708+ const statusText = unsafe === 0 ? '\u2713 ALL CLEAR' : `\u26a0 ${ unsafe } NON-COMPLIANT` ;
709+ overlayCtx . fillText ( statusText , badgeX + 115 , badgeY + 18 ) ;
710+ }
632711 // ── UI Update functions ──
633712 function updateStatus ( message , isError = false ) {
634713 statusBar . textContent = message ;
@@ -1216,9 +1295,12 @@ If you see a hard hat AND a safety vest, safe=true. Otherwise safe=false.`;
12161295 presetSelect . addEventListener ( 'change' , ( ) => {
12171296 updatePresetInfo ( ) ;
12181297 saveSettings ( ) ;
1219- // Show/hide PPE legend for construction mode
1298+ // Show/hide PPE legend + summary for construction mode
12201299 const ppeLegend = document . getElementById ( 'ppeLegend' ) ;
1221- if ( ppeLegend ) ppeLegend . style . display = presetSelect . value === 'construction' ? '' : 'none' ;
1300+ const ppeSummary = document . getElementById ( 'ppeSummary' ) ;
1301+ const isConstruction = presetSelect . value === 'construction' ;
1302+ if ( ppeLegend ) ppeLegend . style . display = isConstruction ? '' : 'none' ;
1303+ if ( ppeSummary ) ppeSummary . style . display = isConstruction ? '' : 'none' ;
12221304 // Clear overlays when switching presets
12231305 overlayCtx . clearRect ( 0 , 0 , overlayCanvas . width , overlayCanvas . height ) ;
12241306 window . reasoningConsole . logInfo ( `Preset changed to: ${ PRESETS [ presetSelect . value ] . name } ` ) ;
0 commit comments