feat(level-blocks): phase completion-priority lateness/earliness scorers
Generalize earlier phase-lateness and early-start scoring terms into
standard registry blocks. Phases are data (an array of
{ name?, taskUniqueIds, targetDay, weight? }) rather than hardcoded:
- PhaseLateness charges the working days a phase's latest task finishes past
its target day; PhaseEarliness charges the working days its earliest task
starts before its target. Each is weighted by the phase's
completion-priority weight (an earlier completionWeight, a static per-phase
priority) and summed. Two blocks, not one moded block: the originals anchor
them on different targets (window end vs start) and rank them in different
lexicographic tiers, and a single phase carries both at once.
- Deviation is counted in working days via the shared calendar prefix sum;
the per-day scale (phaseLatenessPerDay/earlyStartPerDay in the original
formulation) comes from the run config's scorer weight. Both declare
reads.taskUniqueIds so analyzeRun flags a phase pointing at a missing task.
Registered as levelset.PhaseLateness.v1 / levelset.PhaseEarliness.v1
(estimator tier); shared logic in phaseScoring.ts. Tests cover schema,
hand-computed scoring on the Mon-Fri fixture (including a realistic-shaped
partially-complete phase), the MiniZinc fragment, and RunConfig wiring.