Skip to content

feat(batch): implement batch-runner MVP per PRP-33 #280

@w7-mgfcode

Description

@w7-mgfcode

Goal

Execute PRP-33 — build the app/features/batch/ vertical slice that orchestrates portfolio forecasting batches.

Spec: PRPs/PRP-33-batch-runner-mvp.md (1177 lines, landed in #279). Read end-to-end before starting — every column, JSONB key, index predicate, and CHECK constraint is pinned.

Scope (10 dependency-ordered tasks per PRP-33)

  1. Pre-add batch to .claude/rules/commit-format.md (local prerequisite — .claude/ is gitignored).
  2. Alembic migration: batch_job + batch_job_item tables, enums, CHECK constraints, partial picker index with predicate exactly WHERE (status = 'pending').
  3. SQLAlchemy models (app/features/batch/models.py) + status-transition table.
  4. Pydantic v2 schemas with ConfigDict(strict=True) + Field(strict=False) on date fields (per SECURITY.md § "Pydantic v2 strict mode").
  5. BatchServicesubmit, _expand_scope (lazy-imports AnalyticsService for top_revenue, DimensionsService for region/category), _pick_next (with FOR UPDATE SKIP LOCKED), _execute_item (lazy-imports JobService), _shape_metrics (pinned 5-key JSONB; sample_size derived inside the slice from fold_metrics per PRP § "Why not 10"), _settle, get, list_items.
  6. FastAPI router wired into app/main.py between jobs_router and ingest_router.
  7. Config: Settings.batch_max_scope_expansion = 1000 + .env.example entry.
  8. Unit tests (no DB): models, schemas (JSON-path strict-mode regression), service (all 5 BatchScope.kinds, lifecycle structlog events with request_id, picker SKIP LOCKED, status-settlement matrix, pinned JSONB shape).
  9. Integration tests (real Postgres): submit happy path, partial failure, scope-cap 422, sort allow-list, partial-index predicate assertion.
  10. Frontend stub: types, hooks, placeholder frontend/src/pages/visualize/batch.tsx (no slider, no cancel button, no retry, no heatmap — per PRP narrowing).

Success criteria (from PRP § "What → Success Criteria")

  • alembic upgrade head on fresh Postgres creates both tables + every CHECK + the partial picker index with predicate exactly WHERE (status = 'pending').
  • 3-pair manual backtest scope settles completed with completed_items=3; every item's metrics JSONB carries exactly {wape, smape, mae, bias, sample_size} — no extras, no missing.
  • grep -rn "for_update" app/features/batch/service.py returns at least one line with skip_locked=True.
  • Settings.agent_require_approval gains zero entries (MVP exposes zero mutating agent tools).
  • app/features/jobs/, app/features/forecasting/, tests/test_e2e_demo.py unmodified.
  • All validation gates green: ruff check, ruff format --check, mypy app/, pyright app/, pytest -v -m "not integration", pytest -v -m integration, frontend pnpm tsc --noEmit && pnpm lint && pnpm test --run.

Off-limits (PRP non-regression boundary)

  • app/features/jobs/ — no edits.
  • app/features/forecasting/ — no edits.
  • tests/test_e2e_demo.py — no edits.

References

Branch + commit conventions

  • Branch: feat/prp-33-batch-runner-mvp off dev.
  • Commits: feat(batch): <description> (#<this-issue>) after Task 1 adds batch to the gitignored scope allow-list locally.
  • Single PR into dev; maintainer merges.

Refs #277.

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