Spaces:
Running
Running
| """ | |
| event_engine.py — Gov Workflow OpenEnv v2.0 | |
| Deterministic daily event system. Same seed + day + scenario = same events always. | |
| """ | |
| import random | |
| from typing import List | |
| from app.models import EventType, ScenarioMode, TaskConfig | |
| SCENARIO_MULTIPLIER = { | |
| ScenarioMode.NORMAL: 1.0, | |
| ScenarioMode.CRISIS: 2.0, | |
| ScenarioMode.EXTREME_OVERLOAD: 3.5, | |
| } | |
| BASE_PROBS = { | |
| EventType.SURGE_APPLICATIONS: 0.08, | |
| EventType.OFFICER_UNAVAILABLE: 0.07, | |
| EventType.DOCUMENT_REJECTION_SPIKE: 0.10, | |
| EventType.REVENUE_DB_DELAY: 0.06, | |
| EventType.SLA_ESCALATION_ORDER: 0.05, | |
| } | |
| EVENT_EFFECTS = { | |
| EventType.SURGE_APPLICATIONS: | |
| {ScenarioMode.NORMAL: 1.3, ScenarioMode.CRISIS: 1.5, ScenarioMode.EXTREME_OVERLOAD: 2.0}, | |
| EventType.OFFICER_UNAVAILABLE: | |
| {ScenarioMode.NORMAL: 1, ScenarioMode.CRISIS: 1, ScenarioMode.EXTREME_OVERLOAD: 2}, | |
| EventType.DOCUMENT_REJECTION_SPIKE: | |
| {ScenarioMode.NORMAL: 0.15, ScenarioMode.CRISIS: 0.20, ScenarioMode.EXTREME_OVERLOAD: 0.35}, | |
| EventType.REVENUE_DB_DELAY: | |
| {ScenarioMode.NORMAL: 0.30, ScenarioMode.CRISIS: 0.40, ScenarioMode.EXTREME_OVERLOAD: 0.60}, | |
| EventType.SLA_ESCALATION_ORDER: | |
| {ScenarioMode.NORMAL: 0.50, ScenarioMode.CRISIS: 0.50, ScenarioMode.EXTREME_OVERLOAD: 0.40}, | |
| } | |
| class DayEventParams: | |
| def __init__(self): | |
| self.arrival_multiplier: float = 1.0 | |
| self.officer_reduction: int = 0 | |
| self.doc_defect_rate_boost: float = 0.0 | |
| self.system_dependency_boost: float = 0.0 | |
| self.sla_window_multiplier: float = 1.0 | |
| self.active_events: List[EventType] = [] | |
| def has_events(self) -> bool: | |
| return bool(self.active_events) | |
| class EventEngine: | |
| def __init__(self, seed: int, scenario_mode: ScenarioMode): | |
| self.seed = seed | |
| self.scenario_mode = scenario_mode | |
| self._multiplier = SCENARIO_MULTIPLIER[scenario_mode] | |
| def get_events_for_day(self, day: int, task_config: "TaskConfig") -> List[EventType]: | |
| day_rng = random.Random(self.seed + day * 31337) | |
| active = [] | |
| for event_type in task_config.allowed_events: | |
| if event_type == EventType.NO_EVENT: | |
| continue | |
| base_prob = BASE_PROBS.get(event_type, 0.0) | |
| effective_prob = min(0.80, base_prob * self._multiplier) | |
| if day_rng.random() < effective_prob: | |
| active.append(event_type) | |
| return active if active else [EventType.NO_EVENT] | |
| def apply_events(self, events: List[EventType], task_config: "TaskConfig") -> DayEventParams: | |
| params = DayEventParams() | |
| for event in events: | |
| if event == EventType.NO_EVENT: | |
| continue | |
| params.active_events.append(event) | |
| magnitude = EVENT_EFFECTS.get(event, {}).get(self.scenario_mode, 0) | |
| if event == EventType.SURGE_APPLICATIONS: | |
| params.arrival_multiplier *= magnitude | |
| elif event == EventType.OFFICER_UNAVAILABLE: | |
| params.officer_reduction += int(magnitude) | |
| elif event == EventType.DOCUMENT_REJECTION_SPIKE: | |
| params.doc_defect_rate_boost += magnitude | |
| elif event == EventType.REVENUE_DB_DELAY: | |
| params.system_dependency_boost += magnitude | |
| elif event == EventType.SLA_ESCALATION_ORDER: | |
| params.sla_window_multiplier = min(params.sla_window_multiplier, magnitude) | |
| if not params.active_events: | |
| params.active_events = [EventType.NO_EVENT] | |
| return params | |
| def describe_events(self, events: List[EventType]) -> str: | |
| descriptions = { | |
| EventType.SURGE_APPLICATIONS: "Digital surge: arrivals increased", | |
| EventType.OFFICER_UNAVAILABLE: "Officer absent: reduced capacity", | |
| EventType.DOCUMENT_REJECTION_SPIKE: "Doc rejection spike: higher defect rate", | |
| EventType.REVENUE_DB_DELAY: "Revenue DB delay: land records slower", | |
| EventType.SLA_ESCALATION_ORDER: "SLA escalation order: deadlines tightened", | |
| EventType.NO_EVENT: "No active events today", | |
| } | |
| active = [e for e in events if e != EventType.NO_EVENT] | |
| if not active: | |
| return "No active events today" | |
| return "; ".join(descriptions.get(e, str(e)) for e in active) | |