TARGET PROJECT HANDOFF: scheduler

Current configuration (final merged state; superseded and unrelated-project values discarded):
{
  "timezone": "UTC",
  "cutoff_hour": 18,
  "cutoff_inclusive": false,
  "lead_minutes": 60,
  "adjacency_allowed": false,
  "duration_max": 90,
  "duplicate_policy": "last",
  "cancelled_blocks": false,
  "blackout_day": "2026-11-01",
  "priority_high_first": true,
  "capacity": 2,
  "report_order": "input"
}

Fixed interface:
- Implement evaluate(payload) returning {selected: [id], blocked: [id], rejected: [id]}.
- payload contains now (offset-aware ISO datetime) and appointments.
- Each appointment has id, start (offset-aware ISO datetime), positive duration in minutes, integer priority, and cancelled boolean.
- Do not mutate input.
- Use zoneinfo or equivalent timezone conversion.

Required algorithm:
- Deduplicate by id, using the first or last value according to duplicate_policy. Current policy: "last". The retained row's original index is the retained value's actual original index.
- Stably process by priority according to priority_high_first, with ties by original index. Current setting: priority descending.
- Compute start/end using UTC elapsed minutes, not local wall-clock minutes.
- Duration must be > 0 and <= duration_max (currently 90).
- Start must be >= now + lead_minutes (currently 60 minutes).
- Convert start to the configured timezone (currently UTC). Reject the appointment if its local calendar day is blackout_day (currently 2026-11-01).
- Define the cutoff as cutoff_hour:00 on the start's local calendar day in that timezone (currently 18:00). End must be <= cutoff if cutoff_inclusive is true, otherwise < cutoff. Current setting: exclusive, so end < cutoff.
- A cancelled appointment is rejected when cancelled_blocks is false; when true, it may reserve capacity and appears in blocked instead of selected. Current setting: false, so cancelled appointments are rejected and do not block.
- An appointment may be accepted only if fewer than capacity (currently 2) already-accepted intervals overlap it. Count intersecting accepted intervals, not peak concurrency.
- adjacency_allowed determines whether intervals sharing only an endpoint overlap. Current setting: false, so shared endpoints count as overlap.
- Sort each output list by id or retained original index according to report_order. Current setting: "input", meaning retained original index.
- All invalid or conflicting appointment ids go to rejected.

Exceptions/unresolved items:
- None recorded for scheduler beyond the rules above.