# Story 5.3: Confidence Scoring & Threshold Routing

## Story

**As a** system administrator,
**I want** the categorization engine to automatically approve high-confidence predictions and flag low-confidence ones for human review,
**So that** products above the confidence threshold flow directly to Akeneo without manual intervention.

## Status

done

## Acceptance Criteria

All ACs met.

## Tasks / Subtasks

- [x] **Task 1: `python/services/confidence_scorer.py`** — `compute_score(evidence_signals)`: weighted average of `signal_strength` values → [10.0, 100.0]; floor of 10 applied when no signals exist. `derive_level(score, threshold)`: `high` if score ≥ threshold, `medium` if ≥ 70, `low` otherwise.

- [x] **Task 2: `python/services/categorizer.py`** — calls `confidence_scorer.compute_score()` and `derive_level()`, writes `confidence_score`, `confidence_level`, `status='predicted'` to `predictions`. Updates `products.status = 'predicted'` in same DB transaction.

- [x] **Task 3: `CategorizationService::applyThresholdRouting(int $batchId, AkeneoService $akeneo)`** — reads `confidence_threshold` fresh from `system_settings`. Iterates all `predicted` predictions in batch:
  - Score ≥ threshold → `status = 'auto_approved'`, `products.status = 'auto_approved'`, calls `AkeneoService::submitCategoryUpdate()` if `akeneo_identifier` set. On Akeneo failure: reverts both to `needs_review`, logs error, writes `auto_approved_akeneo_failed` audit entry.
  - Score < threshold → `status = 'needs_review'`, `products.status = 'needs_review'`.
  - Each transition writes an `audit_log` entry (`auto_approved` or `flagged_for_review`) with `confidence_score`, `category_code`, `batch_id`.

- [x] **Task 4: `AuditLogService::write()`** — static helper that inserts one append-only record into `audit_log`.

- [x] **Task 5: `CategorizationService::getConfidenceDistribution(int $batchId)`** — returns `{total, high, medium, low, avg_score}` from `predictions` table for batch. Displayed in the batch show view post-completion.

- [x] **Task 6: Confidence badge CSS** — `.badge-confidence-high/medium/low` pill badges in `styles.css` (green/amber/red). Used in batch show distribution card and Epic 6 review queue.

## Dev Notes

### applyThresholdRouting caller

`applyThresholdRouting()` is designed to be called by an external trigger — e.g., a cron job or a PHP endpoint that checks `completed` batches — not automatically by `categorize.py`. This keeps the Python/PHP boundary clean: Python writes predictions, PHP routes them.

### File list

**New files:** `src/Services/AuditLogService.php`  
**Modified files:** `src/Services/CategorizationService.php` (applyThresholdRouting, getConfidenceDistribution), `python/services/confidence_scorer.py`, `python/services/categorizer.py`
