+
+
+
+ Champion Selector
+
+
+ Configure a store, product, time period, horizon and candidate models,
+ and check whether the pair has enough history to model. Running the
+ comparison arrives in a later update.
+
+
+
+ {/* Selection */}
+
+
+ 1 · Pick a store & product
+
+ Search by code/SKU or name. The availability check runs automatically
+ once a valid pair and horizon are chosen.
+
+
+
+
+
+ Store
+
+
+
+ Product
+
+
+
+ Time period
+
+
+
+
+ Forecast horizon (days)
+
+
+ setForecastHorizon(Number(event.target.value) || 0)
+ }
+ />
+
+
+
+
+
+ {/* Availability */}
+
+
+ 2 · Data availability
+
+ Whether this pair has enough observed history for a reliable
+ comparison, plus the recommended split.
+
+
+
+
+
+
+
+ {/* Candidate models */}
+
+
+ 3 · Candidate models
+
+ Pick the models to compare (up to 10). The default five are
+ pre-selected; opt-in extras are flagged.
+
+
+
+ {catalogQuery.isError ? (
+ catalogQuery.refetch()}
+ />
+ ) : (
+
+ )}
+
+
+
+ {/* Backtest settings */}
+
+
+ 4 · Backtest settings
+
+ The ranking metric and cross-validation split. Start with the
+ recommended split or fine-tune under Advanced.
+
+
+
+
+
+
+
+ {/* Run CTA (disabled until Slice B) */}
+
+
+
+ {formReady
+ ? `Ready to compare ${selectedModels.length} model${
+ selectedModels.length === 1 ? '' : 's'
+ }. ${RUN_COMPARISON_PENDING}`
+ : 'Pick a store, product, time period, horizon and at least one model to continue.'}
+
+
+
+
+
+ {/* Dev-only assurance that a valid request is assembled (not sent). */}
+ {runRequest && (
+
+ {JSON.stringify(runRequest)}
+
+ )}
+
+ )
+}
diff --git a/frontend/src/types/api.ts b/frontend/src/types/api.ts
index df2289f4..d6e0584f 100644
--- a/frontend/src/types/api.ts
+++ b/frontend/src/types/api.ts
@@ -1188,3 +1188,161 @@ export interface ForecastExplanation {
as_of_date: string // ISO date
generated_at: string // ISO datetime
}
+
+// =============================================================================
+// Model Selection (Champion Selector) — backend slice app/features/model_selection
+// =============================================================================
+//
+// The FULL workflow contract is declared here so Slices B/C add BEHAVIOR, not
+// type definitions. Slice A CONSUMES only `ModelCatalogResponse`,
+// `PairAvailability`, and `SplitConfig` (read-only). Everything tagged
+// DECLARED-FOR-LATER is wired by Slice B (async run + results) and Slice C
+// (train / predict / business summary / override / promotion).
+
+export type ModelSelectionStatus =
+ | 'pending'
+ | 'running'
+ | 'completed'
+ | 'partial'
+ | 'failed'
+export type RankingMetric = 'wape' | 'smape' | 'mae' | 'bias'
+export type AvailabilityStatus = 'ready' | 'limited' | 'unusable'
+// `ConfidenceLevel` ('high' | 'medium' | 'low') is reused from the
+// Explainability section above — the backend uses the same enum.
+
+// Backtest split config — mirrors `app/features/backtesting/schemas.py`
+// `SplitConfig` EXACTLY (bounds enforced client-side so the assembled run
+// request is always valid for Slice B).
+export type SplitStrategy = 'expanding' | 'sliding'
+export interface SplitConfig {
+ strategy: SplitStrategy // def 'expanding'
+ n_splits: number // 2..20, def 5
+ min_train_size: number // >= 7, def 30
+ gap: number // 0..30, def 0
+ horizon: number // 1..90, def 14; must be > gap; kept === forecast_horizon
+}
+
+// --- CONSUMED in Slice A ---------------------------------------------------
+
+export interface CandidateModelInfo {
+ model_type: string
+ label: string
+ family: ModelFamily
+ feature_aware: boolean
+ /** lightgbm/xgboost — opt-in extra may be absent at runtime. */
+ requires_extra: boolean
+ default_params: Record