Skip to content

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

@w7-mgfcode

Description

@w7-mgfcode

Slice B of 3 (A → B → C) — Forecast Champion Selector

Converts the synchronous champion comparison into a DB-backed long-running operation and builds the results-visualization UI. Builds on merged Slice A (#356/#359) and the model_selection backend (#353). Mirrors the proven async LRO pattern in app/features/batch/ (slice-locally — no cross-slice import).

PRP: PRPs/forecast-champion-selector-slice-b-async-comparison-results.md

Backend

  • POST /model-selection/runs202 immediate, fire-and-forget (detached asyncio.create_task, returns status=running + Location/Retry-After + monitor_url/cancel_url before any backtest finishes).
  • DELETE /model-selection/{selection_id} — cooperative cancel + drain (200 settled / 404 / 409 terminal / 504 drain timeout).
  • GET /model-selection/{selection_id} — additive live progress (GROUP BY children) + candidate_progress; terminal output byte-compatible with the sync /run.
  • New model_selection_candidate child table + additive model_selection_run columns (started_at, count columns) + cancelled status (enum + CheckConstraint) — one Alembic migration.
  • Slice-local runner.py (TaskGroup + Semaphore + CancelHandle registry, mirror of batch/runner.py); new Settings.model_selection_global_max_parallel (4) + model_selection_cancel_drain_timeout_seconds (30).
  • Ranking/chart/business computed once at settle, reusing rank_candidates/build_chart_data/explain_winner unchanged.

Frontend (extends Slice A page)

  • useSubmitSelectionRun / useSelectionRun (poll) / useCancelSelectionRun hooks.
  • components/champion-selector/results/: progress panel, ranking table, winner card, comparison charts, model-detail drawer (Sheet), cancel dialog (AlertDialog).
  • Wire the previously-disabled "Run comparison" CTA → submit → live progress → ranking/winner/charts; partial/all-failed/cancelled states; failed candidates stay visible.
  • Additive types in types/api.ts (SubmitRunResponse, SelectionProgress, CandidateProgress, 'cancelled').

Explicitly NOT in Slice B (Slice C)

  • No train-selected, no predict decision, no promotion/alias, no safety stock, no forecast-decision panel. auto_train_winner/auto_predict are no-ops in the async worker. Legacy sync POST /run kept unchanged (no frontend caller).

Acceptance

  • Backend Level-1..4 (incl. migration up/down + integration: no candidate left running after drain) + frontend tsc/lint/test green.

Slice C (decision layer) follows as a separate issue.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions