# م3 — ملف المريض والتسجيل (Patient File & Registration) — CANONICAL SPEC

Compiled from KB by read-only scout, 2026-07-03. Every item below is cited to its source
file + section/line so it can be re-verified. **Canonical anchor for scope/sequencing:**
`clinic-execution-phases.md` (status: active, "الخطة الملزمة" per project memory, last
updated 2026-07-03) — its م3 block (lines 70–74) is the binding definition of what ships
in this phase. `clinic-profiles-fields.md` is the binding **design** for the fields +
field-management model. `gyna-reference.md` is the binding **source material** (legacy
OBGY fields/behaviors being ported). `clinic-principles.md` supplies the owner principles
that govern/constrain the design. `clinic-dynamic-fields.md` is the mini-design for the
**clinical fields engine**, which is mostly م6-scoped (see §2.3 boundary note).

---

## 1. Patient-file fields — the mature gyna fields being adopted

Source: `clinic-profiles-fields.md` §"الجزء 1 — جرد الحقول" (lines 22–85), cross-referenced
with `gyna-reference.md` item 8 ("إرجونوميكس", line 38) and its Top-10 list item 8 (line 48).

### 1.1 What legacy OBGY has (the gene pool)
`clinic-profiles-fields.md` lines 26–46 — table `patients` in obgy: **≈41 capture fields**,
patient = the wife, husband's data lives as columns on the **same row** ("الملف = الزوجان"):
- Wife demographics (10): name, title(lookup), DOB, age(derived), **national ID → auto-derives
  DOB** via `checknationalid()`, blood type, education, job, marital status, gender.
- Contact (4): address, phone, mobile, phone_qid.
- **Husband dimension (10 fields)**: name, title, age, DOB, national ID, blood type, job,
  education, notes, habits.
- Marriage (4): duration, computed marriage-from-date, sons count, daughters count.
- Obstetric base (6): pno/ab/ectopic/vmodel/svd/cs counters.
- Clinical flags (5): important-note, risk, risk-type, special-notes, report-note.
- **File/record number (1)**: `statusno`, generated `MAX(statusno)+1` (flagged as race-prone —
  Moon should use a sequence instead, line 79).
- Doctor link (1): `doctorid`.

**Registration behaviors** (lines 41–46, cited again in `gyna-reference.md` line 38, 48):
- **Quick-register vs full file (two-depth single form)**: `index()` writes an instant row
  (name/national-id/file-number only, national ID auto-fills DOB); `addfinish()` saves the
  full payload and sets `done=1`.
- **Resumable draft**: `R::findOne('patients','done=0 and userid=?')` — a live per-user draft
  auto-resumes (patients.php:73).
- **Live duplicate check (5 axes)** — see §4 below.
- Instant field-level ajax saves (`updateajax`, `saveage`, `caldatefromage`).

