Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .python-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.11
3.12
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

- Annual-planning readiness now evaluates effective program policy snapshots, recurring program calendar anchors, and policy-layered institutional events together, so coordinators see the full DB-backed policy stack instead of only institutional-event coverage before annual optimization or repair work.
- The annual-planning hub's policy rollover action now refreshes program policy snapshots and recurring calendar anchors alongside institutional events, keeping academic-year policy carry-forward aligned across all current DB-backed policy sources.
- The annual proving-pass runner now records AY readiness preflight results in its markdown/JSON report and stops before any writes when resident, faculty-shape, or policy-anchor blockers are present, unless the operator explicitly overrides that guard.
- Annual planner lifecycle endpoints now require scheduler-level access on the backend, matching the scheduler-only annual-planning UI so residents cannot enumerate or mutate year-level plans directly through the API.
- The annual-planning hub now surfaces the 14-sheet academic-year workbook export from the existing `/export/schedule/year/xlsx` backend path, so coordinators can download current AY schedule truth straight from the planning UI while they review year-level generation and repair work.
Expand All @@ -26,6 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Added admin rollover endpoints for `ProgramPolicySnapshot` and `ProgramCalendarAnchor`, plus annual-planning hub views for the effective program-policy snapshot and effective recurring calendar anchors for a selected academic year.
- Added a recent proving-pass report panel to the annual-planning hub so schedulers can review baseline/shock/repair drill outcomes for the selected academic year without leaving the coordinator surface.
- Added a scheduler-only annual-planner proving-pass report feed sourced from the native `docs/reports/automation/annual_proving_pass_*.json` artifacts, so recent baseline/repair drill outcomes can be surfaced in the app instead of staying trapped in local files.
- Added an annual-planning readiness preflight API for AY-scoped resident roster, faculty weekly-shape, and policy-anchor checks so schedulers can see hard blockers before attempting yearly optimization.
Expand Down
6 changes: 3 additions & 3 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# TODO — Actionable Items

> **Updated:** 2026-03-21 (annual proving pass reaches actionable repair drafts)
> **Updated:** 2026-03-21 (policy-layer readiness/review/rollover actualization in progress)
> **Source:** Extracted from architecture docs, planning docs, code TODOs, and Explore agent audit.
> **Companion:** `docs/planning/ROADMAP.md` (macro vision), `docs/planning/TECHNICAL_DEBT.md` (debt tracker)
> **Cutover tracker:** `docs/planning/ROTATION_SHAPE_CUTOVER_STATUS_20260320.md` (merged vs open PR vs untouched rotation-shape/constraint cutover work)
Expand All @@ -24,7 +24,7 @@

