Skip to content

feat(forecast,ui): MLZOO-D frontend, registry, and explainability polish (#256)#257

Merged
w7-mgfcode merged 4 commits into
devfrom
feat/mlzoo-d-frontend-registry-explainability
May 20, 2026
Merged

feat(forecast,ui): MLZOO-D frontend, registry, and explainability polish (#256)#257
w7-mgfcode merged 4 commits into
devfrom
feat/mlzoo-d-frontend-registry-explainability

Conversation

@w7-mgfcode
Copy link
Copy Markdown
Owner

Summary

Implements PRPs/PRP-31-mlzoo-d-frontend-registry-explainability.md (v3, READY verdict from prp-quality-agent, conf 9.5/10). Closes #256.

Surfaces Advanced ML Model Zoo capabilities in the React dashboard — runs explorer Family column, run-detail / run-compare feature-metadata cards, forecast-viz collapsible importance panel, backtest model-options extension, plus user-guide docs touch-up. No data-model change, no migration, no agent-surface change, no model-contract change.

  • 3 logical commits + the PRP file — backend / frontend / docs split for review
  • +2,457 net code lines (8 new, 13 modified) — see commit messages for the per-layer breakdown
  • 3 real defects caught mid-execution and regression-tested: HistGBR feature_importances_ gap (added FeatureImportanceUnavailableError → 422), SimpleImputer(keep_empty_features=False) drops empty cols (pad via imputer.statistics_ NaN inspection), and a registry ↔ forecasting circular import on alembic cold-boot (fixed via lazy imports following explainability/service.py:57 precedent)

Validation

All gates green locally:

  • ruff check . + ruff format --check .
  • mypy --strict app/ ✅ (276 source files, 0 issues)
  • pyright --strict app/ ✅ (0 errors, 68 pre-existing warnings)
  • pytest -m "not integration"1423 passed (+47 new)
  • pytest -m integration on touched slices ✅ 34 passed
  • pnpm tsc --noEmit ✅ · pnpm lint ✅ · vitest run119 passed (+10 new)
  • alembic upgrade head ✅ on fresh DB
  • API-level curl dogfood across 200 / 400 / 404 / 422 paths on both endpoints ✅

Test plan

  • Backend unit + integration green locally
  • Frontend type-check + lint + vitest green locally
  • Migrations apply cleanly on a fresh DB
  • Curl dogfood: 404 missing, 400 baseline, 422 missing-artifact, 200 prophet_like 14-feature happy path with signed coefs
  • Browser dogfood (PRP Task 21 scenarios 1-7) — deferred to follow-up session per HANDOFF; underlying components unit-tested + wire shape curl-verified
  • Required dev status checks (Lint & Format, Type Check, Test, Migration Check) green in CI

Notes for reviewer

  • Execution report: .agents/execution-reports/prp-31-mlzoo-d-frontend-registry-explainability.md documents divergences, defects caught mid-execution, and recommended PRP/process improvements.
  • One pre-existing test (registry/tests/test_routes.py::TestListRunsEndpoint::test_list_runs_empty) is order-dependent locally; will pass on a fresh CI DB.
  • Memory hooks that drove design decisions (cited inline in code):
    • [[scenario-run-id-vs-registry-run-id]] — load-bearing for the job-keyed sibling endpoint design (forecast.tsx only has job_id, not a registry UUID)

…rror (#256)

Surface the v0.2.16 advanced-model metadata (LightGBM / XGBoost / regression
/ prophet_like) via two new read-only forecasting endpoints — the registry-
keyed twin of PRP-28's /explain/runs/{run_id} and the job-keyed twin of
/explain/jobs/{job_id}. Adds the computed model_family field on
RunResponse, the FeatureImportanceUnavailableError + UnprocessableEntityError
exception classes, and matching unit + route tests.

Backend additions:
- app/features/forecasting/feature_metadata.py — model_family_for,
  importance_type_for, extract_feature_importance. Pure-function;
  lightgbm / xgboost never imported at module scope (optional extras).
  Detects sklearn's HistGradientBoostingRegressor.feature_importances_
  gap and raises FeatureImportanceUnavailableError → 422.
  Detects the SimpleImputer-drops-all-NaN-columns case for prophet_like
  and pads ridge.coef_ back to the canonical 14-column width with 0.0
  (regression caught during dogfood; days_since_launch on a product with
  no launch date triggers it).
- app/features/forecasting/schemas.py — ModelFamily str enum,
  FeatureImportanceItem (signed importance for linear_coef kind),
  FeatureMetadataResponse.
- app/features/forecasting/service.py — get_feature_metadata_for_run +
  for_job. The two cross-slice imports (RegistryService, JobService) are
  LAZY (inside the methods) to break the registry ↔ forecasting cycle
  the computed field would otherwise close.
- app/features/forecasting/routes.py — GET /forecasting/runs/{run_id}/
  feature-metadata + GET /forecasting/jobs/{job_id}/feature-metadata.
  Mirror PRP-28's exception-flow contract: ForecastLabError subclasses
  flow through to forecastlab_exception_handler, SQLAlchemyError
  becomes DatabaseError. Also fixes the /train docstring drift — now
  lists xgboost, regression, prophet_like alongside lightgbm.
- app/core/exceptions.py + problem_details.py — UnprocessableEntityError
  (status_code=422, code='UNPROCESSABLE_ENTITY') and matching ERROR_TYPES
  entry. Distinct from ValidationError (code='VALIDATION_ERROR') so
  consumers can disambiguate state-prevented operations from input
  failures via the RFC 7807 type URI.
- app/features/registry/schemas.py — model_family computed_field on
  RunResponse (no DB column, no migration).

Tests (37 new): test_feature_metadata.py (17, including the
imputer-drop regression), test_routes_feature_metadata.py (20, full
matrix for both endpoints — 200 / 400 / 404 / 422 with mocked
RegistryService / JobService / load_model_bundle), and 10 new
RunResponse model_family cases in test_schemas.py.

Closes the MLZOO sequence backend gap.
Surface the new /forecasting/{runs,jobs}/{id}/feature-metadata endpoints
and the computed model_family field on every ModelRun across the
explorer + visualize pages. One panel, two display modes (tree: positive
bars; linear_coef: signed bars with direction colour + icon).

Frontend additions:
- types/api.ts — ModelFamily ('baseline' | 'tree' | 'additive');
  ModelRun.model_family; FeatureImportanceItem (signed importance);
  FeatureMetadataResponse.
- hooks/use-feature-metadata.ts — useRunFeatureMetadata +
  useJobFeatureMetadata sibling hooks. Mirror useRunExplanation /
  useJobExplanation exactly (retry: false; query-key shape).
- components/common/model-family-badge.tsx + test — pure derivation,
  no hooks: baseline → secondary + Activity, tree → default + TreePine,
  additive → outline + LineChart. data-family attr + testid.
- components/explainability/feature-importance-panel.tsx + test —
  one card, branches on FeatureImportanceItem.kind. Linear coef rows
  render with sign-coloured bars + TrendingUp / TrendingDown icons.
  Verbatim correlation-vs-causation caveat in the card footer.
  Neutral muted message for ApiError 400 (baseline family) and 422
  (no artifact / missing extra / HistGBR-no-importance). Destructive
  ErrorDisplay for unexpected.

Page wiring (additive, in-place edits only):
- explorer/runs.tsx — new 'Family' column with ModelFamilyBadge,
  MODEL_TYPES allow-list extended to regression / lightgbm / xgboost /
  prophet_like, csvColumns + Model filter dropdown extended.
- explorer/run-detail.tsx — ModelFamilyBadge in profile header + row,
  'Feature Metadata' card listing the 14 canonical feature columns,
  FeatureImportancePanel — all gated on model_family !== 'baseline'
  with enabled:false on the TanStack Query so baseline runs never
  trigger a 400 burst.
- explorer/run-compare.tsx — Family row in the profile table, new
  collapsible 'Feature Importance (Run A vs Run B)' card. Side-by-side
  panels only when both runs share a non-baseline family; cross-family
  pairs render a muted explanatory message and DO NOT fetch (enabled:
  false on both hooks).
- visualize/forecast.tsx — CRITICAL: uses useJobFeatureMetadata
  (NOT useRunFeatureMetadata). trainJob.result.run_id is the FORECAST
  ARTIFACT KEY (uuid.uuid4().hex[:12], see forecasting/service.py:270),
  NOT a registry UUID; calling the run-keyed hook would 404. Inline
  CRITICAL comment + memory cross-link to scenario-run-id-vs-registry-
  run-id. Collapsible defaultOpen=false to preserve scan flow.
- visualize/backtest.tsx — MODEL_OPTIONS extended to all seven types
  (B.2's feature-aware backtest is what makes the four advanced
  families reachable from the UI).

Tests (10 new): model-family-badge.test.tsx (3 cases),
feature-importance-panel.test.tsx (7 cases). Both use afterEach
(cleanup) because vitest is not configured for auto-cleanup.
Copy link
Copy Markdown

@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, your pull request is larger than the review limit of 150000 diff characters

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 20, 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: 3ec2f992-77c0-4a3f-949b-93abaaa5725f

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/mlzoo-d-frontend-registry-explainability

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 c81c92c into dev May 20, 2026
8 checks passed
@w7-mgfcode w7-mgfcode deleted the feat/mlzoo-d-frontend-registry-explainability branch May 20, 2026 03:26
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