# Proposal P-1 — ERP-Maximalist / Thin-HIS
## "HIS as an Orchestration Spine: maximum ERP reuse + a metadata-driven clinical forms engine"

Submitted to the architecture review board, 2026-06-11.
Stance: build the **smallest defensible HIS**. Every administrative capability rides an existing,
audited Moon ERP engine; clinical documentation is **content, not schema**, served by one generic
forms/EAV engine seeded with OBGY's ~190 production vocabularies. Hard-coded clinical tables are
admitted only where metadata demonstrably breaks (and this proposal names those places honestly).

All binding decisions are honored as-is: HIS is ONE module `Modules/HIS` (D2); dependency
direction HIS → LIS/Accounting/HRM forward, LIS NEVER imports HIS, events backward only
(`LabResultReleased` / `LabRequestCompleted` / `CriticalResultDetected`); LIS production behavior
untouchable (D5); `lab_patients` is the MPI, B2B rows stay (D1/D1b); D3 `CreateLabRequest`
contract; D4 Encounter/Folio design.

---

## 1. Thesis

The HMS feasibility scorecard already says it: **financial backbone 9/10, cashier "ready",
insurance+NPHIES "ready", pharmacy store "ready", patient master ~80%** — while the gaps
(appointments 1/10, folio 0–1/10) are thin coordination entities, not engines. Meanwhile the
single biggest cost driver in every competing proposal is the **~85-table clinical schema** from
the OBGY blueprint and the 19 specialty screens it implies.

This proposal removes that cost driver. Observation from the legacy evidence: OBGY itself ran its
most-used clinical history capture for a decade on a **metadata engine**
(`presenthistoryquestions` / `presenthistoryanswers` + runtime `gynaph`) and kept **61% of its
schema (~190 tables) as pure lookup vocabulary**. The legacy system is, accidentally, a
proof-of-concept that women's-health documentation is mostly *configurable content*. We
industrialize that insight instead of transcribing 312 tables into 85 hard ones.

What is actually net-new hard schema in this proposal: **~17 tables in `Modules/HIS`** plus **3 in
a content pack**. Everything else is reuse:

