# E1e — answers store v2, private fields, computed on save, v2 question API

Read `_COMMON.md` first. AF2 (commit 7551cca82) just rewrote parts of EncounterHistoryController and PatientProfileFieldController (clinical_type isolation via forClinicalType) — read them fresh and keep that behaviour. Spec: `../design-data-layers.md` §3.1 (definition columns) and §3.2 (facts store). Migration range `2026_11_03_130000`–`130099`.

## State you build on (already merged)
- `history_questions` has: clinical_type, carry_forward, stale_after_days, fill_role, in_intake, storage, show_if(json), module_key, repeat_group, lookup_key, is_private. `HistoryInputType` has date/textarea/computed/score/table/repeat/figo/popq/media.
- `Modules\Clinic\Support\ShowIfEvaluator` + `ShowIfContext`; `Modules\Clinic\Support\ComputedRegistry` (bmi, bsa, age, edd, ga_days, ga, map).
- `HistoryService` already: show_if-aware required enforcement for profile fields, v2 type validation/encoding.
- Permission `clinic.fields.private` exists (seeded).

## Do
1. Migration on `patient_history_answers`: `+ repeat_index` unsignedSmallInteger default 0; replace the unique `(patient_id, history_question_id, scope_key)` (find its real name/columns in the migrations — it may include encounter/scope_key) with one that also includes `repeat_index`; `+ value_json` json nullable; `+ source` string(12) default 'manual' (manual|prefill|portal|import|derived). Guarded; MySQL-safe index swap.
2. `PatientHistoryAnswer` model: fillable/casts; add Spatie `LogsActivity` (logOnly answer_value, value_num, value_json; logOnlyDirty; dontSubmitEmptyLogs) — check how other models use LogsActivity in this repo and mirror it. Model alias `ClinicalFieldValue extends PatientHistoryAnswer` (new file, empty body + docblock).
3. `HistoryService` save paths (encounter + patient): items may carry `repeat_index` (int ≥0) for questions with a `repeat_group`; structured/multi values also stored in `value_json` (keep answer_value text for back-compat); `source` from item (default manual).
   - **Private fields:** saving an answer to an `is_private` question requires `clinic.fields.private` → 403 otherwise.
   - **Computed:** after saving, for every active question of input_type computed/score with a `computed_key` the registry `has()`, compute from the same scope's answers (map code => value, include the just-saved values) and upsert its answer with source='derived' (skip if inputs missing → leave untouched). Keep this in a small private method.
4. Readers hide private answers/questions from users without `clinic.fields.private`: `EncounterHistoryController`, `PatientProfileFieldController`, `HistoryQuestionController::index` (questions with is_private are omitted unless permitted). Read those files fully first — AF2 just edited the first two (column-based clinical_type isolation); keep its changes.
5. `HistoryQuestionResource` exposes all v2 columns; `StoreHistoryQuestionRequest` / `UpdateHistoryQuestionRequest` accept them: clinical_type ∈ ClinicalType::values() except 'general' (null for core), carry_forward ∈ none|prefill, fill_role ∈ reception|nurse|assistant|doctor|patient, storage ∈ answer|observation|alert|condition|surgery|family|home_rx, show_if = array validated by a new rule class `ValidShowIf` (walks the grammar: leaf needs `k` + exactly one op of eq|in|has|nothas|gt|truthy, or cat/mod string, or any/all arrays; max depth 6) — reject malformed with 422, module_key/repeat_group/lookup_key strings ≤ 48 (`[a-z0-9_.]+`), is_private bool, stale_after_days 1–3650.
6. Answer read responses include `repeat_index`, `value_json`, `source`.

## Tests
`Modules/Clinic/tests/Feature/E1eAnswersTest.php`: two repeat rows of the same question persist side by side; value_json stored for a repeat/table answer; private answer save without permission → 403, with → ok; private question hidden from index without permission; bmi computed answer appears after saving weight+height (source derived); a malformed show_if on question create → 422, a valid nested one → 201; activity log row written on answer update. Run also `HistoryEngineTest`, `HistoryMemoryTest`, `HistoryScopeGateTest`, `PlatformE1HistoryEngineTest`, `VisitHistoryTest` and keep them green.

## Out of scope
VitalsService / observations (D1), attachments, prescriptions, FE, routes (no new routes needed — report if you think one is).
