Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 14 additions & 14 deletions PRPs/INITIAL/INITIAL-batch-parallel-execution.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# INITIAL-batch-parallel-execution.md — Parallel Execution Controls for the Portfolio Forecasting Batch Runner

**Status:** proposed
**Depends on:** `batch-runner-mvp` (parent `batch_job` + child `batch_job_item` + `POST /batch/forecasting` — **NEW**, not yet scaffolded)
**Depends on:** PRP-33 batch-runner MVP (merged in #281).
**Source feature doc:** `docs/optional-features/06-portfolio-forecasting-batch-runner.md` § Full Version → "Parallel execution controls"
**Scope:** one Full-Version enhancement on top of the (unbuilt) batch-runner MVP — DB delta, backend service shape, API surface, frontend control, tests.

Expand Down Expand Up @@ -76,18 +76,18 @@ Rationale (2-3 sentences as required by the prompt): `asyncio.Semaphore` is the

## Data Model Delta

The MVP INITIAL will create the table; this INITIAL specifies the column it MUST include so the parallel-execution layer never needs a second migration to add it. Both the column and the MVP migration land together — this is not a follow-up migration (matches the source doc's "Full Version" framing).
PRP-33 already shipped `max_parallel`, `running_items`, and `cancelled_items` on `batch_job` as **forward-compat columns** (see `app/features/batch/models.py:136-139` and migration `alembic/versions/c1d2e3f40512_create_batch_tables.py:49-51`). The MVP runner ignores `max_parallel` (it executes children serially) and never writes the two live counters; this PRP **activates** all three columns. No new migration is required.

**`batch_job` columns this feature touches (column-level deltas only, MVP owns the rest of the schema):**
**`batch_job` columns this feature activates (already in the schema):**

| Column | Type | Default | Notes |
|--------|------|---------|-------|
| `max_parallel` | `Integer NOT NULL` | `4` (server-side default) | Per-batch concurrency cap. Bounded by `Settings.batch_global_max_parallel` at create-time validation. **NEW** — added by the MVP migration on the explicit instruction of this INITIAL. |
| `running_items` | `Integer NOT NULL DEFAULT 0` | `0` | Live count, incremented when a child task enters the semaphore-acquired body, decremented on exit. Lets the UI show "X of Y in flight" without a `COUNT(*) WHERE status='running'` poll. **NEW** — added by the MVP migration. |
| `cancelled_items` | `Integer NOT NULL DEFAULT 0` | `0` | Live count of items the operator cancelled. Mirrors `failed_items` / `completed_items` from the source doc § Data Model. **NEW** — MVP must include it (cancellation is a first-class lifecycle state in this plan). |
| (existing MVP) `status` | `String(20)` | `'pending'` | This INITIAL adds two transitions to the MVP state machine — see § API Delta. |
| `max_parallel` | `Integer NOT NULL` | `4` (server-side default) | Per-batch concurrency cap. Bounded by `Settings.batch_global_max_parallel` at create-time validation. **Shipped by PRP-33** — this PRP wires it into the runner. |
| `running_items` | `Integer NOT NULL DEFAULT 0` | `0` | Live count, incremented when a child task enters the semaphore-acquired body, decremented on exit. Lets the UI show "X of Y in flight" without a `COUNT(*) WHERE status='running'` poll. **Shipped by PRP-33** — this PRP starts writing to it. |
| `cancelled_items` | `Integer NOT NULL DEFAULT 0` | `0` | Live count of items the operator cancelled. Mirrors `failed_items` / `completed_items` from the source doc § Data Model. **Shipped by PRP-33** — this PRP starts writing to it once `DELETE /batch/{id}` lands. |
| (existing MVP) `status` | `String(20)` | `'pending'` | This PRP adds two transitions to the MVP state machine — see § API Delta. |

**Migration policy:** per `.claude/rules/security-patterns.md` ("Migrations are forward-only after merge") and `docs/_base/RULES.md`, the migration that ships the MVP table MUST already include these three columns. If the MVP migration lands without them, this INITIAL becomes infeasible without adding a forward-only migration; the MVP author MUST coordinate.
**Migration policy:** per `.claude/rules/security-patterns.md` ("Migrations are forward-only after merge") and `docs/_base/RULES.md`, the three columns landed forward-only in the MVP migration. This PRP adds no new migration — it only adds behaviour against the existing schema.

**No schema changes to `batch_job_item`** beyond what the MVP defines. Child cancellation is a status transition, not a schema change.

Expand All @@ -101,7 +101,7 @@ All requests/responses follow `.claude/rules/security-patterns.md` § "Pydantic

```python
# app/features/batch/schemas.py (NEW — created by MVP)
class BatchCreateRequest(BaseModel):
class BatchSubmitRequest(BaseModel):
model_config = ConfigDict(strict=True)

# ... MVP fields: operation, scope, model_configs ...
Expand All @@ -122,7 +122,7 @@ A request with `max_parallel > settings.batch_global_max_parallel` is **accepted
### Response schema delta

```python
class BatchResponse(BaseModel):
class BatchSubmitResponse(BaseModel):
# ... MVP fields: batch_id, operation, status, total_items, completed_items, failed_items, ... ...

max_parallel: int # requested value, echoed
Expand Down Expand Up @@ -155,7 +155,7 @@ running → cancelled (operator cancel DURING execution, chi

### `DELETE /batch/{batch_id}` — cancellation contract

- 200: `BatchResponse` with parent `status='cancelled'` and per-child statuses settled.
- 200: `BatchSubmitResponse` with parent `status='cancelled'` and per-child statuses settled.
- 404: unknown batch_id.
- 409: parent already in a terminal state (`completed`, `failed`, `partial`, `cancelled`) — nothing to cancel.

Expand All @@ -177,8 +177,8 @@ NEW slice: `app/features/batch/` per `.claude/rules/product-vision.md` § "Verti

```
app/features/batch/
├── models.py # batch_job, batch_job_item (NEW — owned by MVP)
├── schemas.py # BatchCreateRequest / BatchResponse (NEW — owned by MVP)
├── models.py # batch_job, batch_job_item (shipped by PRP-33)
├── schemas.py # BatchSubmitRequest / BatchSubmitResponse (shipped by PRP-33)
├── service.py # BatchRunnerService (NEW — this INITIAL fills in the runner)
├── runner.py # NEW — semaphore + TaskGroup + registry of active batches
├── routes.py # POST /batch/forecasting, GET /batch/{id}, DELETE /batch/{id} (NEW — MVP)
Expand Down Expand Up @@ -354,7 +354,7 @@ Per `.claude/rules/ui-design.md` and `.claude/rules/shadcn-ui.md`: drive UI work

### Type-safety gate

The `src/types/api.ts` augmentation for `BatchResponse.max_parallel` / `effective_max_parallel` / `running_items` / `cancelled_items` MUST keep `pnpm tsc --noEmit` green (per `.claude/rules/test-requirements.md`).
The `src/types/api.ts` augmentation for `BatchSubmitResponse.max_parallel` / `effective_max_parallel` / `running_items` / `cancelled_items` MUST keep `pnpm tsc --noEmit` green (per `.claude/rules/test-requirements.md`).

---

Expand Down