# Story 5.4: Explainable Evidence Generation & Audit Trail

## Story

**As a** data steward,
**I want** to see exactly what data drove each AI category prediction,
**So that** I can make an informed decision about whether to trust, adjust, or reject it.

## Status

done

## Acceptance Criteria

All ACs met.

## Tasks / Subtasks

- [x] **Task 1: `python/services/evidence_builder.py`** — `build_evidence(product, attributes)`: extracts signals from part-number patterns (`SOURCE_PART_NUMBER_PATTERN`), manufacturer category mapping (`SOURCE_MANUFACTURER_CATEGORY`), and enrichment attributes (`SOURCE_ENRICHMENT_ATTRIBUTE`). `pick_category()`: selects highest-scoring category from signals. `fallback_signal()`: returns `SOURCE_CATEGORY_RULE / Fallback` with `weight=0.001` and `evidence_value="No specific evidence found…"`.

- [x] **Task 2: `python/services/categorizer.py`** — calls `evidence_builder.build_evidence()`. If empty → inserts `fallback_signal()`. Writes all evidence rows to `evidence` table with correct `weight` per signal.

- [x] **Task 3: `AuditLogService::write()`** — static append-only writer. Used by `CategorizationService::applyThresholdRouting()` for all prediction status transitions. Table has no UPDATE or DELETE paths in application code (NFR17).

- [x] **Task 4: `EvidenceService::getEvidenceForPrediction(int $predictionId)`** — returns `{evidence[], attributes[], total_weight}`. Evidence ordered by `weight DESC`. Each row includes `weight_pct` (percentage of total weight). Attributes (from `product_attributes`) included for Epic 6's missing-data panel.

- [x] **Task 5: `PredictionController::evidenceApi(int $id)`** — `GET /api/predictions/{id}/evidence`. 401 if unauthenticated, 404 if prediction not found. Returns standard `{success, data}` envelope.

## Dev Notes

### evidence_builder source types

| `source_type`            | Trigger                             | `weight` |
|--------------------------|-------------------------------------|----------|
| `part_number_pattern`    | SKU/MPN regex match                 | 0.40     |
| `manufacturer_category`  | Keyword in manufacturer_category    | 0.35     |
| `enrichment_attribute`   | One record per product_attribute    | 0.15 each|
| `category_rule` (fallback)| No usable signals                  | 0.001    |

Weights are illustrative starting values — they should be tuned once real categorization data is available.

### File list

**New files:** `src/Services/EvidenceService.php`, `src/Controllers/PredictionController.php`  
**Modified files:** `python/services/evidence_builder.py`, `python/services/categorizer.py`, `public/index.php` (evidence API route)
