Skip to content

feat(api,db,ui): forecast champion selector slice B — async comparison & results#361

Merged
w7-mgfcode merged 3 commits into
devfrom
feat/champion-selector-slice-b
Jun 1, 2026
Merged

feat(api,db,ui): forecast champion selector slice B — async comparison & results#361
w7-mgfcode merged 3 commits into
devfrom
feat/champion-selector-slice-b

Conversation

@w7-mgfcode
Copy link
Copy Markdown
Owner

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).

Note: the commit subject uses scope api,db (the commit-format hook caps scope
at a comma-pair); the change also touches ui — frontend files are included.

Backend

  • POST /model-selection/runs — 202 immediate, fire-and-forget. Inserts
    the parent (running) + N model_selection_candidate rows, then launches a
    detached asyncio.create_task (held in a module set to dodge the GC
    foot-gun) and returns 202 with status=running, Location/Retry-After
    headers, and monitor_url/cancel_url — BEFORE any backtest finishes. The
    worker uses get_session_maker() sessions, never the request db.
  • 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 running
    after drain.
  • GET /{id} — additive live progress (GROUP BY children) +
    candidate_progress; terminal output is byte-compatible with the sync /run.
  • New model_selection_candidate child table (FK CASCADE) + started_at + four
    count columns on model_selection_run + cancelled status (enum +
    CheckConstraint) — one Alembic migration (d3e4f5a6b7c8, up/down verified).
  • Slice-local runner.py (TaskGroup + Semaphore + CancelHandle registry,
    mirror of batch/runner.py); Settings.model_selection_global_max_parallel
    (4, set to 1 ⇒ sequential) + …_cancel_drain_timeout_seconds (30) +
    .env.example.
  • Ranking/chart/business computed once at settle, reusing
    rank_candidates / build_chart_data / explain_winner unchanged.

One correctness fix beyond the blueprint

The detached pattern opens a window where a DELETE can arrive before the worker
registers its CancelHandle — which would be misreported as a false 409. Fixed
by eagerly registering the handle in submit_run (the worker's setdefault
reuses 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, winner
    card, comparison charts, model-detail drawer (Sheet), cancel dialog
    (AlertDialog), + a .ts constants module.
  • champion.tsx: the CTA now submits → polls live progress → renders the
    winner card, ranking table, comparison charts, and per-model detail drawer;
    partial/all-failed/cancelled states render and failed candidates stay visible.
  • Additive 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_predict are no-ops in the
async worker. The legacy sync POST /run and the existing train-winner /
predict endpoints are untouched and unwired.

Validation

  • ruff check . + format --check
  • pytest -m "not integration"1781 passed (+28 Slice B: runner, schemas,
    service, async routes)
  • migration up/down + pytest -m integration13 passed (202 immediacy,
    poll→winner, failed-candidate-visible, no candidate left running after
    drain
    , 409, legacy-no-progress)
  • strict-mode policy ✅ · pyright app/features/model_selection ✅ 0 errors
  • frontend tsc ✅ · lint ✅ (1 pre-existing TanStack Table warning) · test
    298 passed

Known pre-existing / limitations

  • mypy app/ / pyright app/ report lightgbm/xgboost optional-dep import
    errors in forecasting/ + registry/ (CI installs the extras) — none in
    model_selection.
  • A process restart mid-run leaves a parent stuck running (no reconcile pass) —
    an accepted single-host tradeoff, not built here (per the PRP).
  • The cancel integration test accepts 200 or 409: fast baseline fits often
    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.

Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @w7-mgfcode, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 1, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e6e2d58d-b6b5-4006-bca3-689a77f61396

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/champion-selector-slice-b

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@w7-mgfcode w7-mgfcode merged commit 2168b61 into dev Jun 1, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant