# Slice C — picker / lookups / favourites / field rules / computed

Paths: BE = /home/moonui3/moon-erp-be/Modules, FE = /home/moonui3/public_html/moon-erp/src/app. Picker = FE shared/components/clinical-picker/.

## 1. Verdict table

| # | prototype item (proto line) | implementation file:line | verdict | note |
|---|---|---|---|---|
| 1 | ONE catalog `clinical_lookups {lookup_key,label,group,display_order,specialty,is_active}` (587-592) | BE Clinic/database/migrations/2026_11_03_100006_create_clinical_lookups_tables.php:21-39 | ✅ as spec | Group = self-ref `parent_id` (category row → children); value_ar/value_en; company-scoped; soft-deletes. |
| 2 | Field definitions reference `{lookup_key, group}` (591, 1273) | BE …/2026_11_03_100005_add_engine_v2_columns_to_history_questions.php:38-39; HistoryQuestionResource.php:44 | ⚠ partial | `history_questions.lookup_key` only, no `group`. Real users: 279 gyn multi + 27 gyn single (DB). General shell fields have no definitions: complaint/exam hard-code the key in the card. |
| 3 | Multi-select = link rows, never CSV (588-590) | complaint: encounter_complaints + lookup_id (…280002:17-20); exam: encounter_findings (…280000:20-33); dx: encounter_diagnoses | ⚠ partial | Complaint/exam/dx are real link rows. Every multi_select history field (all 279 GYN chip fields, incl. categories `gyn_cat_<g>`) is a JSON code array in `answer_value`/`value_json` (HistoryService.php:411-418, 657-664). Not CSV, but not link rows either. |
| 4 | Per-doctor favourites FAVS[doctor][key] (644-651) | BE `clinical_lookup_favorites(company,user,lookup_key,lookup_id)` migration :41-55; LookupCatalogService.php:28-59,112-133; route api.php:476 | ✅ as spec | Real and persisted per user. The list endpoint sorts ★ first. Un-favourite does forceDelete. Dev DB has 0 favourites so far. |
| 5 | Favourites for lab/rad/drug | BE `clinic_favorites` (2026_07_08_000010), FavoriteController.php:27-33; FE services/clinic-favorite.service.ts | ✅ (2nd store) | A separate per-doctor store for lis_investigation / lis_package / rad_procedure / clinic_service / drug. There is no ICD/diagnosis type. |
| 6 | Inline «＋ إضافة جديد» writes to catalog with `lookups.manage` (778, 796-797) | BE api.php:475 `permission:clinic.history.manage`; StoreLookupRequest.php:84; LookupCatalogService.php:67-105; FE picker-box.ts:160 | 📝 deferred (known) | Real: it persists, is idempotent on value_ar and is permission-gated, both FE and BE. But the gate is the broad `clinic.history.manage`. On dev, roles `doctor` and `gyn_doctor` do NOT have it (only owner / طبيب), so most doctors never see the add row. |
| 7 | On card: ONLY selected chips + up to 4 ★ quick + «＋ اختيار / بحث» (780-784) | FE clinical-picker-box.component.ts:77-99,162-171 | ✅ as spec | Chip click removes it; quick-favs exclude categories and already-selected values; capped at 4. |
| 8 | Focused picker: search, ★ block on top, grouped list, category pills, multi/single, footer «المختار (n)», Done (772-779, 759-771) | FE clinical-picker-dialog.component.html:251-394; .ts:86-121 | ✅ as spec | Search matches ar + en. A ★ row also appears again in its group, as in the prototype. |
| 9 | Investigations = category grid by display_order, open ONE category (763-766) | dialog.html:307-327 (`grid` input) | ⚠ partial | Grid mode exists in the shared picker, but NO caller passes `[grid]=true`. Lab/rad use their own legacy pickers (rows 16-17). |
| 10 | Keyboard: Enter picks first hit, Esc closes (776) | dialog.ts:221-241; rows tabindex + Enter/Space (html:397-398) | ✅ as spec | No arrow-key navigation (the prototype had none either). |
| 11 | Mobile full-screen sheet (screenshot 14) | dialog.scss:87-89 (`max-width:640px` → 100vh, no radius) | ✅ as spec | Gyn option picker: p-dialog breakpoint 640px → 100vw (gyn-option-picker.component.ts:41). |
| 12 | Complaint uses picker, specialty-scoped, multi → link rows (741, 814-818) | FE visit-shell/cards/visit-complaint-card.component.ts:41-50,121-139; shell html:249-250 | ⚠ partial | The mechanics are real (lookup → clinic_complaints by name + lookup_id). **But dev company 1 has NO general complaint rows**: only 20 `complaint/gyn_ob` (DB). In a general visit the picker is empty and there is no seeder (see Bug 1). |
| 13 | Exam: ONE catalog `exam.finding`, regions = categories, a box per region + full picker behind the head button (743, 830-832) | FE visit-exam-card.component.ts:48-86,147-158; BE EncounterFindingService LOOKUP_KEY | ⚠ partial | The code is right. **Dev has 0 `exam.finding` rows and nothing seeds them**, so the card falls back to one flat empty box with no regions (see Bug 1). |
| 14 | History questions picker `history.question` (742) | FE encounter/history-panel (legacy chips from `history_answer_options`); HistoryQuestionResource.php:48-54 | ❌ missing | The general «التاريخ» card is the old panel: open chips per question, no catalog, no ★, no inline add. |
| 15 | Diagnosis: picker with ★, group pills, inline add, first = primary (744, 839-844; screenshot 06) | FE encounter/diagnosis-panel/diagnosis-panel.component.html:42-52 (`app-pick-multiselect`, minChars 2); ts:64-68 | ⚠ partial | Search-as-you-type ICD autocomplete with magic-add (`POST icd10`, perm `clinic.diagnosis.record`). No favourites, no group browse, no picker behind a button. Primary/secondary is set with buttons. |
| 16 | Lab orders: grid picker behind a button, ★, add-new (745, 846; screenshots 02/03) | FE encounter/orders/lab-orders.component.html:3-200; .ts:181-215,501-530 | ⚠ partial | ★ is real (clinic_favorites) and there is a search ⟷ grid toggle. But the whole picker + basket is spread on the card, not behind a button. Add-to-catalog only in standalone mode, with no FE perm check. |
| 17 | Rad orders picker (746) | FE encounter/orders/rad-orders.component.html:13-19; .ts:164-206 | ⚠ partial | Same as lab: inline catalog-grid-picker with real ★; createFn only in standalone mode. |
| 18 | Rx: drug + default sentence auto-filled and editable, Duplicate (747; screenshot 04) | FE encounter/prescription/prescription.component.ts:383-410,462-468,495-499,531-549,242 | ✅ as spec | Search behind a toggle; presets/sentences; duplicate; save typed sentence as preset; quick-add drug gated by perms. ★ favourites only in `products` pharmacy mode (:223-231): standalone_formulary has only most-used. |
| 19 | Gyn categories picker, noAdd, ★ (748-750; screenshot 10) | FE gyn/cards/gyn-category-picker.component.ts:52-56,95-160 + gyn/form/gyn-option-picker.component.ts | ✅ as spec | Feature-filtered groups; sexual group needs `gyn.sexual.view`; ★ persisted through clinical_lookups; no add (as in the prototype). Stored as answer `gyn_cat_<group>` (JSON). |
| 20 | Gyn module chips (PK_DYN/gchips) e.g. VTE (751; screenshot 11) | FE gyn/form/gyn-lookup-field.component.ts:29-45,74-84; gyn-module-form.component.html:42,74 | ✅ as spec | Uses the shared box/dialog with `specialty=gyn_ob`. Lists of ≤6 (multi) or ≤7 (single) render open; `gyn_vte_pre` etc. have lookup_key (DB). |
| 21 | Nutrition dialog investigations picker (screenshot 22) | FE nutrition/visit/nutrition-consult-board.component.html:654-693 (p-dialog → `app-clinic-lab-orders`) | 📝 deferred (S2b) | The classic board hosts the same lab panel in a dialog: ★ is real, but no category-grid-first view as in the screenshot. |
| 22 | Per-client per-field input Select⟷Text (INPUT_FIELDS 654, rule/inputType 734-735) | BE VisitLayoutController.php:42-46,60-61 stores `fields.*.input`; FE complaint-card.ts:101-104, exam-card.ts:152-158 | ⚠ partial | The renderer honours it for `chief_complaint` and `exam_finding_<regionDbId>` only. Diagnosis rule is ignored. **No UI ever writes `fields`**: the only `saveClient` call (visit-shell.component.ts:782) sends `layout` only. The prototype's settings rows (proto 1248) are not built. |
| 23 | Doctor may override field rules/elements when the client allows it (734, 736) | visit-layout.service.ts:42-44 (doctor level = layout only) | ❌ missing | Doctor override covers the layout only, never fields or elements. |
| 24 | ELEMENTS show/hide (653, elem 736) | complaint-card.ts:102,104 (`chief_complaint_note`, `complaint_quick`) | ⚠ partial | Only 2 of the 8 are honoured. hdr.insurance / hdr.lastVisit / hdr.doctor / complaint.hpi / lab.prev / rx.interact / dx.secondary: no code reads them, and there is no settings UI. |
| 25 | Required marker per client rule (711) | complaint-card.ts:103 | ⚠ partial | Only on the complaint text-only mode. There is no generic required-field rendering from `fields`. |
| 26 | Computed BMI read-only (714) | BE VitalsService.php:70,102,135 (client value unset, server computes); FE vitals-panel.component.html:67-78 | ✅ as spec | Shown read-only with an abnormal flag. |
| 27 | Computed WHR (715) | BE Nutrition/app/Services/Visit/ConsultBoardReader.php:94-95; FE nutrition-consult-board.html:157-160 | ✅ as spec | Read-only, with a risk pill. |
| 28 | Computed GA from LMP (716) | BE Clinic/app/Support/ComputedRegistry.php:150-195 (edd/gaDays/ga); Gyn/app/Support/GynComputed.php:58,135-152; FE gyn/engine/computed.ts:333 | ✅ as spec | Saved with source=derived (HistoryService recomputeDerived). |
| 29 | Computed NEWS2 (717) | ComputedRegistry.php:215-253 (per-parameter bands); FE hospital/nursing/vital-round.component.html:4 | ✅ (better) | Real RCP bands instead of the prototype toy. Shown on hospital rounds and as a computed history field. Per-parameter display is 📝 deferred (known). |
| 30 | Managed catalog admin (legacy investcats SHOW/EDIT/DELETE; «كتالوج مُدار») | BE api.php:474-476 (index/store/favorite only) | ❌ missing | No update / rename / deactivate / reorder / delete endpoint or screen, so a mistyped inline-add stays forever. |
| 31 | Favourites shared across sibling pickers live | clinical-lookup.service.ts:45-46,88-98 (`changed` signal + in-place cache patch) | ✅ as spec | Cache is per key+specialty (see Bug 4). |