| Hospital capability | Engine reused (no new build) |
|---|---|
| Patient master / MPI | `lab_patients` via `LabPatient::internal()` (D1, W2-1 item 7) |
| Patient ledger / AR | `business_partners` + `AccBpExt.ar_account_id` auto-created by `Modules\Accounting\Listeners\CreatePartnerAccounts` |
| Folio → GL posting | `Modules\Accounting\Actions\CreateJournalEntry` via a Core `ChargePostingService` extracted from `Modules/LIS/app/Actions/PostLabInvoice.php` (the spec's own deferred "unified charge-posting service") |
| Deposits / advances | Accounting receipt vouchers + FIFO allocation (D4 approved pattern) |
| Cashier, treasuries, shifts, Tamara/Tabby | LIS cashier stack, hoisted per the approved Generalization Register (HIS P1 item) |
| Insurance contracts, price lists | LIS named price lists + coverage, generalized to polymorphic billables (largest register item, already scheduled) |
| Claims / eligibility / preauth | `Modules/NPHIES` (`NphiesEligibilityService`, `NphiesClaimService::submit`, `nphies_transactions`) with `FhirPatientBuilder` input generalized from `LabPatient` to the shared patient |
| Pharmacy store | Inventory warehouses (`warehouse` per pharmacy) + `StockService::increaseStock/decreaseStock/getIssueCost` + Core Product master; POS for OTC |
| Lab | LIS **as-is**: orders via `Modules/LIS/app/Actions/CreateLabRequest` (D3) with `lab_requests.encounter_id`; results back via existing events |
| Hormones / semen analysis | **LIS catalog entries**, not HIS tables — LIS already owns reference ranges, validation workflow, machine interfacing; WHO semen flags are LIS reference ranges |
| Staff, rosters, payroll | HRM (`employees.user_id` chain; shifts already carry `is_night_shift`, `nursing_extra_minutes`; `PayrollAccountingService`) |
| Doctor identity | `lab_doctors.employee_id` (W1-2) → `employees` → `users` |
| Equipment maintenance | CMMS work orders |
| Numbering, settings, attachments, audit, tenancy, branches | `SequenceService`, `SettingsService` (`his.*` keys), Core `Attachment`/`HasAttachments`, `BaseModel` (TenantAware + Auditable + SoftDeletes + HasArabicContent), `DataScope` (W1-1) |
| Permissions | spatie + `PermissionDependencyRegistry::register('his', ...)` (W1-4) |
| Approvals | extend `ApprovalModule` enum with `HIS` |
| Patient portal | LIS portal-token pattern (`portal_link_token`, `/p/:token`) |

---

## 2. Module layout (exact nwidart modules and key tables)

### 2.1 `Modules/HIS` — the only new platform module (one module per D2; internal split by folders)

**Encounters/** (D4 design, columns per `md/08-data-spine.md`):
- `his_encounters` — `encounter_number` (SequenceService), `patient_id → lab_patients` (internal-only writes), `type` (opd|ipd|er|daycase), `status`, `attending_doctor_id → lab_doctors`, `parent_encounter_id`, `queue_position`, `branch_id`, `started_at/ended_at`
- `his_folios` — `encounter_id`, `payer_type` (self_pay|insurance|b2b_account), `insurance_contract_id`, decimal(12,3) totals
- `his_folio_charges` — morph `source_type/source_id` with **UNIQUE(source_type, source_id)** (a clinical act bills exactly once), `service_code` (SBS, per `lab_investigations.sbscs_code` pattern), `unit_cost`/`cost_center_id` for COGS JEs
- `his_deposits` — thin wrapper rows referencing Accounting receipt vouchers; FIFO allocation against folio invoices

**Scheduling/** (the 1/10 gap; domain model lifted from OBGY `visits`/`visit_periods`/`vacations` design, not code):
- `his_appointments` — booking half of legacy `visits`; `source` (reception|portal|mobile|referral)
- `his_visit_periods` — capacity slots (`max_no` enforced in a DB transaction)
- `his_clinic_closures` — replaces never-wired `vacations`

**Forms/** (the clinical documentation engine — the heart of this proposal):
- `his_form_definitions` — `code`, `title`/`title_ar`, `specialty_key` (license gate), `schema` JSON (field defs: type, lookup_domain, validation, repeat-group, computed expressions), `print_template_id`
- `his_form_versions` — immutable published versions; a response always points at the version it was captured with
- `his_form_responses` — `form_version_id`, `encounter_id → his_encounters`, `episode_id` nullable, `patient_id`, `subject` enum (patient|spouse — OBGY's `forhusband` discriminator generalized), `values` JSON, status workflow (draft|signed|amended)
- `his_form_response_index` — typed projection for reporting: `(response_id, field_key, value_numeric, value_text, value_date, value_lookup_id)`, maintained on save — this is the honest answer to "EAV can't be queried"
- `his_lookups` — `(domain, parent_id, code, title_ar, title_en, sort, is_active)` — the single table replacing OBGY's ~190 lookup tables; content seeded **from production export** (risk R7)

**Registries/** (hard tables where metadata legitimately breaks — see §6):
- `his_episodes` — generic EpisodeOfCare header (`type`: pregnancy|infertility_file|chronic_program, `started_at`, `status`, key dates JSON); a pregnancy is an episode, not a bespoke table
- `his_observations` — typed clinical time-series: `(patient_id, episode_id?, encounter_id?, code, value_numeric, unit, body_site, observed_at)` — handles ANC growth curves, IVF follicle measurements, vitals trending as **rows**, indexable and chartable
- `his_patient_spouses` — 1:1 specialty companion record (legacy `husdandname…` columns); the MPI stays clean
- `his_prescriptions` / `his_prescription_items` — orders are workflow objects, not documentation: `product_id → Core products`, dose metadata, `subject` enum; dispense fires `PrescriptionDispensed` → Inventory issue

**Actions/** (house pattern): `OpenEncounter`, `PostFolioCharge`, `CloseFolio`, `GenerateFolioInvoices`, `PostHisInvoice`/`PostHisPayment` (thin wrappers over Core `ChargePostingService`), `DispensePrescription`.

### 2.2 `Modules/ObgyPack` — a license-keyed **content pack**, not a clinical platform

- **Zero controllers for sheets, near-zero migrations.** Contents: seeders publishing ~19 OBGY
  form definitions (ANC visit, gyna sheet, infertility file sections, endoscopy report, ultrasound
  templates with diagram-annotation fields), `his_lookups` content for the ~190 production
  vocabularies, observation-code catalog (follicle, endometrium, BHCG, GA…), print templates,
  `PermissionDependencyRegistry::register('obgy', …)`, and the ETL artisan commands (P0–P7
  playbook from `obgy-erp-analysis.md` §95).
- The **only hard tables** (metadata genuinely breaks — §6): `obgy_ivf_cycles` (stage state
  machine, one-active-per-patient partial unique), `obgy_embryos` (cryopreservation inventory has
  legal/chain-of-custody weight; cannot be JSON), `obgy_lis_test_map` (276 legacy `invests` →
  LIS catalog ids).
- Andrology/hormones ship as **LIS investigation seeders** (reference ranges = WHO flags), not tables.

### 2.3 Core hoists (no new module; all already named in the approved Generalization Register)

- `Modules/Core/app/Services/ChargePostingService.php` — extraction of `PostLabInvoice`/`PostLabPayment`
  parameterized by settings prefix (`lis.*` → `his.*`); still calls `CreateJournalEntry` with
  `source_type='his_invoice'`; COGS JE by `cost_center_id` preserved; LIS call sites unchanged.
- Payment methods / treasuries / cashier shifts hoist with LIS compatibility bridges.
- Price lists + insurance coverage generalized to polymorphic billables.
- `FhirPatientBuilder` input widened from `Modules\LIS\Models\LabPatient` to the shared patient model
  (same table, promoted namespace — D1).

### 2.4 Angular FE organization

- `features/his/` standalone components; `core/services/his-*.service.ts` (`providedIn:'root'`,
  `listAll()` forkJoin pagination, `X-Authorization`); routes under `/core/his/*` with parent
  `data.permissions=['his.']`; optional fullscreen `/clinic/*` layout cloning `LisLayoutComponent`.
- **One dynamic renderer instead of 19 sheet screens**: `his-form-renderer` + a field-widget
  library (lookup select fed by `his_lookups`, numeric-with-range, date/Hijri, repeating group,
  diagram annotator over Core attachments, computed fields e.g. GA/BMI). OBGY's screens become
  JSON, reviewed by clinicians, not Angular code.
- Two bespoke widgets are admitted (see §6): an **observation grid** (spreadsheet-like IVF
  monitoring / ANC flow-sheet over `his_observations`) and an **episode timeline**.
- Queues reuse the server-driven worklist contract (`lis-worklist.service.ts` shape → `his-queue.service.ts`);
  booking reuses the `request-wizard-v2` 4-step pattern; clinical printing clones the configurable
  `lis-html-report.service.ts` engine driven by form definitions; patient portal reuses `/p/:token`.
- `features/obgy-pack/` holds only the IVF cycle screen and ETL/admin views; everything else is content.
- Translations: `HIS` + `OBGY` namespaces in `en.json`/`ar.json`; module activation via `moduleGuard`
  + the Phase-0 W2-2 server-side gating once enforced (tier licensing requirement).

---

## 3. Dependency graph and rules

```
            ┌────────────────────────── events only (backward) ──────────────┐
            ▼                                                                 │
Modules/ObgyPack ──▶ Modules/HIS ──▶ Modules/LIS (CreateLabRequest, internal())
                          │  ├─────▶ Modules/Accounting (CreateJournalEntry via Core ChargePostingService)
                          │  ├─────▶ Modules/HRM (employees, shifts)
                          │  ├─────▶ Modules/Inventory (StockService)
                          │  ├─────▶ Modules/NPHIES (eligibility/preauth/claims)
                          │  └─────▶ Modules/Core (SequenceService, SettingsService, Attachment, DataScope, PermissionDependencyRegistry)
```

Rules (extending the settled D2 golden rule one level up):
1. HIS imports LIS/Accounting/HRM/Inventory/NPHIES/Core **forward** (actions/services); **LIS never imports HIS** (binding); LIS→HIS information flows only via existing events.
2. **HIS never imports ObgyPack**; ObgyPack imports HIS. This keeps the Clinic tier sellable without women's-health content — the exact rule that keeps LIS sellable standalone, applied again.
3. No module reads `lab_patients` except through the promoted shared model's named scopes (`internal()` / `forLab($labId)`); no blanket global scope (spec constraint #2).
4. All GL postings go through `CreateJournalEntry` only, with module `source_type`/`entry_type`; no module writes `journal_entries` directly; folio absorbs lab invoices **by reference only** (D4 anti-double-posting).
5. ObgyPack contributes content via seeders + registries (`PermissionDependencyRegistry`, form definitions, lookups) — never via Core/HIS code edits.
6. Specialty hard tables are allowed only with a board-approved "metadata-break justification" (this proposal pre-approves exactly three: `obgy_ivf_cycles`, `obgy_embryos`, `obgy_lis_test_map`).
7. D5 stands: zero added queries on LIS hot paths; BE+FE ship together; before/after timing gate on worklist + reception endpoints.

---

## 4. Event & FK contracts

**Existing (consumed, never modified):**
- `Modules/LIS/app/Events/LabResultReleased.php`, `LabRequestCompleted.php`, `CriticalResultDetected.php` → HIS listeners attach results/alerts to the encounter (dispatched today at `LabResultController.php:516,1058`, `LabWorkflowService.php:75,120`).
- `BusinessPartnerCreated` → `Modules\Accounting\Listeners\CreatePartnerAccounts` (patient AR accounts come free via `lab_patients.partner_id`).

**New (Modules/HIS/app/Events):**
- `EncounterOpened(encounter_id, patient_id, type)` / `EncounterClosed(encounter_id)`
- `FolioChargePosted(folio_id, source_type, source_id, total)` — the universal charge-capture hook for any clinical module (and for radiology/OR later, same D3 template)
- `FolioClosed(folio_id)` → payer-split invoice generation (self_pay / insurance shares; preauth ref from `nphies_preauths`)
- `PrescriptionDispensed(prescription_id, encounter_id)` → Inventory `StockService::decreaseStock(reference_type='his_prescription')` + folio charge
- `FormResponseSubmitted(form_response_id, form_code, encounter_id)` — generic reaction hook (e.g., ObgyPack listener advances an IVF cycle stage when the retrieval form is signed)
- `AppointmentBooked` / `AppointmentCheckedIn(appointment_id → encounter created)`

**FK contracts:**
- `lab_requests.encounter_id` → `his_encounters.id` (column live and inert since 2026_06_10_160000; FK added at HIS kickoff per W1-3 docblock)
- `his_encounters.patient_id` → `lab_patients.id` (internal rows only); `his_encounters.attending_doctor_id` → `lab_doctors.id` → `employees` via `lab_doctors.employee_id` (W1-2) → `users`
- `his_folio_charges` UNIQUE(`source_type`,`source_id`); `his_form_responses.form_version_id` → immutable version; `his_prescription_items.product_id` → Core `products`
- Invoices carry the `nphies_claim_status` / `nphies_preauth_ref` / `nphies_covered_amount` / `nphies_copay_amount` column set proven on `lab_invoices` (2026_04_03_000001); claims log to `nphies_transactions`.

---

## 5. Role of OBGY assets in this architecture

OBGY contributes **content, vocabulary, domain knowledge, and data — zero code** (its own blueprint
rule R12 forbids porting any endpoint; SQLi confirmed system-wide):

1. **The ~190 curated vocabularies** (production export mandatory — dump lists are empty, risk R7) become `his_lookups` rows: the single most valuable seeding asset for the forms engine.
2. **The Q&A history engine** (`presenthistoryquestions`/`presenthistoryanswers`/`gynaph`) is the *existence proof* of metadata-driven clinical capture and maps 1:1 onto `his_form_definitions`.
3. **19 specialty sheets** (ANC, gyna, infertility, endoscopy, ultrasound templates…) become reviewed JSON form definitions in ObgyPack — bucket B as content.
4. **Bucket-A patterns** inform the thin spine: `visits`/`visit_periods` → appointments+queue design; the consolidation of 18 `*drugs`/`*invest` clone tables → `his_prescriptions` + the D3 lab-order path; per-body-system exam → an examination form definition + `his_observations` vitals.
5. **Historical data** ETLs into: `lab_patients` (wife → patient, `statusno`→`mrn`; husband → `his_patient_spouses`), `his_encounters` (legacy `visits` minus the `detectionid IN (−99,999,9999)` payment rows, which split into folio payments), form responses (patient+date heuristic matcher, risk R2), `his_observations` (follicle grids, growth data), Core attachments (4.8 GB media, checksummed).
6. **The OBGY clinic is the pilot tenant** for Tier 3, with a forced security motive to migrate.

OBGY is explicitly **not** the HIS core and **not even a clinical schema donor** here: it is the
content pack that proves the forms engine on day one.

---

## 6. Honest confrontation: where metadata-driven breaks

1. **IVF cycle management is not a form.** The stimulation grid (legacy `ovst`, 26 columns,
   day-by-day per-ovary follicle counts), the stage state machine
   (retrieval→fertilization→transfer→cryo), and embryo inventory carry workflow, computation and
   legal weight. Concession: `obgy_ivf_cycles` + `obgy_embryos` hard tables, monitoring data in
   typed `his_observations`, and a bespoke grid widget. The "one renderer" claim erodes exactly at
   the product's flagship feature — acknowledged.
2. **Reporting performance.** "All patients with BMI>30 and a failed cycle in 2025" against JSON
   blobs is slow and miserable. Mitigation is real but costly: the `his_form_response_index`
   projection maintained on every save, generated/indexed columns for hot fields, and nightly
   marts for KPIs. This is permanent engineering overhead, and projection drift is a new bug class.
3. **No referential integrity inside JSON.** `value_lookup_id`s can dangle; validation is
   app-layer only — a step down from the platform's FK discipline. Partial mitigation via the
   index table FKs and save-time validation against the form version.
4. **Clinical logic leaks out of metadata.** GA computation, G/P formula, WHO semen flags,
   Westgard-style rules: computed-field expressions cover the simple half; the rest becomes a
   rules layer or hard code (semen/hormones are deliberately pushed to LIS for this reason).
5. **Form versioning burden.** Editing a definition with 10k responses demands version pinning,
   migration tooling, and version-aware printing. Solved by `his_form_versions`, but it is
   discipline the team must keep forever.
6. **NPHIES/FHIR mapping** from generic responses is harder than from typed tables;
   `FhirServiceItemBuilder` wants codes. Mitigation: billables never live in forms — charges go
   through `his_folio_charges.service_code`; clinical FHIR export (if ever needed) maps per
   form definition.
7. **The engine can become its own product** — the platform's "four print engines" lesson (risk
   register #3). Guard: the renderer and schema spec are frozen behind a small contract; new field
   types require board sign-off.
8. **Commercial optics**: "deep IVF support" implemented as forms may look shallow against
   specialty competitors; the IVF hard-table concession plus the grid widget is the answer, and it
   must be demo-quality.

---

## 7. Build phases and sizing

| Phase | Content | Size | Estimate |
|---|---|---|---|
| P0 | Approved Phase-0 spec as-is (W1-1..W2-2): DataScope hoist, `employee_id`, `CreateLabRequest`+`encounter_id`, PermissionDependencyRegistry, B2B hardening, gating log-only | S | ~7–10 dev-days (already approved) |
| P1a | Spine: `his_encounters/folios/folio_charges/deposits`, `OpenEncounter`/`CloseFolio` actions, Core `ChargePostingService` extraction, cashier/treasury hoist, `lab_requests.encounter_id` FK + LIS-order embed (D3), price-list/insurance generalization | L | 6–9 person-weeks |
| P1b | Scheduling: appointments + visit periods + queue (server-driven worklist contract), reception desk, patient-360 over MPI | M | 3–5 pw |
| P1c | Forms engine: definitions/versions/responses/index, `his-form-renderer` + widget library, lookup admin, configurable clinical print | M–L | 4–6 pw |
| P2 | ObgyPack: 19 form definitions authored+clinically reviewed, ~190 vocab ETL from production, IVF cycle tables + grid widget, semen/hormone LIS seeders, full data ETL (P0–P7 playbook) + pilot tenant go-live | L | 8–10 pw |
| P3 | Inpatient (unchanged from approved roadmap: ADT, beds, bed-day charges to folio, nursing) — deferred until tiers 2–3 revenue | L | 10–16 pw |

**Total to a sellable Moon Women's Health tier (through P2): ≈ 21–30 person-weeks**, with the
Clinic tier (P1a–P1c) sellable standalone at ≈ 13–20 pw. Inpatient later, unchanged.

---

## 8. Pros / Cons summary

**Pros**
- Smallest net-new surface in any proposal: ~17 HIS tables + 3 pack tables vs ~85 blueprint tables; admin functions ride engines already graded 9/10 and "ready".
- Honors every binding decision natively; v1 stays administrative-financial (Risk #7 contained *by construction* — clinical depth is content, license-keyed, separately funded).
- Any future specialty (cardio, derma, dental) = a content pack with zero migrations — the fastest specialty onboarding story in the market, perfectly aligned with the 4-tier licensing model.
- One FE renderer replaces ~19 specialty screens; OBGY clinicians review JSON content, not code.
- ETL friendliness: legacy clinical rows (which never reference `visits`) land as form responses without violating hard FKs; the patient+date matcher tolerance lives in content, not schema.
- The OBGY vocabulary corpus and Q&A engine are immediately monetized as seed content.

**Cons**
- EAV/JSON reporting costs are real and permanent (projection maintenance, marts, a new bug class).
- The flagship IVF feature needs hard tables and a bespoke grid anyway — the purity claim has a hole at the most visible spot.
- Referential integrity and type safety inside responses are app-layer only.
- Forms-engine scope creep risk (the print-engines lesson) requires standing governance.
- Clinical analytics/research queries will always be second-class vs typed schemas; if the product later pivots to research-grade EMR, a partial re-materialization to hard tables is the exit path (the index table makes that migration mechanical, but it is a cost).
- Form authoring + clinical review becomes a new discipline/role the team does not have today.
