FlyingNunchucks's picture
Deploy tested GitHub commit 0080b137632d1d201ef3a1f6f7eebcc53777d7f0
445125c verified
Raw History Blame Contribute Delete
5.08 kB
"""Reproducible, public-safe service-operations scenarios; no external data."""
from calendar import monthrange
from datetime import date, datetime, timedelta, timezone
from dataclasses import dataclass
from service_analysis.schemas import Coverage
from service_analysis.quality import prepare_dataset
REGIONS = ("North", "Central", "South")
MONTHS = {"January": ("2026-01-01", "2026-01-31"),
"February": ("2026-02-01", "2026-02-28"),
"March": ("2026-03-01", "2026-03-31")}
@dataclass(frozen=True)
class Scenario:
label: str
description: str
expected_mean_change: float | None
expected_mix: float | None
expected_within: float | None
SCENARIOS = {
"combined": Scenario("Mix and handling change", "More complex requests and slower handling both contribute.",
16, 12, 4),
"case_mix": Scenario("More complex requests", "Category durations stay fixed while the completed-request mix changes.",
12, 12, 0),
"within_category": Scenario("Slower handling", "Category proportions stay fixed while every category takes four hours longer.",
4, 0, 4),
"stable": Scenario("Stable operation", "The completed-request mix and category durations stay unchanged.", 0, 0, 0),
"regional": Scenario("South region slowdown", "Only the South region takes eight hours longer within each category.",
8/3, 0, 8/3),
"workload": Scenario("Workload association", "Daily demand and completed-cohort duration move together in a constructed example.",
None, None, None),
"missing_category": Scenario("Missing category", "One completed request lacks a category; attribution must be withheld.",
16, None, None),
"missing_staffing": Scenario("Missing staffing", "One operations day lacks staffing; workload analysis must be blocked.",
16, 12, 4),
"duplicates": Scenario("Duplicate records", "Exact duplicates must be removed and disclosed before analysis.", 16, 12, 4),
"invalid_records": Scenario("Invalid timestamp", "An invalid source timestamp blocks request-based analysis.",
None, None, None),
}
def load_scenario(name):
if name not in SCENARIOS:
raise ValueError("Unknown demo scenario")
requests, operations = [], []
combined = name in ("combined", "missing_category", "missing_staffing", "duplicates", "invalid_records")
for month in (1, 2, 3):
for day in range(1, monthrange(2026, month)[1]+1):
for region in REGIONS:
phase = (day-1) % 5 + 1
operations.append(dict(date=date(2026, month, day).isoformat(), region=region,
incoming_requests=phase*10, staffed_hours=10))
for region in REGIONS:
if name == "workload":
for day in range(1, monthrange(2026, month)[1]+1):
phase = (day-1) % 5 + 1
opened = datetime(2026, month, day, tzinfo=timezone.utc)
requests.append(dict(request_id=f"demo-{month}-{region}-{day}",
opened_at=opened.isoformat(), resolved_at=(opened+timedelta(hours=10+phase*2)).isoformat(),
category="Routine", region=region))
continue
shifted_mix = month > 1 and (combined or name == "case_mix")
counts = (2, 3, 5) if shifted_mix else (6, 3, 1)
index = 0
for category, count, base_hours in zip(("Routine", "Standard", "Complex"), counts, (10, 20, 40)):
adjustment = 0
if month > 1 and (combined or name == "within_category"):
adjustment = 4
if month > 1 and name == "regional" and region == "South":
adjustment = 8
for _ in range(count):
opened = datetime(2026, month, 5+index*2, tzinfo=timezone.utc)
requests.append(dict(request_id=f"demo-{month}-{region}-{index}",
opened_at=opened.isoformat(), resolved_at=(opened+timedelta(hours=base_hours+adjustment)).isoformat(),
category=category, region=region))
index += 1
if name == "missing_category":
next(row for row in requests if row["request_id"] == "demo-2-South-0")["category"] = None
if name == "missing_staffing":
next(row for row in operations if row["date"] == "2026-02-10" and row["region"] == "South")["staffed_hours"] = None
if name == "invalid_records":
requests[0]["resolved_at"] = "invalid-synthetic-timestamp"
if name == "duplicates":
requests.append(dict(requests[0]))
operations.append(dict(operations[0]))
coverage = Coverage(resolution_start="2026-01-01", resolution_end="2026-03-31",
operations_start="2026-01-01", operations_end="2026-03-31")
return prepare_dataset(requests, operations, coverage)