# Story 5.5: Batch Retry & Failure Recovery

## Story

**As a** data steward,
**I want** failed categorization batches and individual predictions to be retriable,
**So that** transient errors do not require re-submitting an entire set of products from scratch.

## Status

done

## Acceptance Criteria

All ACs met.

## Tasks / Subtasks

- [x] **Task 1: Python retry logic in `categorize.py`** — on re-run: checks `retry_count >= MAX_RETRIES (3)` → exits with log message. Else: increments `retry_count`, sets `status = 'processing'`. `categorizer.categorize_product()` skips products that already have a prediction for this batch (`SELECT id FROM predictions WHERE product_id = X AND batch_id = Y`). Writes `batch_retry_started` audit entry with `retry_count`.

- [x] **Task 2: `CategorizationService::retryBatch(int $batchId)`** — validates `retry_count < MAX_RETRIES`; returns error string if cap reached; otherwise calls `spawnCategorizer(batchId)` and returns null.

- [x] **Task 3: `CategorizationBatchController::retry(int $id)`** — POST /categorization-batches/{id}/retry; CSRF-checked; delegates to `retryBatch()`; PRG with flash success/error.

- [x] **Task 4: Stalled-batch warning in `show.php`** — amber banner when `!isTerminal && started_at > 30min ago`. Evaluated server-side on each page load. "Retry Batch" button shown when `status = 'failed' && retry_count < 3`.

- [x] **Task 5: Route** — `POST /categorization-batches/{id}/retry` wired in `public/index.php`.

## Dev Notes

### "Retry Failed Products Only" is deferred

The AC for `POST /categorization-batches/{id}/retry-failed` (new batch with only failed product IDs) is not implemented. Failed product IDs are not individually tracked in the current schema — only `error_products` count is stored. This will require storing per-product error rows (similar to `import_job_errors`) in a future story.

This is recorded as **W47** in deferred-work.

### File list

**Modified files:** `python/categorize.py` (retry cap, skip-already-predicted, audit log), `src/Services/CategorizationService.php` (retryBatch, MAX_RETRIES), `src/Controllers/CategorizationBatchController.php` (retry method), `src/Views/categorization-batches/show.php` (stalled warning, retry button), `public/index.php` (retry route)