## 2. Real bugs found (implemented but would misbehave)

1. **The general complaint and exam pickers are empty on a fresh or dev install: there is no seed.** On dev company 1 there are 0 rows of `complaint` with specialty NULL/general and 0 rows of `exam.finding` (SELECT on clinical_lookups). Only `complaint/gyn_ob` (20) comes from `gyn:install-pack`. The complaint card asks for `specialty='general'` (visit-shell.component.html:250 → LookupCatalogService.php:41-44), so it gets []. The exam card then drops to the "no regions" fallback (exam-card.ts:79-86). No seeder or migration in Modules/* inserts either key (grep). So on general visits the two flagship pick-don't-type cards show nothing to pick, and roles without `clinic.history.manage` (dev: `doctor`, `gyn_doctor`) cannot even add values.
2. **The exam Select⟷Text rule is keyed by a company-specific DB id** (`exam_finding_<parent row id>`, exam-card.ts:153,157). A rule cannot be shipped in a default or copied between installs, and it breaks if a region is re-created. The prototype used stable keys (`exam_general`, `exam_chest` …). `clinical_lookups.code` exists (D2a) and should be the key.
3. **BE inline-add does not validate `parent_id`** (StoreLookupRequest.php:95: `nullable|integer`). The id is not checked to belong to the same company or lookup_key, so a value can be filed under another key's or tenant's category row. Its `specialty` is also not checked against known types, and the FE never sends it (clinical-lookup.service.ts:26-32). So every inline-added GYN complaint becomes core (visible to all specialties).
4. **Cache drift across specialties.** add() and toggleFavorite() patch only the `key|specialty` cache entry they were called with (clinical-lookup.service.ts:79-81,92-94). A value starred or added under `gyn_ob` does not show as starred or present in the same key's `general` cache until reload. Minor.
5. **A failed list (403) re-fires HTTP on every `changed` bump.** On error, catchError deletes the cache and never sets `rows` (service :63-66). Each box's effect (picker-box.ts:174-183) then calls list() again on every add or ★ anywhere. This is a perf leak for users without `clinic.history.view`.
6. **Short GYN lists rendered open cannot be starred or searched by non-managers.** The «＋» that opens the dialog only shows when `canAdd` is true (gyn-lookup-field.component.ts:37,84), so a doctor without `clinic.history.manage` never reaches the ★ for lists of ≤6/≤7.

## 3. Top 5 gaps by owner impact

1. There is no seed catalog for general `complaint` and `exam.finding` (with regions), so the general visit's pick-don't-type cards are empty out of the box (Bug 1).
2. Inline add-new is gated on `clinic.history.manage`, which the stock `doctor` / `gyn_doctor` roles lack, and there is no catalog admin screen to fix or reorder or deactivate values. "Managed catalog" is half-done.
3. There is no client settings UI for field rules (Select⟷Text, visible, required) or for elements. The renderer honours only 2 element keys and 2 input rules, and the doctor-level override of fields is missing.
4. Diagnosis (no ★, no group browse) and History (legacy chips, no catalog) are not on the picker engine. Lab and rad keep their legacy pickers spread on the card instead of chips + a «اختيار» button.
5. GYN multi-selects (incl. categories) are stored as JSON code arrays in history answers, not link rows. That is better than CSV, but reports like "how many patients have X" still need JSON queries, which goes against spec guard (b).

**Decision: favourites and add-new are REAL, not cosmetic.** Favourites are persisted per doctor in `clinical_lookup_favorites` (lookups) and `clinic_favorites` (lab / rad / drug / service); the server sorts them first and they are scoped by company and user. Add-new is persisted, idempotent, and permission-checked on BE (`clinic.history.manage`) and FE. Their usefulness on general visits is limited by the empty seed catalog and by the permission most doctor roles lack.
