# G6 — GYN urogynaecology: LUTS · SUI · OAB + the bladder diary (computed) · the urogyn exam + PVR / uroflow · POP-Q (auto stage, pre-op vs post-op across visits) · urodynamics (fields → an editable, signable P5 `uds_report`) · voiding / retention / nocturia · fistula · mesh complications · outcomes + PROMs (scored, baseline vs follow-up) · the urogyn follow-up schedule 6 w / 3 / 6 / 12 m · their summaries (`uds` · `urogyn`)

Read `_COMMON.md` first. G6 is **two serial WPs**: G6a (BE: install the 10 p3f modules from the committed schema JSON as **encounter-scope answers**, the urogyn computed keys (diary / NPI / ICIQ / POP-Q stage / uroflow / UDS report / PROM change / OAB class), the PROM item definitions + their scorers, the POP-Q series / before-after read model, the UDS report as a P5 `uds_report` draft, the urogyn follow-up rules, the 10 summary templates) → G6b (FE: the POP-Q widget, the PROM score widgets, the bladder-diary table with live totals, the UDS report card, the before/after POP-Q + PROM comparison). Like G2–G5, the modules are **definitions in the G1 engine — no table, no component per module**; only POP-Q (widget), the PROM scores (widget) and the UDS report (a P5 document) get bespoke UI.

