feat(api,db,ui): forecast champion selector slice B — async comparison & results#361
Conversation
There was a problem hiding this comment.
Sorry @w7-mgfcode, you have reached your weekly rate limit of 500000 diff characters.
Please try again later or upgrade to continue using Sourcery
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Closes #360 · Slice B of 3 (A → B → C) of the Forecast Champion Selector.
PRP:
PRPs/forecast-champion-selector-slice-b-async-comparison-results.md.Converts the synchronous champion comparison into a DB-backed long-running
operation and builds the results-visualization UI. Mirrors the proven async LRO
pattern in
app/features/batch/slice-locally (no cross-slice import).Backend
POST /model-selection/runs— 202 immediate, fire-and-forget. Insertsthe parent (
running) + Nmodel_selection_candidaterows, then launches adetached
asyncio.create_task(held in a module set to dodge the GCfoot-gun) and returns 202 with
status=running,Location/Retry-Afterheaders, and
monitor_url/cancel_url— BEFORE any backtest finishes. Theworker uses
get_session_maker()sessions, never the requestdb.DELETE /model-selection/{id}— cooperative cancel + drain(200 settled / 404 / 409 terminal / 504 drain timeout). Pending candidates
skip; running ones stop at the next safe yield (sklearn/LightGBM fits are
uncancellable mid-call — honest). Invariant: no candidate left
runningafter drain.
GET /{id}— additive liveprogress(GROUP BY children) +candidate_progress; terminal output is byte-compatible with the sync/run.model_selection_candidatechild table (FK CASCADE) +started_at+ fourcount columns on
model_selection_run+cancelledstatus (enum +CheckConstraint) — one Alembic migration (
d3e4f5a6b7c8, up/down verified).runner.py(TaskGroup + Semaphore +CancelHandleregistry,mirror of
batch/runner.py);Settings.model_selection_global_max_parallel(4, set to 1 ⇒ sequential) +
…_cancel_drain_timeout_seconds(30) +.env.example.rank_candidates/build_chart_data/explain_winnerunchanged.One correctness fix beyond the blueprint
The detached pattern opens a window where a
DELETEcan arrive before the workerregisters its
CancelHandle— which would be misreported as a false 409. Fixedby eagerly registering the handle in
submit_run(the worker'ssetdefaultreuses it), so the cancel surface exists the moment 202 returns.
Frontend (extends the Slice A page)
useSubmitSelectionRun/useSelectionRun(polls every 2s, stops on terminal)/
useCancelSelectionRun.components/champion-selector/results/: progress panel, ranking table, winnercard, comparison charts, model-detail drawer (Sheet), cancel dialog
(AlertDialog), + a
.tsconstants module.champion.tsx: the CTA now submits → polls live progress → renders thewinner card, ranking table, comparison charts, and per-model detail drawer;
partial/all-failed/cancelled states render and failed candidates stay visible.
types/api.ts(SubmitRunResponse,SelectionProgress,CandidateProgress,'cancelled').Scope boundaries (no Slice C)
No train-selected, predict-decision, promotion/alias, safety stock, or
forecast-decision panel.
auto_train_winner/auto_predictare no-ops in theasync worker. The legacy sync
POST /runand the existingtrain-winner/predictendpoints are untouched and unwired.Validation
ruff check .+format --check✅pytest -m "not integration"✅ 1781 passed (+28 Slice B: runner, schemas,service, async routes)
pytest -m integration✅ 13 passed (202 immediacy,poll→winner, failed-candidate-visible, no candidate left
runningafterdrain, 409, legacy-no-progress)
pyright app/features/model_selection✅ 0 errorstsc✅ ·lint✅ (1 pre-existing TanStack Table warning) ·test✅ 298 passed
Known pre-existing / limitations
mypy app//pyright app/reportlightgbm/xgboostoptional-dep importerrors in
forecasting/+registry/(CI installs the extras) — none inmodel_selection.running(no reconcile pass) —an accepted single-host tradeoff, not built here (per the PRP).
settle the whole run before the DELETE arrives (the honest race); the
load-bearing assertion (no candidate left
running) holds either way.Slice C (decision layer: train/predict/business interpretation/override/promotion)
follows as a separate issue.