OPENENV_RL_01 / app /signal_computer.py
Siddharaj Shirke
deploy: fresh snapshot to Hugging Face Space
3eae4cc
"""
signal_computer.py — Gov Workflow OpenEnv v2.0
Computes normalized compressed state signals for observations.
All signals are deterministic and normalized to [0.0, 1.0].
"""
from typing import Dict
from app.models import QueueSnapshot, OfficerPool
class ComputedSignals:
def __init__(self):
self.backlog_pressure: float = 0.0
self.sla_risk_score: float = 0.0
self.fairness_index: float = 1.0
self.resource_utilization: float = 0.0
self.digital_intake_ratio: float = 0.5
self.blocked_cases_missing_docs: int = 0
self.blocked_cases_enrichment: int = 0
self.field_verification_load: float = 0.0
class SignalComputer:
def compute(
self,
queue_snapshots: Dict[str, QueueSnapshot],
officer_pool: OfficerPool,
todays_arrivals: int = 0,
digital_arrivals: int = 0,
capacity_per_day: float = 1.0,
) -> ComputedSignals:
signals = ComputedSignals()
snapshots = list(queue_snapshots.values())
if not snapshots:
return signals
total_pending = sum(s.total_pending for s in snapshots)
# Backlog pressure
capacity_ceiling = max(1.0, capacity_per_day * 5.0)
signals.backlog_pressure = min(1.0, total_pending / capacity_ceiling)
# SLA risk score (weighted average)
total_nonzero = max(1, total_pending)
signals.sla_risk_score = min(1.0, max(0.0,
sum(s.current_sla_risk * s.total_pending for s in snapshots) / total_nonzero
))
# Fairness index (1 - coefficient of variation of completion rates)
if len(snapshots) < 2:
signals.fairness_index = 1.0
else:
rates = []
for s in snapshots:
total = s.total_pending + s.total_completed_today
rates.append(s.total_completed_today / max(1, total) if total > 0 else 0.0)
mean = sum(rates) / len(rates)
if mean > 0:
variance = sum((r - mean) ** 2 for r in rates) / len(rates)
cv = (variance ** 0.5) / mean
signals.fairness_index = max(0.0, 1.0 - min(1.0, cv))
else:
signals.fairness_index = 1.0
# Resource utilization
allocated = sum(officer_pool.allocated.values())
signals.resource_utilization = min(1.0, allocated / max(1, officer_pool.available_officers))
# Digital intake ratio
signals.digital_intake_ratio = (
min(1.0, digital_arrivals / todays_arrivals) if todays_arrivals > 0 else 0.5
)
# Blocked cases
signals.blocked_cases_missing_docs = sum(s.blocked_missing_docs for s in snapshots)
signals.blocked_cases_enrichment = sum(s.blocked_enrichment for s in snapshots)
# Field verification load
total_in_field = sum(s.field_verification_pending for s in snapshots)
signals.field_verification_load = total_in_field / total_nonzero if total_nonzero > 0 else 0.0
return signals