- [x] **DB-backed constraint binding cutover** — Merged [#1409](https://github.com/Euda1mon1a/Autonomous-Assignment-Program-Manager/pull/1409) and [#1410](https://github.com/Euda1mon1a/Autonomous-Assignment-Program-Manager/pull/1410); mutable hard/soft policy now lives in DB-backed scoped bindings on `main`.
- [x] **Faculty weekly shapes** — Primary operational blocker from `docs/reviews/FULL_STACK_AUDIT_20260320.md`. Replaced heuristic-first faculty scheduling with baseline weekly shapes, role drivers, person deltas, and week-specific overrides across merged PRs [#1412](https://github.com/Euda1mon1a/Autonomous-Assignment-Program-Manager/pull/1412) through [#1419](https://github.com/Euda1mon1a/Autonomous-Assignment-Program-Manager/pull/1419).
- [ ] **Program / regulatory / institution policy tables** — Add DB-backed layers above rotation shapes for PGY-wide rules, ACGME policy, military/institution policy, and program calendar anchors.
- [ ] **Program / regulatory / institution policy tables** — `ProgramPolicySnapshot`, `ProgramCalendarAnchor`, and policy-layered `InstitutionalEvent` tables/admin surfaces now exist, and the annual-planning hub can now assess, review, and roll all three sources together. Remaining: move the surviving higher-order policy still trapped in Python into these DB-backed layers without making ad hoc scheduling-logic changes.
- [ ] **13-block AY 26-27 draft / validate / publish workflow** — Core lifecycle is merged on `main` via [#1425](https://github.com/Euda1mon1a/Autonomous-Assignment-Program-Manager/pull/1425) through [#1428](https://github.com/Euda1mon1a/Autonomous-Assignment-Program-Manager/pull/1428). Current hardening stack is [#1452](https://github.com/Euda1mon1a/Autonomous-Assignment-Program-Manager/pull/1452) through [#1457](https://github.com/Euda1mon1a/Autonomous-Assignment-Program-Manager/pull/1457), plus [#1468](https://github.com/Euda1mon1a/Autonomous-Assignment-Program-Manager/pull/1468), [#1484](https://github.com/Euda1mon1a/Autonomous-Assignment-Program-Manager/pull/1484), and [#1485](https://github.com/Euda1mon1a/Autonomous-Assignment-Program-Manager/pull/1485): publish aliases, naive UTC timestamps, idempotent block-draft regeneration, annual optimization leave pressure, resilience-helper proving-pass fixes, and exact shock-impact assignment review. Native proving passes now reach baseline publish, repair publish, and repair-draft publish. Remaining: merge the open stack and keep tightening annual diff/review ergonomics.
- [ ] **Shock-event model + targeted regeneration** — Initial `Absence`-driven shock preview/draft slice is merged on `main`. Current extensions are [#1457](https://github.com/Euda1mon1a/Autonomous-Assignment-Program-Manager/pull/1457), [#1468](https://github.com/Euda1mon1a/Autonomous-Assignment-Program-Manager/pull/1468), and [#1485](https://github.com/Euda1mon1a/Autonomous-Assignment-Program-Manager/pull/1485), which feed approved/confirmed `Absence` rows into annual optimization as leave requests, persist shock repair scope even when the annual rotation diff is unchanged, keep repair generation from aborting on archived combo templates, legacy encrypted absence fields, or fully absent repair residents, and now expose the exact resident/block assignments affected by a shock before draft creation. Remaining: broader blast-radius analysis and repair publish flow.

Expand Down Expand Up @@ -78,7 +78,7 @@
- [ ] **ACGME call duty validation gap** — `call_assignments` excluded from 24+4/rest checks. **Blocked on MEDCOM ruling.**
- [ ] **Faculty weekly-shape gaps** — Current faculty scheduling still leans on incomplete weekly templates and heuristics. Rebuild this around reusable baseline weekly shapes, role drivers, and overrides. See `docs/architecture/FACULTY_WEEKLY_SHAPE_REFERENCE_20260320.md`.
- [ ] **Closed-loop validation pipeline** — Automated generate → validate → diagnose → fix → regenerate loop. Not yet implemented.
- [ ] **Program calendar / holiday anchors** — Add a Postgres-backed calendar layer for holiday-anchored program events, deadlines, and equity checkpoints above rotation shapes. Do not copy these anchors into outpatient weekly patterns. See `docs/planning/HOLIDAY_ANCHORED_RESIDENCY_CALENDAR.md` and `docs/planning/HANDBOOK_ADDENDUM_HOLIDAY_ANCHORED_CALENDAR.md`.
- [ ] **Program calendar / holiday anchors** — Base Postgres-backed anchor tables and annual-planning review/rollover surfaces now exist. Remaining: cut the live preload, validation, and downstream operator workflows over to the new calendar layer instead of leaving those semantics split between DB rows and Python-owned policy. See `docs/planning/HOLIDAY_ANCHORED_RESIDENCY_CALENDAR.md` and `docs/planning/HANDBOOK_ADDENDUM_HOLIDAY_ANCHORED_CALENDAR.md`.
- [ ] **DEBT-025: 5 pre-existing failing tests** — `test_min_limit_enforcement`, `test_engine_calls_faculty_expansion`, `test_pcat_do_created_for_each_call`, `test_cpsat_allows_templates_requiring_procedure_credential`, `test_cpsat_respects_locked_blocks`.

### Infrastructure
Expand Down
20 changes: 20 additions & 0 deletions backend/app/api/routes/program_calendar_anchors.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from app.schemas.program_calendar_anchor import (
ProgramCalendarAnchorCreate,
ProgramCalendarAnchorListResponse,
ProgramCalendarAnchorRolloverResponse,
ProgramCalendarAnchorResponse,
ProgramCalendarAnchorUpdate,
)
Expand Down Expand Up @@ -50,6 +51,25 @@ async def list_program_calendar_anchors(
)


@router.post("/rollover", response_model=ProgramCalendarAnchorRolloverResponse)
async def rollover_program_calendar_anchors(
source_academic_year: int = Query(..., ge=2020, le=2100),
target_academic_year: int = Query(..., ge=2020, le=2100),
db: AsyncSession = Depends(get_async_db),
current_user: User = Depends(get_admin_user),
):
del current_user
try:
return await db.run_sync(
lambda sync_db: ProgramCalendarAnchorService(sync_db).rollover_anchors(
source_academic_year=source_academic_year,
target_academic_year=target_academic_year,
)
)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc


@router.get("/{anchor_id}", response_model=ProgramCalendarAnchorResponse)
async def get_program_calendar_anchor(
anchor_id: UUID,
Expand Down
19 changes: 19 additions & 0 deletions backend/app/api/routes/program_policy_snapshots.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from app.schemas.program_policy_snapshot import (
ProgramPolicySnapshotCreate,
ProgramPolicySnapshotListResponse,
ProgramPolicySnapshotRolloverResponse,
ProgramPolicySnapshotResponse,
ProgramPolicySnapshotUpdate,
)
Expand Down Expand Up @@ -40,6 +41,24 @@ async def list_program_policy_snapshots(
)


@router.post("/rollover", response_model=ProgramPolicySnapshotRolloverResponse)
async def rollover_program_policy_snapshots(
source_academic_year: int = Query(..., ge=2020, le=2100),
target_academic_year: int = Query(..., ge=2020, le=2100),
db: AsyncSession = Depends(get_async_db),
_current_user: User = Depends(get_admin_user),
):
try:
return await db.run_sync(
lambda sync_db: ProgramPolicySnapshotService(sync_db).rollover_snapshots(
source_academic_year=source_academic_year,
target_academic_year=target_academic_year,
)
)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc


@router.get("/{snapshot_id}", response_model=ProgramPolicySnapshotResponse)
async def get_program_policy_snapshot(
snapshot_id: UUID,
Expand Down
12 changes: 11 additions & 1 deletion backend/app/schemas/annual_rotation.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,10 +255,20 @@ class AnnualRotationPlanningReadinessFacultyResponse(BaseModel):


class AnnualRotationPlanningReadinessPolicyResponse(BaseModel):
"""Institutional-event policy-anchor readiness for annual planning."""
"""DB-backed policy-layer readiness for annual planning."""

status: Literal["ready", "warning", "blocked"]
has_effective_program_policy: bool
effective_program_policy_source: Literal["default", "academic_year"] | None = None
active_anchor_count: int
active_calendar_anchor_count: int
active_event_count: int
program_anchor_count: int
regulatory_anchor_count: int
institution_anchor_count: int
program_calendar_anchor_count: int
regulatory_calendar_anchor_count: int
institution_calendar_anchor_count: int
program_event_count: int
regulatory_event_count: int
institution_event_count: int
Expand Down
10 changes: 10 additions & 0 deletions backend/app/schemas/program_calendar_anchor.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,13 @@ class ProgramCalendarAnchorEffectiveListResponse(BaseModel):
total: int
page: int
page_size: int


class ProgramCalendarAnchorRolloverResponse(BaseModel):
source_academic_year: int = Field(..., ge=2020, le=2100)
target_academic_year: int = Field(..., ge=2020, le=2100)
source_anchor_count: int = Field(..., ge=0)
created_count: int = Field(..., ge=0)
skipped_count: int = Field(..., ge=0)
created_names: list[str] = Field(default_factory=list)
skipped_names: list[str] = Field(default_factory=list)
10 changes: 10 additions & 0 deletions backend/app/schemas/program_policy_snapshot.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,13 @@ class ProgramPolicySnapshotListResponse(BaseModel):
class ProgramPolicySnapshotEffectiveResponse(ProgramPolicySnapshotResponse):
requested_academic_year: int
source: Literal["default", "academic_year"]


class ProgramPolicySnapshotRolloverResponse(BaseModel):
source_academic_year: int = Field(..., ge=2020, le=2100)
target_academic_year: int = Field(..., ge=2020, le=2100)
source_snapshot_count: int = Field(..., ge=0)
created_count: int = Field(..., ge=0)
skipped_count: int = Field(..., ge=0)
created_names: list[str] = Field(default_factory=list)
skipped_names: list[str] = Field(default_factory=list)
Loading