**Depends on:** G1a–G5a ✅ (`Modules/Gyn`, `gyn_schema.json` — the 10 uro modules are already exported with `feature: urogyn` and their computed keys, `GynFieldPackSeeder`, `GynModuleValues`, `GynFieldWriteGuard`, `GynFeatures` `urogyn` (the `uro` category group + `urogyn/pop/sui_oab` gyn chips already lock), `GynProcedureCatalog` (the urogyn PROC_LIB rows), `GynOperationService::sign`, JS-fixture approach `export-g5a-fixtures.mjs`), C1a/C1b ✅ (`follow_up_tasks` + `FollowUpService::generate` + `ClinicalTypeRegistry::followupRules`; the `uds` / `urogyn` / `followup` summary types already declared for `gyn_ob`), P5 ✅ (`ClinicalDocType::UdsReport` authored, `ClinicalTemplate` kind `uds_report`, `DocumentPrefill::uds()` = the visit's `module_key='uds'` answers, `CreateClinicalDocument`, `PUT clinic/documents/{id}`, `POST clinic/documents/{id}/issue`), E1 ✅ (`HistoryInputType::PopQ` / `Score` / `Table`, `recomputeDerived` incl. per-row computed). G6b needs G6a ✅, G1b ✅, S1 ✅, G4b ✅.

## Spec (read before coding)
- `/home/moonui3/public_html/knowledge-base/plans/gyn-obstetrics-module-src/p3f-schema-uro.js` — **the whole file** (10 modules: luts · sui · oab · uroexam · popq · uds · voiding · fistula · meshc · urofu — sections, fields, options, show_if, `sum`). `Modules/Gyn/database/data/gyn_schema.json` is its committed machine form (`gyn_luts_*` … `gyn_urofu_*`) — do not hand-edit it. The `media` fields (`uf_img`, `uds.media`, `meshc.media`) are not in the JSON (media = AF3 attachments of the visit).
- `p4a-engine-calc.js` `popqStr` (line 7), `CALC.iciq / diary / diaryFrom / muiNote / oabClass / popq / ufSum / udsReport / npi / promChange` (lines 32–41); SUM_TYPES `uds` = profile, luts, uroexam, uds · `urogyn` = profile, luts, sui, oab, uroexam, popq, uds, voiding, fistula, meshc, urofu, operation · `followup` = profile, dx, followup, urofu, postpartum.
- `p4c-scenarios.js` scenario 3 «POP + SUI → Urodynamics → MUS + Anterior repair» (luts · sui · oab · uroexam · popq · uds · urofu = the fixture); **no voiding / fistula / meshc scenario** — hand-made cases.
- `../design-gyn-integration.md` §2 rows `popq_measurements`, `prom_scores`, `urodynamic_studies`, `follow_up_tasks`, صلاحيات `gyn.urogyn.*` / `gyn.uds.sign`; §3 (`popq` = widget; `udsReport` → summary type `uds`; `muiNote` = a hint); §4 computed list (`iciq, diary, diaryFrom, npi, popq, promChange, oabClass, ufSum`); §7 decisions 19 (POP-Q / PROMs = measurements, UDS = fields + document ⏳), G4 (urogyn 6 w / 3 / 6 / 12 m ⏳), G5 (questionnaires in use ⏳).
- `../design-data-layers.md` §3 — **the facts vs measurements rule** («الحقيقة ليها قيمة واحدة لكل scope؛ القياس بيتكرر داخل نفس الـ scope»): binding for decision 3/4 below.
- `../visit-shell-prototype.html` — the POP-Q 3×3 grid widget (`.popq`, leading point highlighted, `popq:` calc, line ~1030/1051), `GYN_SUM` `uds` / `urogyn`, the `uro` tab.
- `../master-plan.md` G6 row (acceptance: **POP-Q stage automatic + a before/after comparison · a UDS report that is edited and signed**).
- `../legacy-obgy-options-analysis.md` — read-only reference (pick-don't-type option lists). Never connect to the legacy system, never persist its credentials.
- `G1-gyn-foundation.md` decisions 2–8, `G2` 7–8, `G4` 1–2 / 7–8, `G5` 7 / 11 (one home per value, templates ported line by line, Gyn-only follow-up triggers) — binding here too.

## Decisions (do not re-litigate)
1. **The 10 modules are encounter-scope answers** (the JSON scope), `feature='urogyn'`, category `custom`, doctor fill. No entity, no table, no record scope: a urogyn work-up is a sequence of visits; the operation that treats it is G4's procedure. Feature off ⇒ the modules leave `GET gyn/schema`, their field writes 422 `gyn_feature_off` (existing guard), every new G6 endpoint 422 `gyn_feature_off`.
2. **Bladder diary = the `gyn_oab_diary` table answer** (`value_json` = the day rows, one diary per visit — the patient's 3–7 day sheet reviewed at one visit; a diary day is a summary row, not a timed measurement) → `gyn_diary` stored derived on save (CALC.diary port: averages / 24 h, mean voided volume, max, NPI % + «nocturnal polyuria» > 33 %). The PVR rows (`gyn_uroexam_pvr`, table with their own dates) likewise. Not D1 observations.
3. **POP-Q = the `popq` answers** `gyn_popq_pre` / `gyn_popq_post` (`value_json` `{aa, ba, c, gh, pb, tvl, ap, bp, d}`, cm, ± allowed) and **the stage is derived on save** (`gyn_popq` with `meta.ref` pre / post = `CALC.popq` ported: leading point over `Ba, Bp, C, Aa, Ap, D` in that order; IV `lead ≥ tvl − 2`, III `lead > +1`, II `lead ≥ −1`, 0 only when Aa/Ba/Ap/Bp = −3 and `C ≤ −(tvl − 2)`, else I; tone bad III/IV, warn II) stored as `{value: 0–4, text, tone, stage, leading, compartment}`. **Deviation from design-gyn §2 / decision 19 (⏳)** (an observation set of 9 definitions): a POP-Q is one examination per phase per visit — one value per scope, i.e. a fact by the design-data-layers §3 rule; observations would add 9 definitions + a phase inference and the widget would write two stores. Nothing is lost: **the series = the patient's visits' POP-Q answers**. **Before / after across visits:** the pre-op POP-Q of a visit = its own `pre`, else the latest earlier visit's `pre`; the post-op = its own `post` (when `post_done`). The summary of a follow-up visit that records only `post` therefore prints the pre-op from the earlier visit (deviation from the JS, which prints «(not measured)» for a one-card patient — documented). `GET gyn/patients/{p}/popq` = `{series: [{encounter_id, date, phase, points, stage, text, tone, leading}], index_procedure {id, performed_at, procs}|null, comparison {pre, post, stage_change, points_delta{aa…d}}|null}` (pre = the latest pre-op before the index procedure — the latest signed urogyn procedure — or the latest `pre`; post = the latest `post` after it). The series keeps a visit's `post` points unless `post_done` is explicitly No; the summary prints the post-op only when `post_done` = Yes (JS identity) — **the G6b widget sets `post_done` when it writes `post`**.
4. **PROMs = scored answers, items seeded in code** (the JSON `score` type is numeric-only in E1 — a score field cannot hold its items). `Modules/Gyn/app/Support/GynProms.php` declares the instruments and seeds their **item questions** (single-select, options = the item's allowed values, create-only per (company, code) — the G5a `GynObservationDefinitions` rule; a removed code is never resurrected) and their **total** questions (`input_type=score`, `computed_key=gyn_prom`, `meta.instrument`), all encounter scope:
   - **ICIQ-UI SF** (in `sui`, total = the JSON's `gyn_sui_iciq`, `computed_key=gyn_iciq`): items `gyn_sui_iciq_f` (0–5), `gyn_sui_iciq_a` (0 / 2 / 4 / 6), `gyn_sui_iciq_i` (0–10) → `ICIQ-UI SF n/21 (none | slight 1–5 | moderate 6–12 | severe 13–18 | very severe 19–21)`.
   - **PFDI-20** (in `urofu`): **UDI-6** (6 items), **POPDI-6** (6), **CRADI-8** (8), each item 0–4 (0 = not present, 1–4 = bother), scale = mean × 25 (0–100, one decimal), only when every item of the scale is answered (else `partial (n/N)`); **PFDI-20** total = the 3 scales' sum (0–300) when all three are complete. UDI-6 = the PFDI-20 subscale (0–4 response scale) so scenario 3's `udi_b: 67` stays on the same 0–100 scale.
   - **OAB-q SF symptom bother** (in `urofu`, 6 items 1–6) → transformed `(sum − 6) / 30 × 100` (0–100, higher = worse).
   - **PGI-I** = the JSON's `gyn_urofu_pgi` (no change).
   Item labels are short symptom paraphrases (like the p3 ICIQ items), never the licensed questionnaire wording; the owner confirms the instrument list (master-plan G5 ⏳). **Baseline vs follow-up:** the urofu `iciq_b/_f`, `udi_b/_f`, `oabq_b/_f` stay seeded manual numbers (an outside / paper score) — when a pair is empty, `promChange` (`gyn_prom_change`, stored derived) takes it from the series: baseline = the latest total on / before the index urogyn procedure date (else the patient's first total), follow-up = this visit's total (else the latest after the baseline visit). `GET gyn/patients/{p}/proms` = per instrument `{series [{encounter_id, date, value, text}], baseline, latest, change}`. A partial scale (`partial (n/N)`, no value) is not a point of the series.
5. **UDS = the `uds` answers + a P5 `uds_report` document.** `gyn_uds_status` (Draft / Finalized) is **not seeded** — one home: the document's status (draft → issued). `gyn_uds_report` (computed, no key) is rendered by the read model / template (`CALC.udsReport` port). `POST gyn/encounters/{e}/uds-report` creates the **draft** through `CreateClinicalDocument` (`doc_type=uds_report`, body = the C1b `uds` summary type sections of the visit — profile · LUTS · urogyn exam · UDS, the `GynOperationService::dischargeDocument` pattern; `data.uds` = P5's prefill; `fields.report` = the udsReport text); a second call returns the existing draft, `{regenerate: true}` rebuilds its body; an issued report → a new draft (an amendment). **Edit** = P5 `PUT clinic/documents/{id}`, **sign** = P5 `POST clinic/documents/{id}/issue` (`clinic.documents.issue`, number + PDF). `GET gyn/encounters/{e}/uds` = `{values (p3 shape), report, diary, uroflow, document {id, status, document_number, signed_at}|null}`. `gyn_diary_from` / `gyn_uf_sum` read the same visit's `oab.diary` / `uroexam` uroflow, else the latest earlier visit within **90 days** that has it (a UDS is often a separate visit).
6. **Follow-ups (code defaults, doctor to confirm — design-gyn G4 ⏳)** = `follow_up_tasks` through `FollowUpService::generate` with **Gyn-only trigger strings** (G5a decision 7): `urogyn_post_op` (`gyn.urogyn.procedure_signed` — 6 w, 3 m, 6 m, 12 m, cards `urofu` + `popq`) fired by `GynOperationService::sign` when the procedure's `procedure_codes` hold a **urogyn PROC_LIB code** (`GynSchema::UROGYN_PROCEDURES`: burch_colposuspension, mid_urethral_sling_retropubic / _transobturator, single_incision_sling, autologous_fascial_sling, urethral_bulking, anterior_repair, posterior_repair, vaginal_hysterectomy_pos_repair, sacrospinous_fixation, uterosacral_suspension, lateral_suspension, sacrocolpopexy, mesh_procedure, colpocleisis, fistula_repair, mesh_excision) and `urogyn` is on: in the same transaction the C1 generic `post_procedure` open tasks of that procedure are **cancelled** (G5a delivery precedent) and the urogyn rule is generated anchored at `performed_at`. `urogyn_treatment` (`gyn.urogyn.treatment_started` — 6 w, 3 m, 6 m, 12 m) via `POST gyn/encounters/{e}/urogyn/follow-ups` for a non-surgical pathway (pessary / PFMT / Botox / SNM), anchored at the visit date (source = the encounter). The G4a follow-up summary prints «6 weeks, 3 months, 6 months, 12 months» from those tasks.
7. **Mesh complications** = the `meshc` answers (+ summary); the urogyn read model lists the patient's signed mesh procedures (`GynSchema::MESH_PROCEDURES`: the MUS ×3, single-incision sling, sacrocolpopexy, mesh_procedure) so the FE can prompt; no D2 condition / alert (out of scope).
8. **`voiding.pvr`** (`gyn_mirror` of the `gyn_uroexam_pvr` table) prints the PVR rows as the uroexam summary does («40 mL (02 Apr 2026), 35 mL») — `GynComputed::mirror` learns table refs. `voiding.boo_note` / `oab.mui_note` = static hints (`meta.hint`), never derived. `gyn_oab_drugs_class` = per-row computed (`CALC.oabClass`, row codes → labels).
9. **Summaries** = `Modules/Gyn/app/Summary/{Luts,Sui,Oab,Uroexam,Popq,Uds,Voiding,Fistula,Meshc,Urofu}Template.php`, ported **line by line** from the p3f `sum` (English, identical to the JS for the same values). `CALC.popq` / `CALC.npi` `{t, cls}` render as `.t` (the JS interpolates them raw → «[object Object]»; G2a / G5a documented deviation). Popq applies decision 3's pre fallback; urofu decision 4's PROM fallback; uds = `udsReport` with decision 5's diary / uroflow fallback. The `uds` / `urogyn` / `followup` types already exist (C1b).
10. **Permissions:** no new `gyn.*` (G4a precedent) — answers `clinic.history.record` (+ `clinic.history.view` for the read models), the report `clinic.documents.create` / `clinic.documents.issue`, the follow-ups `clinic.followups.manage`. design-gyn's `gyn.urogyn.*` / `gyn.uds.sign` are **not** introduced: P5's issue permission already gates signing (report — the orchestrator may add `gyn.uds.sign` as a dependency later).

## Shared-file rules
- Orchestrator-owned — REPORT: `Modules/Clinic/routes/api.php`, `RolePermissionSeeder.php`, `StandardRolesSeeder.php`, `ClinicPermissionDependencies`, setting seeders, permission-labels, `config/moonstack.php`. You own `Modules/Gyn/routes/api.php` and `GrantGynPermissionsSeeder` (untouched in G6a — decision 10).
- No Clinic file is edited in G6a (the hooks are `ComputedRegistry::extend`, `ClinicalTypeRegistry::register`, the P5 actions).
- FE i18n (G6b): targeted keys under `GYN.*` only, ar + en; **no `I18N_VERSION` bump**.

---

## G6a — BE (migrations `2026_11_03_420000`–`420099`, in `Modules/Gyn/database/migrations`)

### Files
- `Modules/Gyn/app/Support/GynSchema.php` (+`G6_MODULES`, `G6_SKIP`, `UROGYN_PROCEDURES`, `MESH_PROCEDURES`), `GynComputed.php` (+8 keys + `gyn_prom`, table mirror), `GynExtensions.php` (10 templates, 2 follow-up rules), `Services/GynPackInstaller.php` (default set + PROM items), `Services/GynOperationService.php` (sign → urogyn rule), `Http/Controllers/GynSchemaController.php` (PROM items in the module fields + `proms` map), `routes/api.php`, `lang/{ar,en}/gyn.php`.
- New: `Support/GynUrogyn.php` (CALC ports: popqStr / popq / iciq / diary / npi / oabClass / ufSum / udsReport / promChange), `Support/GynProms.php` (instruments, item + total definitions, scorers), `Services/GynUrogynService.php` (POP-Q series + comparison, PROM series, UDS read model + report draft, follow-ups, prior-visit fallback), `Http/Controllers/GynUrogynController.php`, `Summary/{Luts,Sui,Oab,Uroexam,Popq,Uds,Voiding,Fistula,Meshc,Urofu}Template.php`, `database/data/tools/export-g6a-fixtures.mjs` → `tests/Fixtures/g6a_expected.json`.
- Migration: `420000_install_gyn_urogyn_modules` (data, pattern 410000: the 10 modules + the PROM item / total definitions on every gyn_ob company).

### Interfaces (`api/gyn`, company-scoped, patient data scope through `Patient::query()`)
| Method | Path | Permission | Body / result |
|---|---|---|---|
| GET | `patients/{patient}/urogyn` | `clinic.history.view` | `{popq (decision 3), proms (decision 4), mesh_procedures[], follow_ups[], features.urogyn}` |
| GET | `patients/{patient}/popq` | `clinic.history.view` | decision 3 |
| GET | `patients/{patient}/proms` | `clinic.history.view` | decision 4 |
| GET | `encounters/{encounter}/uds` | `clinic.history.view` | decision 5 |
| POST | `encounters/{encounter}/uds-report` | `clinic.documents.create` | `{regenerate?, language?}` → 201 draft / 200 existing draft |
| POST | `encounters/{encounter}/urogyn/follow-ups` | `clinic.followups.manage` | `{anchor_date?}` → the urogyn_treatment tasks |
Fields: the shared encounter answers path. Report edit / issue: the P5 endpoints.

### Tests — `Modules/Gyn/tests/Feature/G6aGynUrogynTest.php` + `Modules/Gyn/tests/Unit/G6aGynUrogynComputedTest.php` (prefix `g6a_`)
installer seeds the 10 modules (encounter scope, `gyn_uds_status` absent) + the PROM items / totals idempotently; computed vectors = the JS (popq stages 0 / I / II / III / IV + leading point + popqStr signs, iciq bands, diary (+ NPI polyuria), npi, oabClass, ufSum, udsReport, promChange) and the PROM scorers (complete / partial / PFDI-20 sum / OAB-q transform); the 10 summary ports = the JS for scenario 3 + hand-made voiding / fistula / meshc / edge cases; save path: the diary table → `gyn_diary` derived, POP-Q pre → stage derived (`value_json.stage`), ICIQ items → `gyn_sui_iciq` total, UDI-6 items → `gyn_prom_udi6`, OAB drug row → class; voiding.pvr mirror text; POP-Q pre on visit 1 + post on visit 2 → the popq summary of visit 2 prints both + the comparison endpoint; PROM baseline / follow-up from the series when the manual pair is empty; UDS report draft (body from the `uds` summary, existing draft returned, regenerate, after issue a new draft) and the uds read model (diary from an earlier visit); a signed MUS → generic post_procedure cancelled + 6 w / 3 / 6 / 12 m tasks, a TLH keeps 7 d / 1 / 3 / 6 m; treatment follow-ups; the `uds` / `urogyn` summary types carry the lines; feature off ⇒ 422 + modules hidden + field write 422. Run also every `Modules/Gyn/tests`, `Af2IsolationTest`, `PlatformD0FieldIsolationTest`, `ClinicProfilePackTest`, `C1aEntitiesTest`, `C1bSummaryRecordTest`, `P5ClinicalDocumentsTest`, `D1ObservationsTest` — keep green.

### Out of scope (G6a)
Any FE; urodynamic curve storage / device import; an automatic BOO / DO diagnosis (the JS forbids it — clinician interpretation only); conservative-treatment rule variants per pathway (one `urogyn_treatment` schedule); mesh history → D2 condition / alert; PROM item wording licensing (labels are paraphrases); FSFI / FSDS-R (G7 sexual); a urogyn outcomes dashboard.

---

## G6b — FE (`src/app/features/gyn/`; no migrations)

Design consult (cheap agent, not Fable — owner rule) on: the POP-Q widget (3×3 grid Aa Ba C / gh pb TVL / Ap Bp D, live stage chip, leading point highlighted, pre / post side by side + the comparison from `GET patients/{p}/popq`), the PROM widgets (item pickers → live total chip; baseline / follow-up / change), the bladder-diary table (day rows + live totals line), the UDS report card (fields → «إنشاء التقرير» → P5 editor → issue), the urogyn follow-up timeline.

**G6a hand-off notes:** `GET gyn/schema` lists the PROM item fields inside `sui` / `urofu` `fields[]` (urofu's after every schema field — sort_order) and a `proms` map `{total_code: {instrument, title_en, title_ar, items: [codes], scale, of?}}`; `uds` carries `document_fields: {gyn_uds_status: 'uds_report.status'}`; `score` / `popq` fields are the widgets the module form defers today (`LATER_TYPES`). `gyn_uds_status` is in `sections[].codes` but has **no** `fields[]` entry — show the document status from `GET encounters/{e}/uds`. `computed.ts` mirrors `popq / iciq / diary / npi / oabClass / ufSum / udsReport / promChange` + the PROM scorers (vectors in `g6a_expected.json`). The popq value is `{aa, ba, c, gh, pb, tvl, ap, bp, d}` numbers (cm).

### Do
1. `gyn-popq-widget.component.ts` — writes `gyn_popq_pre` / `gyn_popq_post` through the encounter path; live stage; the before / after card.
2. `gyn-prom-score.component.ts` — the item pickers of a PROM total (ICIQ in `sui`, UDI-6 / POPDI-6 / CRADI-8 / OAB-q in `urofu`), live total; urofu shows the series baseline / follow-up.
3. The bladder-diary table (`gyn_oab_diary`) with the live `diary` line; the UDS report card.
4. `computed.ts` (G1b file — extend) identical to PHP for the G6a vectors (`npx tsx` one-off; report).
5. i18n `GYN.UROGYN.*`, `GYN.POPQ.*`, `GYN.PROM.*`, `GYN.UDS.*` (ar + en), no version bump. `npx ng build --base-href /app/` green. No deploy.

---

## Report back (each WP)
Files created/edited; installer counts; test output with pass counts; migrations + dev commands; Gyn route lines (Clinic route lines to paste — should be none); permissions/settings needed; CHANGELOG EN/AR; decisions taken; files needed but not touched; any p3f field the JSON lacks or the summary port could not match exactly.
