Commit Graph

2 Commits

Author SHA1 Message Date
Croissant Le Doux
7e26de1b6c feat: calibration from closed-issue actuals → forecast flips off cold-start (#1)
Close the D3 loop. The forecast now learns from the team's own estimate-vs-actual
history (the working time #5 infers from git events) instead of guessing forever.

core (@commitea/core/calibration-v0):
- fitCalibration(samples): lognormal fit on log(actual/estimate) — global +
  per-bucket (once a bucket clears the floor) + per-person bias. coldStart until
  n >= 20 closed-with-estimate issues.
- calibrationSamples(): pull those samples from the closed backlog via lifecycle
  inference (estimate label vs inferred actualWorkingDays).
- toDurationModel(): project the fit to the params forecast consumes.
- forecast() gains options.model: when past cold-start, fitted params drive the
  sim (per bucket, global fallback); otherwise the code priors do. Forecast.coldStart
  now reflects the model. nearestBucket extracted + exported.

app:
- AppShell fits calibration once from the reconciled backlog, feeds the model into
  forecastBacklog (cone), and drives the Calibration screen + Runway header.
- Focus cone footer, Runway note, and Calibration screen now say cold-start (N/20)
  vs calibrated (on N closed) from real data; Calibration scatter / bucket bias /
  per-person all fitted, degrading honestly on a thin dataset.

Known refinement: same-day closes yield 0 working-day actuals (day-granular) and
are excluded, so a fast-moving repo can sit at n=0 — honest, but a fractional
(hours-based) actual would let those count. Per-person uses gitea login, not
display name, until the person map lands.

Note: also re-lands #10 (Monte Carlo) and #5 (lifecycle) which merged into their
stacked base branches but never propagated to main (stacked-merge trap); this
branch is cut from main and carries all three so main is whole again.

Verified: 74 core tests green (9 calibration + 2 forecast-switch added), desktop
typecheck clean, 14 fixture e2e green, live spec asserts the real cold-start
calibration surface (Runway note + screen badge fitted from actuals).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 19:51:50 -04:00
Croissant Le Doux
be70c8607d feat: Monte Carlo forecast → real burn-up cone (#10)
Replace the demo cone on Morning service with a real, seeded Monte Carlo
forecast over the open backlog. The LLM never does this — it's plain,
reproducible code (evidence-based scheduling).

core (@commitea/core/forecast-v0):
- Code-resident lognormal cold-start priors per estimate bucket (D3):
  sampled actual = estimate * exp(N(mu, sigma)), mu > 0 (actuals run long),
  sigma shrinks as tickets grow. Replaced by the team's empirical fit at
  n >= 20 (#5 supplies the actuals).
- forecast(): seeded mulberry32 + Box-Muller over the scheduler's
  deterministic order (order is fixed from estimates/deps; only durations
  vary, so the cone stretches, never reorders). Returns p50/p80/p95 landing
  + a per-issue burn-up curve (p10/p50/p90). 12 unit tests; reproducible.

renderer:
- lib/dates.ts: working-day -> calendar mapper (skips weekends) + buildBurnUpData.
- BurnUpCone gains a data-driven twin; falls back byte-identical to the
  fixture cone when no forecast (demo mode unchanged).
- Focus card shows the real "80% of the open backlog lands by <range>",
  real scope count, and names the cold-start priors.

v0 scope (each a later slice): single serial worker (capacity is #8);
cold-start priors only (empirical fit is #5); no historical actual polyline
(needs lifecycle events, #5). Header chrome (reconcile time, ahead/behind
badge) stays fixture until milestone due dates land.

Verified: 51 core tests green, desktop typecheck clean, 14 fixture e2e green,
live spec asserts the real cone renders (25 open issues, "lands by Nov 11-27").

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 19:42:46 -04:00