Saqr/core/compliance.py

56 lines
2.0 KiB
Python

"""Binary SAFE / UNSAFE classification — every item in REQUIRED_PPE must be worn.
The required list comes from ``config/core_config.json::compliance.required_ppe``
so it can be retuned without a code change.
"""
from __future__ import annotations
from typing import Dict, List, Tuple
from core.detection import POSITIVE_TO_NEGATIVE, PPE_DISPLAY_ORDER
from utils.config import load_config
# PPE items required for SAFE status. Other items (gloves/goggles/boots) are
# still detected and logged but don't affect compliance.
REQUIRED_PPE: Tuple[str, ...] = tuple(
load_config("core").get("compliance", {}).get("required_ppe", ["helmet", "vest"])
)
def _is_wearing(items: Dict[str, float], ppe: str) -> bool:
pos = items.get(ppe, 0.0)
neg = items.get(POSITIVE_TO_NEGATIVE[ppe], 0.0)
return pos > neg and pos > 0
def status_from_items(items: Dict[str, float]) -> str:
"""SAFE only when every required item is confidently worn."""
if all(_is_wearing(items, p) for p in REQUIRED_PPE):
return "SAFE"
return "UNSAFE"
def split_wearing_missing(items: Dict[str, float]) -> Tuple[List[str], List[str], List[str]]:
"""Bucket each PPE item into wearing / missing / unknown.
``missing`` only contains REQUIRED items that aren't confidently worn —
this is what the TTS announcement keys off of, so it maps cleanly to a
recorded clip. Non-required items (gloves/goggles/boots) are never in
``missing`` regardless of whether their ``no-X`` class was detected; they
go to ``unknown`` so they're still visible in the event log.
"""
wearing, missing, unknown = [], [], []
for pos in PPE_DISPLAY_ORDER:
neg = POSITIVE_TO_NEGATIVE[pos]
pos_conf = items.get(pos, 0.0)
neg_conf = items.get(neg, 0.0)
worn = pos_conf > neg_conf and pos_conf > 0
if worn:
wearing.append(pos)
elif pos in REQUIRED_PPE:
missing.append(pos)
else:
unknown.append(pos)
return wearing, missing, unknown