TARGET PROJECT: scheduler — HANDOFF NOTE

Purpose / fixed interface:
Implement evaluate(payload) -> {selected:[id], blocked:[id], rejected:[id]}.
payload: now (offset-aware ISO datetime), appointments with id, start (offset-aware ISO datetime), positive duration in minutes, integer priority, cancelled boolean.
Do not mutate payload. Use zoneinfo or equivalent timezone conversion.

CURRENT CONFIGURATION (only current values; superseded and unrelated archived-project values discarded):
{
  "timezone": "America/New_York",
  "cutoff_hour": 17,
  "cutoff_inclusive": true,
  "lead_minutes": 30,
  "adjacency_allowed": true,
  "duration_max": 120,
  "duplicate_policy": "first",
  "cancelled_blocks": true,
  "capacity": 1,
  "report_order": "id",
  "blackout_day": "2026-11-02",
  "priority_high_first": false
}

Processing contract:
- Deduplicate by id before processing, preserving first insertion position and using the value selected by duplicate_policy. With current policy "first", keep the first value. The retained row's original index is that retained value's actual original index.
- Process stably by priority: descending only if priority_high_first is true; otherwise ascending. Current setting is false, so priority ascending. Ties use retained original index.
- Start/end arithmetic must use UTC elapsed minutes, not local wall-clock minutes.
- Duration must be > 0 and <= duration_max (120).
- Start must be >= now + lead_minutes (30 minutes).
- Convert start to timezone America/New_York. Reject if its local calendar date is blackout_day 2026-11-02.
- Cutoff is defined on the start's local calendar day at cutoff_hour 17:00 in America/New_York. Because cutoff_inclusive is true, end must be <= cutoff.
- Cancelled handling: a cancelled appointment is rejected if cancelled_blocks is false; otherwise it may reserve capacity and appears in blocked instead of selected. Current setting is true, so valid cancelled appointments can block capacity and go to blocked.
- Capacity: an appointment can be accepted only if fewer than capacity (1) already-accepted intervals overlap it. Count intersecting accepted intervals, not peak concurrency.
- Overlap: adjacency_allowed=true means intervals sharing only an endpoint do not overlap.
- All invalid or conflicting ids go to rejected.
- Report ordering: report_order="id", so sort each output list (selected, blocked, rejected) by id.

Unresolved items: None. All 12 configuration keys above are currently set.