### 1.2 What Moon Clinic has today (gap baseline)
`clinic-profiles-fields.md` lines 53–58: `LabPatient` (`lab_patients`, ~30 DB columns, form
captures ~16): mrn, name/name_ar/name_en, DOB, gender, phone(+country code), email, blood_group
(**column survives but input was removed from UI, ticket #1626**), address(_ar), national_id+type
+nationality (NPHIES: national-id/iqama/passport/visa/border) + passport_country, insurance_info
json, insurance_contract_id, medical_history, external_lab_id, tax_status, partner_id, portal
tokens, is_active. Quick-add in booking (`clinic/booking/create-visit.component.ts`) = **4 fields
only**: name, name_ar, phone, gender.
**Completely absent** (line 58): any spouse/couple dimension, marital status, job, education,
NID→DOB derivation, draft, soft duplicate check (today only hard DB-unique on national_id/phone).

### 1.3 Gap verdict / adoption decisions
`clinic-profiles-fields.md` §"جدول الفجوات" (lines 65–84) — resolved per-row:
- **Husband dimension (10 fields)** → ✅ adopt, but as a **dynamic-field package** (`scope=patient`,
  `category=profile`) inside an "OBGYN pack" — **NOT core columns**. This is the explicit
  translation of the owner's "fits each client" requirement (line 69).
- Marriage duration + sons/daughters → ✅ same OBGYN dynamic pack (line 70).
- Obstetric counters (pno/ab/…) → ✅ but belongs to the **م3-clinical engine's** category=history/vitals,
  not the patient file itself (line 71) — i.e. out of patient-file scope, in scope for م6's
  clinical fields engine.
- **National-ID→DOB derivation** → ✅ **high value / effort S**, FE parser keyed by
  `national_id_type` + country (Egyptian 14-digit; Saudi ID carries no DOB → keep manual
  age↔DOB sync for that case) (line 72).
- Marital status / job / education / title → ⚖️ dynamic fields (single_select with options),
  not core columns; marital status defaults ON inside the OBGYN pack (line 73).
- **Blood type** → ✅ first direct customer of the visibility mechanism: comes back as a core
  field, default-OFF, instead of being code-deleted (line 74).
- important-note/risk/special-notes → ✅ simple optional core field "important note/alert"
  shown in the patient header (author leans core-simple over dynamic) (line 75).
- **Quick-register vs full** → ✅ solved by the visibility mechanism itself: a **"Quick
  registration" preset** = hide everything non-required (line 76).
- **Resumable draft (done=0)** → ⚖️ **deferred** — value drops with a short customizable form;
  compensated by local form-state persistence in a v2 (line 77).
- **Live duplicate check (name/file/national/phone)** → ✅ **soft warning on-blur** (similar name
  / existing phone → "open the existing file"), effort S, prevents duplicate files (line 78).
- Auto file number → ✅ already exists as `mrn`, recommendation: sequence not MAX+1 (line 79).

**Count summary** (line 84): patient — legacy ≈41 capture fields vs Moon ≈16-on-form (≈30 DB
columns); the core gap = spouse dimension/socials (deliberate: KSA client doesn't want it,
OBGYN client wants all of it = **the owner's own argument for configurability**).

### 1.4 Owner principle governing these fields
`clinic-profiles-fields.md` line 13 (frontmatter callout) and line 17 (owner quote, verbatim):
> «ملف الدكتور وملف المريض محتاج نشوف بياناتهم من الجينا، ويكون ليه إدارة لإخفاء الـ inputs
> بتاعته هنا أو هنا علشان يتناسب مع كل عميل.»
Recommendation locked in: **hybrid** — core registry with per-company visibility/require
settings + additions via the dynamic field engine (scope=patient, category=profile) + specialty
presets (line 13, elaborated §2 below).

---

## 2. Field-management model for م3

### 2.1 The three existing precedents being reused (not inventing new machinery)
`clinic-profiles-fields.md` §"السابقتان الموجودتان" (lines 90–93):
1. **`DocConfigService` + `DOC_CONFIG_REGISTRY`** pattern — FE static registry per document
   (`DocFieldDef{key,labelKey,area,locked,canRequire,defaultVisible,defaultRequired}`) + overrides
   in one JSON company setting (`core.document_settings`) + a central settings tab — proven on
   12 documents, owner-approved ("change once not 12×").
2. **م3 dynamic field engine** itself (`clinic-dynamic-fields.md`): generalization of
   `history_questions` with `is_active`, `scope(patient|encounter)`, `category`, presets seeded
   via `updateOrCreate(company_id,code)`, unified renderer.
3. **`SettingsService` + dynamic clinic-settings screen** (`SettingDefinitionSeeder` Clinic
   block + `clinic-settings.component.ts` renders any new definition with zero FE code).

### 2.2 The decided design (hybrid) — `clinic-profiles-fields.md` §"المواصفة" (lines 103–141)
1. **Core field registry per form** (FE, same pattern as `DocFieldDef`):
   `clinic/config/profile-form-registry.ts` → `PATIENT_FORM_REGISTRY` + `DOCTOR_FORM_REGISTRY`.
   Each field: `{key, labelAr, labelEn, locked?, canRequire?, defaultVisible, defaultRequired, section}`.
   - **Locked (never hideable)** — patient: `name`(or name_ar), `gender` (drives LIS reference
     ranges), `phone` (booking/duplicate key). Doctor: `name_ar/name_en`, `code`.
   - **Toggleable examples** — patient: national_id+type+nationality (KSA=ON+required,
     Egypt=OFF — **this is the exact mapping of the owner's ask**), passport_country, email,
     blood_group, address(_ar), DOB/age, medical_history, insurance, important-note. Doctor:
     license_number, specialty_id, email, department_id, price_list_id, hospital_name, address,
     signature, show_on_report, commission block (one toggle for the whole block), grade_id,
     employee_id (lines 108).
   - Forms (full LIS patient form + booking quick-add + clinic doctor form **after
     completion**) render off the registry: `@if (profileConfig.fieldVisible('patient', key))`
     — same wiring style as sales documents (line 109).

2. **Storage — two per-company settings via `SettingsService`** (lines 111–113):
   `clinic.patient_form_fields` + `clinic.doctor_form_fields` (value_type=json, seeded rows in
   `SettingDefinitionSeeder`, `is_visible=false` like `core.document_settings`). Shape mirrors
   `DocOverride`: `{fields: {"<key>": {visible?, required?}}}`; absent = registry default. One FE
   service `ProfileFormConfigService` (either a slim clone of `DocConfigService`, or generalize
   `DocConfigService` itself with a parametrized storage key — author leans slim-now/unify-later).

3. **Extra/custom fields = the dynamic field engine** (line 114): a definition with
   `scope=patient` + **`category=profile`** (new enum value proposed for the engine's `category`)
   auto-renders as an "Extra fields" section under the registration form via the same
   `dynamic-field-renderer`, stored in the unified values table (patient-scoped, `encounter_id`
   NULL). **Zero new machinery** — just a category filter. This is where the whole husband/spouse
   package lives.

4. **Where it's managed — both, mirroring the documents pattern** (lines 116–119):
   - **Central (source of truth)**: settings screen (م9) tab **"نماذج الملفات" (File Forms)**
     renders both registries (Visible + Required toggles per field, locked greyed-out) + a CRUD
     table of `category=profile` dynamic fields (on top of the existing `HistoryQuestionController`).
   - A **"⚙️ Customize fields" button** on the registration form itself opens the same panel as
     a dialog (same gear/central-tab merge pattern as documents; mind lesson D3: read a live
     signal, not a stale copy).

5. **Specialty presets** (line 120): JSON packs (pattern `packs/obgy.json` planned in م3) carry
   **both** halves together: (a) core-field visibility overrides, (b) dynamic `category=profile`
   field definitions — applied via an "Apply pack" button using the same
   `updateOrCreate(company_id,code)` seeder. Examples: **OBGYN** (spouse+marriage+marital-status
   ON, national-id optional) · **KSA-NPHIES** (national-id/nationality ON+required, tax) ·
   **Quick registration** (bare minimum only) · **Lab/B2B** (as-is today).

6. **Validation golden rule — hidden≠required** (lines 122–126):
   `StoreLabPatientRequest`/`StoreLabDoctorRequest` read the same setting server-side (via
   `SettingsService`): `hidden → prohibited/ignored` · `visible+required(setting) → required` ·
   `locked → always required`. (Today `national_id/nationality` are hard-required — they become
   setting-conditional; the unique index already accepts NULL, so no break.) FE mirrors this:
   the screen disables the Required toggle when a field is hidden (lesson D1 from documents,
   already applied — no hidden+required field can exist). Hiding is **display-only** — DB
   columns and internal consumers (tax_status, partner, portal) are unaffected; booking
   quick-add stays a subset of what's visible. Dynamic-field validation already lives in
   `HistoryService::saveAnswers` (type/options/is_required).

7. **Stations & effort table** (lines 128–141) — explicitly scopes work to م1/م3/م9. For م3
   specifically: registry FE for both forms + `ProfileFormConfigService` = S; wiring the LIS
   patient form + booking quick-add to the registry = M; BE settings-pair definitions + conditional
   validation in both Store requests = S; **`category=profile` addition to the م3 engine + the
   "extra fields" section in the form = S** (line 137, explicitly gated "يشترط بناء م3" — i.e.
   assumes the base engine exists). Presets and the central "نماذج الملفات" tab are **م9**, not م3.
   **Overall ≈M (4–6 days)**; the "visibility" slice (registry+settings+validation) is fully
   independent and shippable before the dynamic engine; the "extra fields/spouse pack" slice rides
   on the dynamic engine once it exists (line 141).

### 2.3 م3 scope boundary vs م6 clinical-fields engine — IMPORTANT, resolve carefully
Two KB documents disagree on where the **general** clinical dynamic-fields engine (vitals +
danger ranges + unit + patient/encounter scope unification) lands, and this scout treats
`clinic-execution-phases.md` as authoritative since it is the binding, most-recently-updated
(2026-07-03) sequencing document:

- **`clinic-execution-phases.md`** (binding) — م3 (lines 70–74) mentions the field engine ONLY
  for `category=profile` (patient-form extra fields, i.e. the spouse/OBGYN package). The **full**
  clinical fields engine — "محرك الحقول الإكلينيكية (تمديد history_questions...)، بسكوبَي
  patient/encounter" with danger-range alerts, vitals migration, unit, computed fields — is
  explicitly placed in **م6 "شاشة الطبيب بذاكرة"** (line 90), whose test scenario is "عرّف
  مؤشر جديد من الإعدادات → ظهر في الشيت وتنبّه عند الخطر" (line 91). م6 depends on م5, not
  primarily م3 (line 92).
- **`clinic-dynamic-fields.md`** (design doc, same day, status=design — i.e. a design sketch,
  not the sequencing authority) has an internal effort table (§8, lines 142–151) that assigns
  "schema expansion (definitions+values) + migration" to **"م3 (core)"**, danger-evaluation and
  the unified renderer also to "م3", and only the full **admin/settings CRUD screen** to م9.

**Reconciliation for this spec**: treat `clinic-execution-phases.md` as canonical for
phase-boundaries (it is the doc the project memory calls "الخطة الملزمة"). Practically this
means: م3 is scoped to **reusing** the existing `history_questions`-based engine mechanics
(category filter + renderer) to serve **patient-profile** extra fields only; the **schema
extension** that `clinic-dynamic-fields.md` describes (unit/scope/category/danger_min-max/
is_computed columns, vitals migration, patient|encounter dual-scope storage) is the م6 payload.
If م3 work ends up needing a few of those new columns early (e.g. `category` enum gaining a
`profile` value, `scope` already partly modeled), that is the minimal delta explicitly called
"م3 (delta)" in `clinic-profiles-fields.md` line 137 — not the full engine. **This
boundary should be explicitly confirmed with the owner/Fable before م3 execution starts**, since
it is the one place the KB is internally inconsistent (flagged as open in §6 below).

**Also note (naming caution, not to be confused with the above):** `clinic-test-scenarios.md`
(dated 2026-07-02, not in this scout's required reading list but discovered while
cross-checking) uses a **different, apparently older/stale phase-numbering scheme** where "م3"
= the doctor's **consultation/exam screen** (history/vitals/complaint), not "ملف المريض
والتسجيل". Do not conflate its "م3" with the canonical م3 defined in
`clinic-execution-phases.md` (patient file & registration) — they are different phases under
the old vs new numbering. Treat `clinic-execution-phases.md`'s numbering as authoritative.

---

## 3. Doctors-file completion scope (م3)

Source: `clinic-execution-phases.md` line 72 (م3 "اللي هيطلع منها"): "**إتمام شاشة الأطباء
(CRUD كامل — الدين المسجل) + ملف الدكتور بنفس نظام الإخفاء**." Cross-referenced with the م1
progress-log decision (line 26): *"قرار مالك: شاشة الأطباء (CRUD كامل: تعديل شامل+حذف/تعطيل)
مؤجّلة رسميًا لـ م3 زي الخطة؛ م1 = Add + Edit-identity فقط."* — this is the "recorded debt"
referred to in the task: م1 shipped **Add + identity-only Edit** for doctors (deliberately,
per owner decision), with **full CRUD (comprehensive edit + delete/disable)** formally deferred
to م3.

Detail on the current gap, from `clinic-profiles-fields.md` §"الدكتور" (line 60–63, 82):
- **BE (`lab_doctors`, ~30 columns)** is already rich: name/name_ar/name_en, code, free-text
  `specialization(_ar)` **+** structured `specialty_id`→`lab_doctor_specialties` (#1627),
  license_number, phone(required+unique/company), email, address(_ar), hospital_name(_ar),
  doctor_group, price_list_id, signature, is_internal, show_on_report, department_id, commission
  fields (type/value/account/payable/is_active+rules), `employee_id`→employees (doctor→employee→
  user identity chain, previously dormant), `grade_id`→`clinic_doctor_grades`
  (resident/specialist/consultant/professor). **No syndicate/union column.**
- **LIS doctor screen** is complete (18 controls incl. structured specialty_id + commissions).
- **Clinic doctor screen is half-built**: add uses only 7 fields (and falls back to the free-text
  `specialization` instead of `specialty_id`), edit = an "identity" dialog (grade_id+employee_id
  only), **no full edit, no delete, no active/inactive toggle, no department field**, and
  `DoctorResource` for the clinic actively **hides** phone/email/license from the list (line 63).
- **Verdict** (line 82): Moon's doctor data model is already richer than legacy gyna — the gap
  is purely **exposing it in the clinic screen**, not adding columns.

**م3 doctor scope = two things together**:
1. Complete the clinic doctor screen to **full CRUD**: comprehensive edit (all BE fields, not
   just identity), delete/soft-disable, department field, switch free-text `specialization` to
   the structured `specialty_id`, stop hiding phone/email/license in the resource.
2. Apply the **same hide/show field-management system** built for the patient file (§2 above)
   to the doctor form — i.e. `DOCTOR_FORM_REGISTRY` + `clinic.doctor_form_fields` setting +
   locked fields (`name_ar/name_en`, `code`) + toggleable fields (license_number, specialty_id,
   email, department_id, price_list_id, hospital_name, address, signature, show_on_report,
   commission block as one toggle, grade_id, employee_id) — per `clinic-profiles-fields.md`
   line 108 (doctor half of the registry spec).

---

## 4. Live duplicate check + typeahead unification

**Duplicate check** — `clinic-profiles-fields.md` line 44 (legacy behavior) + line 78 (adoption
decision) + line 73 (test scenario, see §5):
- Legacy OBGY has **5-axis live duplicate check**: `checkingwifename` (name),
  `checkingwifestatusno` (file number), `checkingnationalwifename` (wife's national ID),
  `checkingnationalhusband` (husband's national ID), `checkmobile` (phone OR mobile) — all
  on-blur ajax checks (line 44).
- Moon today only has **hard DB-unique** on national_id/phone (line 58, 78) — no soft/live
  warning, no name-similarity check.
- **Decision**: adopt as a **soft on-blur warning** ("similar name exists / phone already
  registered → open the existing file"), effort **S**, explicit goal = "prevents duplicate
  files" (line 78). This is a UX warning layered on top of (not replacing) the existing hard
  uniqueness constraints.
- The task item asks specifically for name/phone/national — those three axes are exactly what's
  named in the м3 owner test (§5) and in the gap table (line 78); the file-number and
  husband-national-id axes from legacy are not explicitly re-stated as म3 scope in
  `clinic-execution-phases.md`'s м3 line (only "فحص تكرار حي (اسم/تليفون/قومي)" — line 72), so
  file-number/husband-ID duplicate checks are implicitly folded in only if/when the husband
  package (§1.3) is active for that company.

**Typeahead unification** — `gyna-reference.md` item 8 (line 38) + Top-10 item 8 (line 48):
- Legacy: multi-axis search (spouse/address/age/national-id).
- Moon today: **"بحث 2-char بثلاث نسخ typeahead منفصلة"** — i.e. three separate, inconsistent
  typeahead implementations, each triggering on a 2-character search (line 38 diagnosis).
- Adopted as Top-10 item 8: "إرجونوميكس تسجيل (NID→ميلاد + فحص تكرار حي + **توحيد الـ
  typeaheads**)" — effort S/M (line 48).
- Restated as م3 deliverable in `clinic-execution-phases.md` line 72: "**توحيد الـ typeaheads
  الثلاثة**" — i.e. the three separate patient-search typeahead components/implementations get
  merged into one shared component/behavior. No further mechanical detail (debounce ms, min
  chars, etc.) is specified in these KB files — only the “unify the three” directive.

---

## 5. م3 owner acceptance test ("تست المالك") — exact scenario

Source: `clinic-execution-phases.md` line 73, verbatim (Arabic), plus dependency/reference
lines 74:

> **تست المالك:** سجّل مريض بالرقم القومي → الميلاد اتملى لوحده · جرب تسجل نفس التليفون →
> تحذير تكرار · أخفِ حقول الزوج من الإعدادات → اختفت من الفورم.

Translated, three concrete checks the owner will personally run:
1. **Register a patient by national ID → date of birth auto-fills itself** (validates the
   NID→DOB derivation, §1.3).
2. **Try to register the same phone number again → get a duplicate warning** (validates the
   live duplicate check, §4).
3. **Hide the spouse/husband fields from the settings screen → they disappear from the
   registration form** (validates the field-visibility mechanism end-to-end, §2).

Dependencies (line 74): **م2** (the settings system) — م3 cannot be tested/built meaningfully
before م2's settings infrastructure exists, since the field-visibility mechanism is stored via
`SettingsService`. Reference doc: `[[clinic-profiles-fields]]`.

Estimated duration for the whole phase: **٢–٣ أيام** (2–3 days) per the phase header (line 70).

---

## 6. Owner decisions — resolved vs still open, for م3

### Resolved (locked, do not re-litigate)
- **Hybrid model** (core registry + settings JSON, extras via dynamic engine) is the chosen
  design — three alternatives were explicitly rejected in `clinic-profiles-fields.md` lines
  97–101 (pure-settings-JSON rejected: can't add new fields; full-EAV rejected: core fields
  have real FKs/uniqueness/NPHIES/tax consumers that can't become value-rows).
- **Husband/spouse dimension = dynamic fields, not core columns** (line 69) — deliberate, this
  IS the owner's own configurability argument.
- **Blood type returns as a core field, default-OFF** rather than staying deleted (line 74) —
  the reference case ("first direct customer of the visibility mechanism").
- **Resumable draft (done=0 pattern) is explicitly deferred**, not in م3 scope (line 77) — "قيمته
  تقل مع فورم قصير قابل للتخصيص" — compensated later by local form-state persistence (v2, not
  م3).
- **Doctors screen full CRUD is formally м3-scoped** (not م1) — owner decision recorded in the
  م1 progress-log line (line 26): "مؤجّلة رسميًا لـ م3 زي الخطة".
- **Locked (un-hideable) fields are fixed**: patient name/gender/phone; doctor name/code — these
  are load-bearing for LIS reference ranges, booking, and duplicate-detection respectively (line
  107).
- **Validation golden rule** (hidden→prohibited, visible+required→required, locked→always
  required) is settled (lines 122–126).

### Open / needs explicit owner or Fable confirmation before/at م3 execution
- **Schema-expansion boundary (م3 vs м6)** — flagged in §2.3: `clinic-dynamic-fields.md`'s own
  effort table places the engine's schema migration (unit/scope/category/danger ranges/
  is_computed) at "م3 (core)", while `clinic-execution-phases.md` (the newer, binding
  sequencing doc) places the full clinical engine at м6 and only names `category=profile` for
  м3. This scout could not find an explicit reconciliation statement in the KB — **this is the
  single most important open question to resolve before scoping م3's dynamic-field delta work**,
  since it determines whether м3 needs any new DB columns on `history_questions`/
  `patient_history_answers` or can reuse the table exactly as-is with only a new `category`
  enum value.
- **Which duplicate-check axes exactly ship in м3**: `clinic-execution-phases.md` line 72 names
  name/phone/national-id (3 axes); legacy had 5 (also file-number and husband-national-id). Not
  explicitly resolved whether file-number/husband-ID duplicate checks are м3 scope or deferred
  with the husband package's own conditional activation.
- **`ProfileFormConfigService` implementation choice**: `clinic-profiles-fields.md` line 112
  explicitly leaves this open — "أرجّح المصغّر الآن وتوحيدًا لاحقًا" (author's own lean, not a
  ruling) between a slim clone of `DocConfigService` vs generalizing `DocConfigService` itself
  with a parametrized storage key.
- **Important-note/risk/special-notes field type**: line 75 — author leans "core simple field"
  but explicitly flags it as a judgment call ("أرجّح core بسيط... أو ديناميكي") rather than a
  closed decision.
- Per the standing project rule (`clinic-execution-phases.md` line 19, owner 2026-07-03 amendment):
  م3 does **not** need a standalone owner pause/approval gate before starting — phases now run
  back-to-back with only internal SDD review gates; the owner batch-tests multiple phases
  together at the end. So "open decisions" above should be escalated to **Fable** during
  execution per the binding operating procedure, not held for an owner stop.
