{"commit_sha":"bbef4a834b9f813aba69a9a88682cfd6a2826897","platform_version":"2.7.0","rules":{"CNT08":{"rule_codes":["CNT08","CNT09"],"name":"Teacher:student ratio — CNT08 (too few teachers) / CNT09 (too many)","version":"1.1.0","domain":"karterschool","kind":"accumulator","tier":0,"description":"Staffing-ratio compliance for supervised rooms. Counts teachers (adults) vs students (children) per frame — persons are reclassified to adult/child via detector classes or SGIE age scores — estimates a robust per-second count by reliability-weighted mode (tolerates flickering detections), maps the student count to a required number of teachers via a configurable ratio table, and fires when the requirement is violated for >= violation_rate_threshold of a sliding window (default 10 minutes). CNT08 = too few teachers present (OVERRATIO, the safety violation); CNT09 = too many (UNDERRATIO, staffing efficiency). Accumulator rule: needs no tracker, works on raw per-frame detections + video clock, so it is robust to ID switches in crowded rooms. Fires per camera (room), not per person.\n","use_cases":["kindergarten / childcare licensing compliance (minimum caregiver-to-child ratio)","classroom supervision monitoring over long windows rather than instant snapshots","any \"count class A vs class B and enforce a ratio over time\" requirement (adaptable via config)"],"limitations":["needs a detector (or SGIE) that separates the two counted classes (adult/child)","room-level signal only — does not identify WHICH teacher left or track individuals","sliding-window design means intentionally slow alerts (minutes, not seconds)"],"entry_points":{"CNT08":"cnt.module:CNT08Module","CNT09":"cnt.module:CNT09Module"},"config_schema":"config.schema.json","io":{"input":{"topic":"deepstream_frame","contract":"celia.contracts.DeepstreamFrameMessage"},"output":{"topic":"evidence_request","contract":"celia.contracts.EvidenceRequest"}},"requires":{"models":["warehouse_detector"],"tracking":"none"},"verification":{"golden_cases":"tests/fixtures"}},"CNT09":{"rule_codes":["CNT08","CNT09"],"name":"Teacher:student ratio — CNT08 (too few teachers) / CNT09 (too many)","version":"1.1.0","domain":"karterschool","kind":"accumulator","tier":0,"description":"Staffing-ratio compliance for supervised rooms. Counts teachers (adults) vs students (children) per frame — persons are reclassified to adult/child via detector classes or SGIE age scores — estimates a robust per-second count by reliability-weighted mode (tolerates flickering detections), maps the student count to a required number of teachers via a configurable ratio table, and fires when the requirement is violated for >= violation_rate_threshold of a sliding window (default 10 minutes). CNT08 = too few teachers present (OVERRATIO, the safety violation); CNT09 = too many (UNDERRATIO, staffing efficiency). Accumulator rule: needs no tracker, works on raw per-frame detections + video clock, so it is robust to ID switches in crowded rooms. Fires per camera (room), not per person.\n","use_cases":["kindergarten / childcare licensing compliance (minimum caregiver-to-child ratio)","classroom supervision monitoring over long windows rather than instant snapshots","any \"count class A vs class B and enforce a ratio over time\" requirement (adaptable via config)"],"limitations":["needs a detector (or SGIE) that separates the two counted classes (adult/child)","room-level signal only — does not identify WHICH teacher left or track individuals","sliding-window design means intentionally slow alerts (minutes, not seconds)"],"entry_points":{"CNT08":"cnt.module:CNT08Module","CNT09":"cnt.module:CNT09Module"},"config_schema":"config.schema.json","io":{"input":{"topic":"deepstream_frame","contract":"celia.contracts.DeepstreamFrameMessage"},"output":{"topic":"evidence_request","contract":"celia.contracts.EvidenceRequest"}},"requires":{"models":["warehouse_detector"],"tracking":"none"},"verification":{"golden_cases":"tests/fixtures"}},"CNT10":{"rule_code":"CNT10","name":"CNT10 — students present with no teacher in the room","version":"1.0.0","domain":"karterschool","kind":"accumulator","tier":0,"description":"Unattended-children rule: fire when at least one student is present AND no teacher is present, sustained for >= violation_rate_threshold (default 80%) of a sliding window (default 30 s). Per-frame counts come from detector classes or SGIE age scores (persons reclassified to adult/child); inter-frame time is clamped to 5 s so stream gaps can't inflate the violation time, and the window counts committed one-second buckets only. After the initial alert the rule re-fires as a reminder every reminder_interval_seconds (default 5 min) while the violation persists; when a teacher returns the cycle resets. Room-level accumulator — no tracker.\n","use_cases":["childcare supervision — alert when children are left without any caregiver","classroom coverage monitoring during operating hours","reminder-style escalation for violations that persist (initial alert + periodic reminders)"],"limitations":["needs adult/child separation (detector classes or SGIE age scores)","room-level only — does not identify which teacher left or track individuals","by design slow (window-rate) — not an instant \"teacher stepped out\" alert"],"entry_point":"cnt10.module:CNT10Module","config_schema":"config.schema.json","io":{"input":{"topic":"deepstream_frame","contract":"celia.contracts.DeepstreamFrameMessage"},"output":{"topic":"evidence_request","contract":"celia.contracts.EvidenceRequest"}},"requires":{"models":["person_detector"],"tracking":"none"},"verification":{"golden_cases":"tests/fixtures"}},"HM05":{"rule_code":"HM05","name":"HM05 — person walking in a no-walk zone","version":"1.0.0","domain":"warehouse","kind":"track","tier":1,"description":"Pedestrian-routing safety rule: fire when a person WALKS through a designated no-walk zone (forklift aisles, racking lanes). When a person enters a no_walking_zone polygon a 7-second monitoring window opens; the rule fires at window close only if the person actually travelled (max displacement >= distance_multiplier x their average bbox height) AND none of three worker exclusions applies over the window: overlapping/near an MHE >= 30% of frames (operating a vehicle), overlapping/near product >= 30% (handling goods), or classified as cleaner >= 30% (SGIE cleaner_scores). Leaving the zone resets the window. Per-track, fire-once.\n","use_cases":["pedestrian-exclusion aisles in warehouses (people must not walk through MHE lanes)","enforcing marked walkways by flagging shortcut-takers","reducing false alarms for legitimate workers (MHE operators, product handlers, cleaners)"],"limitations":["needs the MHE + product classes visible to compute the exclusions, not just person boxes","cleaner exclusion needs an SGIE emitting cleaner_scores (skipped gracefully when absent)","a person standing still inside the zone does not fire (walking is the violation)","product exclusion is ACTIVE by default (legacy prod never tracked product_package, so it was dormant there); in product-dense aisles it can suppress the rule — drop the class from target_class for legacy parity"],"entry_point":"hm05.module:HM05Module","config_schema":"config.schema.json","io":{"input":{"topic":"deepstream_frame","contract":"celia.contracts.DeepstreamFrameMessage"},"output":{"topic":"evidence_request","contract":"celia.contracts.EvidenceRequest"}},"requires":{"models":["warehouse_detector"],"tracking":"bytetrack","classes":["person","forklift"],"optional_classes":["product_package","hand_pallet_jack","electric_pallet_jack","reach_truck"]},"verification":{"golden_cases":"tests/fixtures"}},"HM07":{"rule_code":"HM07","name":"HM07 — person stationary beyond a time limit","version":"1.0.0","domain":"warehouse","kind":"track","tier":1,"description":"Loitering / inactivity detection on tracked persons. Fires when a person stays effectively stationary — bbox center barely shifts vs the still-streak anchor (move_distance_threshold) — for longer than limit_time seconds (default 130s), with the detections over the streak clearing mean/median confidence + count gates to suppress flicker. Honours cam_zones polygons when configured (only fires inside monitored areas). Per-track, fire-once per still-streak; any real movement resets the streak. Pure geometry over tracking (tier 1) — no extra model beyond a person detector; a downstream VLM recheck can be layered as the evidence gate.\n","use_cases":["worker down / unresponsive person detection (stationary too long in a work area)","loitering in restricted or dangerous zones","idle-time monitoring at workstations (person present but not moving)","unattended-person alerts in areas that should be pass-through only"],"limitations":["stationary != inactive — a person standing still while working also triggers; tune limit_time/zones","needs stable tracking; heavy occlusion or crowd churn fragments the still-streak","does not distinguish sitting/lying/standing posture (that needs a pose or VLM layer)"],"entry_point":"hm07.module:HM07Module","config_schema":"config.schema.json","io":{"input":{"topic":"celia.warehouse","contract":"celia.contracts.DeepstreamFrameMessage"},"output":{"topic":"celia.evidences.slow","contract":"celia.contracts.EvidenceRequest"}},"requires":{"models":["person_detector"],"tracking":"bytetrack"},"verification":{"golden_cases":"tests/fixtures"}},"MHE01":{"rule_code":"MHE01","name":"MHE01 — collision warning (MHE too close to MHE / person)","version":"1.0.0","domain":"warehouse","kind":"track","tier":1,"description":"Collision-warning rule in real-world metres. Each camera is calibrated with a 4-point planar homography (image_points → real_points); bbox reference points are transformed to floor coordinates and pairwise distances checked every frame: MHE↔MHE closer than min_distance_mhe_vs_mhe (default 2.0 m) while BOTH vehicles are moving, or MHE↔person closer than min_distance_mhe_vs_person (default 2.5 m) while the MHE is moving. Persons riding an MHE are excluded as drivers (bbox-overlap history); a vehicle stationary >= 3 s (with brief-movement tolerance) is exempt; near-duplicate boxes are skipped. A pair fires after violent_count consecutive illegal frames, once per pair. Confidence is a Gaussian of the measured distance blended with (1 - IoU). Cross-object rule — pure geometry + homography, no extra model.\n","use_cases":["forklift-to-pedestrian struck-by prevention in shared warehouse floors","MHE convoy distance enforcement in narrow aisles","any real-distance proximity requirement between two tracked object classes"],"limitations":["needs per-camera homography calibration (4 image points ↔ 4 floor points in metres)","distances are floor-plane — raised loads/mezzanines break the planar assumption","fire-once per track pair; a new approach by the same pair after track loss re-fires"],"entry_point":"mhe01.module:MHE01Module","config_schema":"config.schema.json","io":{"input":{"topic":"deepstream_frame","contract":"celia.contracts.DeepstreamFrameMessage"},"output":{"topic":"evidence_request","contract":"celia.contracts.EvidenceRequest"}},"requires":{"models":["warehouse_detector"],"tracking":"bytetrack","classes":["person","forklift"]},"verification":{"golden_cases":"tests/fixtures"}},"MHE03":{"rule_code":"MHE03","name":"MHE03 — MHE crossing an intersection at excessive speed","version":"1.0.0","domain":"warehouse","kind":"track","tier":1,"description":"Intersection-speed rule for material handling equipment. Maintains a per-track speed history (bottom-center pixel displacement per frame); when the vehicle's recent motion segment crosses a configured intersection line, the last ~1 second of speed samples is analysed in 5-sample chunks: the rule fires when the mean approach speed exceeds medium_speed (px/frame) AND the slowest chunk is one of the last two (deceleration right at the line — the legacy \"came in hot, braked at the crossing\" pattern). Fire-once per track. Confidence blends mean detection confidence, speed consistency (1 - coefficient of variation of chunk sums) and a Gaussian of the minimum chunk around ideal_speed. Speeds are raw pixels/frame — thresholds are per-camera tuning values (legacy semantics, not metres/second).\n","use_cases":["forklift speed compliance at aisle intersections and blind corners","gate/doorway approach-speed enforcement for MHEs","template for line-crossing + kinematics rules (any class + any line set via config)"],"limitations":["speed is pixel-based — thresholds must be re-tuned per camera position/zoom","the deceleration heuristic also fires on legitimate hard braking at the line","short lookback segment (5 frames) can miss very slow crossings"],"entry_point":"mhe03.module:MHE03Module","config_schema":"config.schema.json","io":{"input":{"topic":"deepstream_frame","contract":"celia.contracts.DeepstreamFrameMessage"},"output":{"topic":"evidence_request","contract":"celia.contracts.EvidenceRequest"}},"requires":{"models":["warehouse_detector"],"tracking":"bytetrack","classes":["forklift"]},"verification":{"golden_cases":"tests/fixtures"}},"MHE04":{"rule_code":"MHE04","name":"MHE04 — excessive pallet stacking (oversized stack in motion)","version":"1.0.0","domain":"warehouse","kind":"track","tier":1,"description":"Oversized-stack rule: fire when a cluster of max_pallet_number or more pallets is being moved as one stack. A moving seed pallet gathers vertically-stacked neighbours (horizontal overlap >= 50% of the narrower box, column split at y-gaps > 1.5x seed height); a big-enough cluster opens a pending window and is confirmed only after ~2 s of stability gates pass (count within tolerance, bounded count std-dev, enough frames at the threshold, merged bbox doesn't balloon, and the centroid actually DRIFTS >= min_centroid_disp_ratio of the cluster height — the stack must be in transport). At creation at least half the pallets must be moving — a parked warehouse stack is not a violation, the rule targets stacks in transport. Rejected seeds get a frame-count cooldown; confirmed cluster members never re-fire. Confidence blends detection confidence, cluster cohesion, motion consistency and a Gaussian of the count.\n","use_cases":["forklift carrying more pallets than the safe stacking limit","tip-over prevention for tall mobile stacks in aisles","template for count-of-clustered-objects rules (any class via config)"],"limitations":["x-overlap clustering cannot distinguish a true vertical stack from a tight side-by-side row seen head-on — camera angle matters","counts fluctuate with occlusion; the stability gates absorb some of it but low-FPS or heavily occluded scenes need threshold retuning","the split threshold (1.5x seed height) assumes roughly camera-uniform pallet sizes","uses the stock ByteTrack (min_consecutive_frames pinned to 1 in defaults); the legacy custom pallet matcher (cost-based re-assignment) is not reproduced"],"entry_point":"mhe04.module:MHE04Module","config_schema":"config.schema.json","io":{"input":{"topic":"deepstream_frame","contract":"celia.contracts.DeepstreamFrameMessage"},"output":{"topic":"evidence_request","contract":"celia.contracts.EvidenceRequest"}},"requires":{"models":["warehouse_detector"],"tracking":"bytetrack","classes":["pallet"]},"verification":{"golden_cases":"tests/fixtures"}},"MHE10":{"rule_code":"MHE10","name":"MHE10 — moving while load at height","version":"1.0.0","domain":"warehouse","tier":1,"emission_strategy":"immediate","description":"Forklift-safety rule: detect a Material Handling Equipment unit (forklift / reach truck) TRAVELLING while its load (product package) is raised above the vehicle base — a classic tip-over / dropped-load hazard. Works purely on geometry over tracked boxes: associates the package box to its carrying MHE, measures the package's vertical offset vs the vehicle base (base_y_distance / increased_y_distance) and the vehicle's travel angle/velocity (degrees_threshold, prd_move_threshold); event confidence is a weighted blend of MHE/product confidence and geometry terms. An optional sklearn postprocess gate re-validates each candidate on the last-5 bbox features (fail-open if the service is down). Per-track, immediate emit.\n","use_cases":["warehouse forklift safety — \"no driving with raised forks/load\" policy enforcement","reach-truck operation audits in racking aisles","any moving-vehicle-with-elevated-load hazard (adaptable classes via config)"],"limitations":["needs a detector emitting BOTH the vehicle classes and the load/package class","camera should view the vehicle side-on enough that raised-load geometry is visible","does not measure actual load height in metres — thresholds are relative to bbox geometry"],"entry_point":"mhe10.module:MHE10Module","config_schema":"config.schema.json","io":{"input":{"topic":"deepstream_frame","contract":"celia.contracts.DeepstreamFrameMessage"},"output":{"topic":"evidence_request","contract":"celia.contracts.EvidenceRequest"}},"requires":{"models":["warehouse_detector"],"classes":["forklift","product_package"],"optional_classes":["reach_truck"],"tracking":"bytetrack","optional_services":[{"name":"mhe10_postprocess","kind":"http","purpose":"sklearn gate on last-5 bbox features; fail-open if unavailable","config_key":"postprocess_url"}]},"verification":{"golden_cases":"tests/fixtures"}},"OTH02":{"rule_code":"OTH02","name":"OTH02 — skipped the alcohol test at the checkpoint","version":"1.0.0","domain":"warehouse","kind":"track","tier":1,"description":"Checkpoint-compliance rule: fire when a person's trajectory crosses BOTH the start_line and the end_line of the alcohol-test area, entering in the expected direction (y-center increasing), without ever using the test device — \"using\" is any frame where the person's bbox overlaps an alcohol-tool detection by more than alcohol_iou_threshold of the tool's area. Line-crossing rule built on segment-segment intersection of the track's first→last center trajectory. Fire-once per track. Confidence = 0.5·mean + 0.5·median of recent detection confidences. The legacy camera-specific truck variant (truck passing without a full stop) is not ported.\n","use_cases":["mandatory alcohol/breathalyzer checkpoints at facility gates","template for any two-line \"passed through without doing X\" compliance rule","entry-protocol enforcement where an interaction with a device/desk is required"],"limitations":["needs the test device as a detectable class (alcohol_tool) in the detector","straight first→last trajectory segment — loops inside the checkpoint can under-detect crossings","directional check assumes entry moves downward in the image (y increasing); flip lines otherwise"],"entry_point":"oth02.module:OTH02Module","config_schema":"config.schema.json","io":{"input":{"topic":"deepstream_frame","contract":"celia.contracts.DeepstreamFrameMessage"},"output":{"topic":"evidence_request","contract":"celia.contracts.EvidenceRequest"}},"requires":{"models":["checkpoint_detector"],"tracking":"bytetrack","classes":["person","alcohol_tool"]},"verification":{"golden_cases":"tests/fixtures"}},"PAR02":{"rule_code":"PAR02","name":"PAR02 — person using phone while moving","version":"1.0.0","domain":"warehouse","kind":"track","emission_strategy":"deferred","description":"Distracted-walking safety rule: detect a person who is USING A PHONE (per-frame using_phone_scores from the PAR attribute SGIE) AND MOVING (accumulated travel >= movement_ratio x average bbox width) at the same time. A confirmation window opens when the phone is first seen inside a monitored zone (cam_zones); at window close the rule decides on the accumulated evidence (deferred emission — one clean event with the full clip, not per-frame spam). Per-track, fire-once. The combination matters: phone-while-stationary is allowed (break areas), walking-without-phone is allowed — only the combination fires.\n","use_cases":["no-phone-while-walking policies on factory / warehouse floors and forklift aisles","pedestrian-safety zones where distracted walking is a struck-by hazard","template for any attribute+motion combination rule (attribute gate x travel gate over a window)"],"limitations":["phone signal is a classifier score on the person crop — small/occluded phones lower recall","needs the PAR SGIE attached to the person detector in the pipeline","zone polygons required to scope enforcement (whole-frame monitoring is noisy)"],"entry_point":"par02.module:PAR02Module","config_schema":"config.schema.json","io":{"input":{"topic":"celia.par","contract":"celia.contracts.DeepstreamFrameMessage"},"output":{"topic":"celia.evidences.slow","contract":"celia.contracts.EvidenceRequest"}},"requires":{"models":["par"],"sgie_model":"par","tracking":"bytetrack"},"verification":{"golden_cases":"tests/fixtures"}},"PAR03":{"rule_codes":["PAR03"],"name":"PAR03 — adult sustained phone usage","version":"1.0.0","domain":"karterschool","kind":"track","tier":0,"description":"Sustained phone-usage rule for staff monitoring: flag an ADULT who is using a phone for at least phone_usage_ratio_threshold of a sliding time window (window must be >= min_duration long). Per-frame binary phone gate (using_phone_scores > threshold_phone from the attribute SGIE), ratio-over-window smoothing so brief glances don't fire, immediate emit on confirmation, fire-once per track. Unlike PAR02 there is NO motion requirement — this targets prolonged distraction (e.g. a caregiver on the phone instead of supervising), stationary or not, and it only evaluates adults (children with phones are ignored).\n","use_cases":["caregiver/teacher attention monitoring in childcare rooms (phone instead of supervising)","staff phone-usage policy during work hours at counters, gates, guard posts","sustained-distraction detection where duration matters more than movement"],"limitations":["needs adult/child separation (detector classes or SGIE age scores) to target adults only",{"score-based":"phones held low / out of crop reduce recall; tune threshold_phone + window"},"per-track fire-once — a person who stops and resumes later re-fires only after track loss"],"entry_point":"par03.module:PAR03Module","config_schema":"config.schema.json","io":{"input":{"topic":"deepstream_frame","contract":"celia.contracts.DeepstreamFrameMessage"},"output":{"topic":"evidence_request","contract":"celia.contracts.EvidenceRequest"}},"requires":{"models":["person_detector"],"tracking":"bytetrack"},"verification":{"golden_cases":"tests/fixtures"}},"PL02":{"rule_code":"PL02","name":"PL02 — pallet on the floor over the time limit (batched)","version":"1.0.0","domain":"warehouse","kind":"track","tier":1,"description":"Housekeeping/blockage rule: fire when a pallet (or product package) stays on the floor longer than violent_count seconds (default 60 min), and re-fire every violent_count while it remains. A pallet overlapping a truck is skipped (being loaded/unloaded). Alert batching: when other pallets are within first_waiting_window_time of violating, reporting is held so several pallets land in one event instead of alert spam — held at most max_wait_time before force-fire. Zone check keeps the legacy two-point semantics (bbox center AND center-bottom inside a cam_zone). Evidence per pallet = first + last detection of each video segment. See PL03 for the hardened variant with movement resets, region-level cooldown and stricter batch FSM guards.\n","use_cases":["pallets left blocking aisles or staging areas beyond the allowed window","dock housekeeping SLAs (nothing on the floor for more than an hour)","grouped reporting to keep long-lived violations from spamming operators"],"limitations":["track-id based — if the tracker loses and re-acquires a pallet the timer restarts (PL03 fixes this with a region cooldown)","on-floor means a static bbox on camera; it cannot tell a pallet on the floor from one on a rack edge without proper zones","long thresholds mean slow feedback by design"],"entry_point":"pl02.module:PL02Module","config_schema":"config.schema.json","io":{"input":{"topic":"deepstream_frame","contract":"celia.contracts.DeepstreamFrameMessage"},"output":{"topic":"evidence_request","contract":"celia.contracts.EvidenceRequest"}},"requires":{"models":["warehouse_detector"],"tracking":"bytetrack","classes":["pallet"]},"verification":{"golden_cases":"tests/fixtures"}},"PL03":{"rule_code":"PL03","name":"PL03 — pallet on floor over time (hardened FSM variant)","version":"1.0.0","domain":"warehouse","kind":"track","tier":1,"description":"The production-hardened successor to PL02. Same violation (\"pallet on the floor longer than violent_count seconds\") but with the false-positive/duplicate machinery that real deployments needed: a per-track floor timer anchored by timestamp that RESETS when the pallet moves and re-anchors after track gaps; the violation threshold separated from the re-report cooldown (report_interval); slender side-on boxes get extra confirm time; a REGION-level cooldown (centroid distance + IoU vs recently fired locations) so tracker id churn on the same physical pallet cannot re-fire; and a guarded batch FSM — one-shot post-ready hold, one-shot near-grace while near-violation pallets exist, minimum emit gap, max batch wait force-fire, and a duplicate-signature guard. Evidence is sampled (first/last per video segment + midpoints, capped). Prefer PL03 over PL02 for new deployments; PL02 stays as the faithful simple port.\n","use_cases":["aisle/staging-area blockage enforcement with operator-friendly, deduplicated alerts","long-lived floor violations in scenes with flaky tracking (id churn handled by region cooldown)","batched emission timing on busy docks (one event per pallet, released together)"],"limitations":["thresholds interact (hold/grace/gap/wait) — tune with real traffic, not defaults alone","region cooldown radius is pixel-based; re-tune per camera geometry","like PL02, cannot distinguish floor vs low rack without zones","legacy nested-pallet suppression / pallet-merge preprocessing is not ported; the IoU 0.95 dedup is weaker against double-detections"],"entry_point":"pl03.module:PL03Module","config_schema":"config.schema.json","io":{"input":{"topic":"deepstream_frame","contract":"celia.contracts.DeepstreamFrameMessage"},"output":{"topic":"evidence_request","contract":"celia.contracts.EvidenceRequest"}},"requires":{"models":["warehouse_detector"],"tracking":"bytetrack","classes":["pallet"]},"verification":{"golden_cases":"tests/fixtures"}},"PPE01":{"rule_code":"PPE01","name":"PPE01 — person not wearing required PPE (helmet / safety clothes)","version":"1.0.0","domain":"warehouse","kind":"track","tier":1,"description":"PPE-compliance rule: detect a tracked person NOT wearing required protective equipment (helmet and/or safety clothes). Per-frame SGIE classifier scores (not_wearing_helmet, wearing_safetyclothes_1/6) are voted across a short ppe_check_window_seconds window — small or frame-edge boxes are dropped as unreliable — and the rule fires once per track when the violating fraction clears voting_ratio_threshold. The voting window suppresses single-frame misclassifications (turned heads, brief occlusions). Which PPE items are enforced is configurable, so helmet-only or vest-only deployments reuse the same rule.\n","use_cases":["hard-hat / helmet enforcement on factory floors, docks and construction-like areas","hi-vis / safety-clothing enforcement in vehicle-traffic zones","entry-gate PPE checks (short dwell is enough thanks to the voting window)"],"limitations":["requires a PPE attribute classifier (SGIE) emitting the *_scores — detector alone is not enough",{"attribute-level check":"verifies worn/not-worn, not whether PPE is worn CORRECTLY (e.g. chinstrap)"},"distant/low-resolution person crops degrade classifier scores — camera placement matters"],"entry_point":"ppe01.module:PPE01Module","config_schema":"config.schema.json","io":{"input":{"topic":"celia.warehouse","contract":"celia.contracts.DeepstreamFrameMessage"},"output":{"topic":"celia.evidences.slow","contract":"celia.contracts.EvidenceRequest"}},"requires":{"models":["person_detector"],"tracking":"bytetrack"},"verification":{"golden_cases":"tests/fixtures"}},"cl01":{"rule_codes":["vlm_periodic","cl01","cl02","cl03"],"name":"VLM periodic — scheduled full-frame VLM check","version":"1.0.0","domain":"karterschool","kind":"frame","tier":0,"description":"Scheduled full-frame Vision-Language-Model check — the catch-all rule for requirements that need scene UNDERSTANDING rather than object detection. On a Mon-Fri schedule (one-shot at fixed times + periodic every N seconds within a time window, in the camera's timezone) it emits a full-frame snapshot event; the downstream VLM service answers a configured natural-language question about the scene (e.g. \"is the room tidy?\", \"are curtains closed during nap time?\", \"is the gate locked?\"). No detector, no tracker, no model from the zoo — pure schedule. The cl01/cl02/cl03 codes are the same module with different questions/schedules per deployment. This is usually the fastest way to cover a new requirement BEFORE investing in a dedicated rule or model: write a question + schedule instead of code.\n","use_cases":["periodic housekeeping / tidiness / setup checks (room state, equipment presence)","open-ended compliance questions a detector cannot answer (signage, door/gate state, lighting)","fast prototyping of a new requirement as a VLM question before building a dedicated rule","end-of-day / start-of-day site condition snapshots"],"limitations":["schedule-driven, not event-driven — it cannot react to a live incident between ticks","answer quality depends on the VLM and question phrasing; needs the celia-vlm-serving service","no per-object localization or tracking — scene-level verdicts only"],"entry_point":"vlm_periodic.module:VlmPeriodicModule","config_schema":"config.schema.json","io":{"input":{"topic":"deepstream_frame","contract":"celia.contracts.DeepstreamFrameMessage"},"output":{"topic":"evidence_request","contract":"celia.contracts.EvidenceRequest"}},"requires":{"models":[],"tracking":"none"},"verification":{"golden_cases":"tests/fixtures"}},"cl02":{"rule_codes":["vlm_periodic","cl01","cl02","cl03"],"name":"VLM periodic — scheduled full-frame VLM check","version":"1.0.0","domain":"karterschool","kind":"frame","tier":0,"description":"Scheduled full-frame Vision-Language-Model check — the catch-all rule for requirements that need scene UNDERSTANDING rather than object detection. On a Mon-Fri schedule (one-shot at fixed times + periodic every N seconds within a time window, in the camera's timezone) it emits a full-frame snapshot event; the downstream VLM service answers a configured natural-language question about the scene (e.g. \"is the room tidy?\", \"are curtains closed during nap time?\", \"is the gate locked?\"). No detector, no tracker, no model from the zoo — pure schedule. The cl01/cl02/cl03 codes are the same module with different questions/schedules per deployment. This is usually the fastest way to cover a new requirement BEFORE investing in a dedicated rule or model: write a question + schedule instead of code.\n","use_cases":["periodic housekeeping / tidiness / setup checks (room state, equipment presence)","open-ended compliance questions a detector cannot answer (signage, door/gate state, lighting)","fast prototyping of a new requirement as a VLM question before building a dedicated rule","end-of-day / start-of-day site condition snapshots"],"limitations":["schedule-driven, not event-driven — it cannot react to a live incident between ticks","answer quality depends on the VLM and question phrasing; needs the celia-vlm-serving service","no per-object localization or tracking — scene-level verdicts only"],"entry_point":"vlm_periodic.module:VlmPeriodicModule","config_schema":"config.schema.json","io":{"input":{"topic":"deepstream_frame","contract":"celia.contracts.DeepstreamFrameMessage"},"output":{"topic":"evidence_request","contract":"celia.contracts.EvidenceRequest"}},"requires":{"models":[],"tracking":"none"},"verification":{"golden_cases":"tests/fixtures"}},"cl03":{"rule_codes":["vlm_periodic","cl01","cl02","cl03"],"name":"VLM periodic — scheduled full-frame VLM check","version":"1.0.0","domain":"karterschool","kind":"frame","tier":0,"description":"Scheduled full-frame Vision-Language-Model check — the catch-all rule for requirements that need scene UNDERSTANDING rather than object detection. On a Mon-Fri schedule (one-shot at fixed times + periodic every N seconds within a time window, in the camera's timezone) it emits a full-frame snapshot event; the downstream VLM service answers a configured natural-language question about the scene (e.g. \"is the room tidy?\", \"are curtains closed during nap time?\", \"is the gate locked?\"). No detector, no tracker, no model from the zoo — pure schedule. The cl01/cl02/cl03 codes are the same module with different questions/schedules per deployment. This is usually the fastest way to cover a new requirement BEFORE investing in a dedicated rule or model: write a question + schedule instead of code.\n","use_cases":["periodic housekeeping / tidiness / setup checks (room state, equipment presence)","open-ended compliance questions a detector cannot answer (signage, door/gate state, lighting)","fast prototyping of a new requirement as a VLM question before building a dedicated rule","end-of-day / start-of-day site condition snapshots"],"limitations":["schedule-driven, not event-driven — it cannot react to a live incident between ticks","answer quality depends on the VLM and question phrasing; needs the celia-vlm-serving service","no per-object localization or tracking — scene-level verdicts only"],"entry_point":"vlm_periodic.module:VlmPeriodicModule","config_schema":"config.schema.json","io":{"input":{"topic":"deepstream_frame","contract":"celia.contracts.DeepstreamFrameMessage"},"output":{"topic":"evidence_request","contract":"celia.contracts.EvidenceRequest"}},"requires":{"models":[],"tracking":"none"},"verification":{"golden_cases":"tests/fixtures"}},"vlm_periodic":{"rule_codes":["vlm_periodic","cl01","cl02","cl03"],"name":"VLM periodic — scheduled full-frame VLM check","version":"1.0.0","domain":"karterschool","kind":"frame","tier":0,"description":"Scheduled full-frame Vision-Language-Model check — the catch-all rule for requirements that need scene UNDERSTANDING rather than object detection. On a Mon-Fri schedule (one-shot at fixed times + periodic every N seconds within a time window, in the camera's timezone) it emits a full-frame snapshot event; the downstream VLM service answers a configured natural-language question about the scene (e.g. \"is the room tidy?\", \"are curtains closed during nap time?\", \"is the gate locked?\"). No detector, no tracker, no model from the zoo — pure schedule. The cl01/cl02/cl03 codes are the same module with different questions/schedules per deployment. This is usually the fastest way to cover a new requirement BEFORE investing in a dedicated rule or model: write a question + schedule instead of code.\n","use_cases":["periodic housekeeping / tidiness / setup checks (room state, equipment presence)","open-ended compliance questions a detector cannot answer (signage, door/gate state, lighting)","fast prototyping of a new requirement as a VLM question before building a dedicated rule","end-of-day / start-of-day site condition snapshots"],"limitations":["schedule-driven, not event-driven — it cannot react to a live incident between ticks","answer quality depends on the VLM and question phrasing; needs the celia-vlm-serving service","no per-object localization or tracking — scene-level verdicts only"],"entry_point":"vlm_periodic.module:VlmPeriodicModule","config_schema":"config.schema.json","io":{"input":{"topic":"deepstream_frame","contract":"celia.contracts.DeepstreamFrameMessage"},"output":{"topic":"evidence_request","contract":"celia.contracts.EvidenceRequest"}},"requires":{"models":[],"tracking":"none"},"verification":{"golden_cases":"tests/fixtures"}}},"models":{"karter-child-adult":{"version":1,"task":"detection","labels":["adult","child"],"artifact":"blob://ai-models/karter/child_adult.onnx","digest":null,"used_by":[]},"karter-par-dinov2":{"version":1,"task":"classification","labels":["not_using_phone","using_phone","unknown","child","adult"],"artifact":"blob://ai-models/karter/dinov2_par.onnx","digest":null,"used_by":[]},"karter-phone":{"version":1,"task":"detection","labels":["phone"],"artifact":"blob://ai-models/karter/phone.onnx","digest":null,"used_by":[]},"karter-yolo11":{"version":1,"task":"detection","labels":["person"],"artifact":"blob://ai-models/karter/yolo11.onnx","digest":null,"used_by":[]},"par":{"version":1,"task":"classification","labels":["gender_male","gender_female","gender_other","age_less_than_15","age_16_30","age_30_35","age_46_60","age_more_than_60","head_hair_loss","head_long_hair","head_black_hair","head_color_hair","head_hat","head_glasses","head_sun_glasses","head_facemask","head_cover","head_other","shirt_somi","shirt_tshirt","shirt_sweater","shirt_hoodie","shirt_short_coat","shirt_long_coat","shirt_vest","shirt_skirt","shirt_ppe","shirt_rain_coat","shirt_none","shirt_long","shirt_other","tayao_long","tayao_short","tayao_tanktop","shirtcolor_black","shirtcolor_white","shirtcolor_gray","shirtcolor_red","shirtcolor_green","shirtcolor_blue","shirtcolor_yellow","shirtcolor_brown","shirtcolor_purple","shirtcolor_pink","shirtcolor_orange","shirtcolor_mix","shirtcolor_other","pant_jean","pant_quanthung","pant_leging","pant_vest","pant_vay_xoe","pant_vay_suon","pant_vay_bo","pant_other","pantlength_angle","pantlength_knee","pantlength_upper_knee","pantcolor_black","pantcolor_white","pantcolor_gray","pantcolor_red","pantcolor_green","pantcolor_blue","pantcolor_yellow","pantcolor_brown","pantcolor_purple","pantcolor_pink","pantcolor_orange","pantcolor_mix","pantcolor_other","shoes_slipper","shoes_sport_shoes","shoes_boot","shoes_giay_au","shoes_caogot","shoes_bupbe","shoes_other","status_stand","status_sit","status_lie","status_sleep","status_walk_run","status_other","action_use_phone","action_hand_taking","action_deo_vat_tren_canh_tay","action_deo_vat_tren_vai","action_be_tre_em","action_cong_tre_em","action_bung_be","action_push","action_other","thing_phone","thing_tui_vi_cam_tay","thing_tui_vi_xach_tay","thing_tui_deo_cheo","thing_backback","thing_box","thing_weapond","thing_clothes","thing_other"],"artifact":"blob://ai-models/par/par_2609_dynamic_shape.onnx","digest":null,"used_by":["PAR02"]},"yolov8m-warehouse":{"version":2,"task":"detection","labels":["person","forklift","product","pallet"],"artifact":"blob://ai-models/warehouse/yolov8m_v2.onnx","digest":null,"used_by":[]}}}