# G2 — GYN core modules: fibroid (FIGO, repeat) · AUB PALM-COEIN · endometriosis · adenomyosis · polyp/hyperplasia · PCOS (Rotterdam) · pelvic pain · adnexal mass (RMI) · gyn oncology + MDT · cervical · vulval/vaginal — and their summaries

Read `_COMMON.md` first. G2 is **two serial WPs**: G2a (BE: install the 11 modules from the committed schema JSON, computed keys, summary templates, RMI suggestion + MDT referral actions) → G2b (FE: FIGO widget, suggestion/MDT UX, module cards in the host). These modules need **no new tables and no new UI per module** — they are definitions in the G1 engine. If you find yourself writing a component or a table for one module, stop: only FIGO and the RMI/MDT actions get bespoke UI.

**Depends on:** G1a ✅ (`Modules/Gyn`, `gyn_schema.json`, `GynFieldPackSeeder`, `GynModuleRegistry`, `GynComputed` + the `{value,text,tone}` derived-write convention in `HistoryService`, `GynModuleValues::local`, `FieldWriteGuard`, `GynFeatures`), G1b ✅ (`gyn-module-host`, `gyn-module-form`, TS `show-if.ts`/`computed.ts`, GYN summary card), C1a/C1b ✅ (team/MDT referrals, `SummaryTemplateRegistry`).

## Spec (read before coding)
- `/home/moonui3/public_html/knowledge-base/plans/gyn-obstetrics-module-src/p3b-schema-gyn.js` — **the whole file** (11 modules: sections, fields, options, show_if, `sum` functions). It is the content spec; `Modules/Gyn/database/data/gyn_schema.json` is its committed machine form (generated in G1a) — read both and confirm the 11 modules are complete in the JSON before you start (if a field is missing, re-run the G1a exporter, don't hand-edit the JSON).
- `p4a-engine-calc.js` `CALC.rotterdam`, `CALC.rmi`, `CALC.rasrm`, `CALC.bmiProfile` (lines ~14–20), `p4b-engine-ui.js` (`ev`, FIGO rendering — grep `figo`), `p4c-scenarios.js` (fibroid/adnexal/pcos scenario values = your test fixtures; also `../visit-shell-prototype.html` `GYN_SCEN`).
- `../design-gyn-integration.md` §3 (modules = cards, `gyn.feature.*`), §4 (types: `figo` = single_select with `meta.render=figo`; computed keys list), §2 rows Summary generator + team_referrals.
- `../design-visit-shell.md` §3 (module host behaviour), §2.6 (pick-don't-type).
- `../master-plan.md` G2 row (acceptance: **Myoma 1..n with FIGO and an aggregated summary · RMI ≥ 200 suggests an oncology referral**).
- `G1-gyn-foundation.md` decisions 2–8 (placement, export rules, codes, categories, features) — binding here too.

## Decisions already made (do not re-litigate)
1. All 11 modules are **encounter-scope L4 answers** (`clinical_type='gyn_ob'`, `module_key=<module>`), category `custom`, `fill_role=doctor`, not in intake. Repeat rows (`fibroid.myoma`, `adnexal.mass`) use `repeat_group` + `repeat_index` (E1e). Visibility = module show_if (registry) AND field show_if, exactly as the JSON says — e.g. fibroid opens on `{cat:'fibroid'}` OR `{k:'gyn_imaging_myo', has:<code of 'Fibroid(s) → Fibroid module'>}`; adnexal on cats `adnexal|ovarian_cyst|tubal`; onc on `{cat:'onc'}` OR the adnexal plan code for «Oncology referral (RMI ≥200 / O-RADS 4–5)».
2. **Feature mapping:** all 11 are group `gyn` (always available) except `onc`, which carries `feature='oncology'` (`gyn.feature.oncology` off ⇒ the onc category, module, MDT action and its summary disappear; adnexal still computes RMI but the suggestion text omits the referral action).
3. **FIGO** (`fibroid.myoma.figo`, `input_type=figo`): value = one code of `0,1,2,3,4,5,6,7,8,2-5` (FIGO 2011 leiomyoma subclassification incl. hybrid «2-5»), stored like a single_select (`answer_value` = code, `value_json` = `{type, hybrid?:true}`); options seeded as `history_answer_options` with labels (`0 Pedunculated intracavitary`, `1 <50% intramural`, `2 ≥50% intramural`, `3 contacts endometrium, 100% intramural`, `4 intramural`, `5 subserosal ≥50% intramural`, `6 subserosal <50% intramural`, `7 subserosal pedunculated`, `8 other (cervical, parasitic)`, `2-5 submucosal and subserosal`). `HistoryService` validates `figo` against those options (extend the existing single-select branch to include `Figo` — G2a owns HistoryService during G2).
4. **Computed keys** (registered in `GynComputed`, ported line-by-line from p4a, returning `{value, text, tone}`): `gyn_rotterdam` (value = criteria count 0–3, text/tone exactly as `CALC.rotterdam`, `—` ⇒ null when no input), `gyn_rmi` (RMI = CA-125 × U × M; U from the mass rows: multilocular, solid, bilateral (rows>1 or side Bilateral), ascites, mets → 0 features U=0, 1 ⇒ 1, ≥2 ⇒ 3; M = 3 if menopause else 1; tone `bad` + text «N (≥200 — oncology referral)» when ≥ 200), `gyn_rasrm` (stage text from points: ≤5 I, ≤15 II, ≤40 III, else IV; value = points), `gyn_bmi_profile` (BMI from the latest weight/height observations — D1 `ObservationService::latestFor`, else core profile answers; text `—` when none), `gyn_count` and `gyn_mirror` already exist (G1a) — fibroid `count` uses `gyn_count`, cervical `pap`/`hpv` use `gyn_mirror` of `gyn_imaging_pap`/`gyn_imaging_hpv`.
5. **RMI ≥ 200 is a suggestion, never an automatic action.** BE returns it on the module read model (`suggestions: [{code:'oncology_referral', reason:'rmi', value, text}]`) and in the summary line; creating the referral happens only when the doctor clicks it (C1 team referral `kind=mdt`, `team='oncology'`, reason prefilled «RMI 312 — adnexal mass»). Same for O-RADS 4–5 / IOTA «Malignant» rows (suggestion reason `orads|iota`).
6. **MDT** (onc `mdt` chips, endometriosis `mdt` chips): the chips stay answers; an explicit action «إنشاء إحالات الفريق» creates one C1 referral per chosen team that has no open referral of the same team in this episode/encounter (idempotent), `kind=mdt`. Team codes map from the module's chip codes to the C1 `team` catalog codes via a static map in `Modules/Gyn/app/Support/MdtTeamMap.php` (e.g. `gyn_oncology→oncology`, `medical_oncology→oncology`, `radiotherapy→oncology`, `radiology→radiology`, `pathology→pathology`, `colorectal→colorectal_gi`, `urology→urology`, `palliative→other`, `endometriosis_team→endometriosis_team`, `fertility→fertility_ivf`, `gyn→gyn`) — unknown → `other` with the label in `reason`.
7. **Summaries** = PHP templates per module, ported **line by line** from each module's `sum` in p3b (English, identical output to the JS for the same values; aggregated fibroid line «Fibroid uterus 10 × 6 × 5 cm, AV, 2 fibroids — Myoma 1: 4.2 cm FIGO 2 posterior mid corporeal (distorts cavity); Myoma 2: …. Plan: …»). Registered for module keys in `SummaryTemplateRegistry`; included automatically by the gyn_ob `clinic` summary when the module is visible and has values (G1a/C1b mechanism). Option codes are resolved back to their `value_en` before formatting (the JS used raw strings — the resolved strings must match them).
8. Pick-don't-type: every chips list in these modules renders as the compact picker (selected chips + «＋ اختيار»), segmented only for `seg` ≤ 7 options — the G1b form already does this; do not special-case modules.

## Shared-file rules
- Orchestrator-owned — REPORT: `Modules/Clinic/routes/api.php`, `RolePermissionSeeder.php`, `ClinicSettingDefinitionSeeder.php`. You own `Modules/Gyn/routes/api.php` (add your 2 routes there).
- Hot files you own during G2 (minimal, additive): `Modules/Clinic/app/Services/HistoryService.php` (FIGO validation only). Everything else lives in `Modules/Gyn` / `src/app/features/gyn`.
- FE i18n: targeted keys under `GYN.*` only, ar + en; **no `I18N_VERSION` bump**.

---

## G2a — BE (migrations `2026_11_03_202000`–`202099`, in `Modules/Gyn/database/migrations`)

### Do
1. **Install**: extend `GynPackInstaller`/`GynFieldPackSeeder`'s default module set with the 11 module keys (so new gyn companies get them) + data migration `202000_install_gyn_core_modules.php` (every company with a `gyn_ob` department; non-fatal, logged; skipped in unit tests like the G1 install migration). Their lookups (`gyn.v.*` vocab like `histo`, and `gyn.<module>.<k>` lists) and `history_answer_options` come from the JSON. FIGO options per decision 3 (the JSON has `t:'figo'` with no options — add them in the seeder from a constant in `Modules/Gyn/app/Support/Figo.php`).
2. **`HistoryService`**: `Figo` joins the single-select validation path (decision 3); a repeat child `figo` value validates per row.
3. **Computed** (decision 4) in `Modules/Gyn/app/Support/GynComputed.php` (G1a file — extend it): `gyn_rotterdam`, `gyn_rmi`, `gyn_rasrm`, `gyn_bmi_profile`. Unit-test each against the p4c/`GYN_SCEN` values and edge cases (missing CA-125 ⇒ null; one mass, unilocular, premenopausal ⇒ U=0 ⇒ RMI 0; RMI exactly 200 ⇒ bad).
4. **Suggestions** `Modules/Gyn/app/Services/GynSuggestionService.php`: `forEncounter(Encounter): list<suggestion>` (RMI ≥ 200, any mass row `orads` ∈ {4,5} or `iota`=malignant ⇒ `oncology_referral`; skipped when `gyn.feature.oncology` is off or an open mdt/team referral to oncology already exists for this episode/encounter). Expose through `GET api/gyn/encounters/{encounter}/suggestions` and inside the existing `GET api/gyn/schema`-adjacent module read the FE uses (if G1a added an encounter module read endpoint, add a `suggestions` key there instead of a new route — read first).
5. **MDT action** `POST api/gyn/encounters/{encounter}/mdt-referrals` body `{module_key: 'onc'|'endometriosis'|'adnexal', teams?: [codes]}` → creates C1 referrals per decision 6 via C1's referral service (no direct table writes), returns the created/existing referrals; permission `gyn.file.edit` + `clinic.referrals.create`; encounter must be editable.
6. **Summary templates** (decision 7): `Modules/Gyn/app/Summary/{Fibroid,Aub,Endometriosis,Adenomyosis,Polyp,Pcos,PelvicPain,Adnexal,Onc,Cervical,Vulval}Template.php`, registered in the Gyn provider. Use `GynModuleValues::local()`; resolve codes → `value_en`; reuse the computed outputs (`rotterdam`, `rasrm`, `rmi`) exactly as the JS calls `CALC.*` inside `sum`.
7. **Fertility hand-off** (no G3 code yet): PCOS plan code «Fertility pathway» and endometriosis plan «Fertility treatment» are plain answers now; do not open fert modules (G3 rewires show_if from the JSON — nothing to do here).
8. CHANGELOG lines EN/AR in the report.

### Tests — `Modules/Gyn/tests/Feature/G2aGynCoreModulesTest.php` + `Modules/Gyn/tests/Unit/G2aGynComputedTest.php` (prefix `g2a_`)
installer idempotent and creates the 11 modules' fields + lookups; category `fibroid` opens the fibroid module, imaging `myo` «Fibroid(s) → Fibroid module» also opens it; two myoma rows with FIGO 2 and 5 persist side by side (`repeat_index` 0/1), an invalid FIGO code 422, `gyn_count` = 2; fibroid summary for the `GYN_SCEN.fibroid` values equals the JS `sum` output string; AUB/PCOS/endometriosis/adnexal/onc summaries match their JS outputs for scenario values; Rotterdam 2/3 → tone warn + text; RMI scenario ≥200 → `bad`, suggestion present, absent after an oncology MDT referral exists, absent when `gyn.feature.oncology` off (and the onc module + category hidden); MDT action creates one referral per team and a second call creates none; a private/feature-off guard still applies (urogyn off unaffected). Run also `G1aGynFoundationTest`, `G1aSchemaExportTest`, `C1aEntitiesTest`, `C1bSummaryRecordTest`, `E1eAnswersTest`, `HistoryEngineTest` — keep green.

### Out of scope (G2a)
Fertility/obstetric/urogyn/sexual modules, operations templates, any new table, Clinic routes.

---

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

**Fable design consult is MANDATORY but narrow:** the FIGO picker (diagram vs grid of 10 labelled cells, how a hybrid 2-5 shows, mobile), the RMI/O-RADS suggestion banner inside the adnexal module (tone, wording, one-click «تحويل للأورام»), the MDT action placement, and the aggregated fibroid summary line in the module's collapsed header. Send screenshots of the prototype fibroid module (`../visit-shell-prototype.html`, GYN specialty, fibroid scenario) and of your built module host. One round, two at most; record decisions.

### Do
1. `gyn-figo-picker.component.ts` — the `figo` input renderer registered in `gyn-module-form`'s type switch (G1b file — add one case): 10 options in FIGO order with short labels + tooltip with the full definition, hybrid «2-5» visually distinct, keyboard/RTL-safe (numbers `ltr`), compact on mobile; value = the code.
2. `computed.ts` (G1b file — extend): `rotterdam`, `rmi`, `rasrm`, `bmiProfile` TS mirrors returning `{value, text, tone}` identical to PHP for the G2a unit-test vectors (add those vectors to `Modules/Clinic/tests/Fixtures/` as `gyn_computed_cases.json` if G2a didn't, and check with the same `npx tsx` one-off as G1b; report).
3. Suggestion banner `gyn-suggestion-banner.component.ts` rendered by `gyn-module-host` above the adnexal module (and onc when opened via the suggestion): reads the suggestions endpoint after each autosave (debounced) and offers «تحويل للأورام (MDT)» → `POST …/mdt-referrals {module_key:'adnexal', teams:['gyn_oncology']}` → toast + the team-referral card refreshes (emit the shell's `changed`).
4. «إنشاء إحالات الفريق» action in the onc and endometriosis modules' footer (only when the `mdt` chips are non-empty and `gyn.feature.oncology` / referrals permission allow).
5. Module collapsed summary line: use the BE summary text for the module when available (G2a templates) else the generic «Label: value» line (G1b) — e.g. fibroid shows «2 fibroids — FIGO 2, 5 · Plan: …».
6. Each of the 11 modules is addable from the catalog as `gyn_<key>` (G1b runtime registration already does this — verify they appear; onc carries `feature:'oncology'`).
7. i18n targeted keys `GYN.FIGO.*`, `GYN.SUGGEST.*`, `GYN.MDT.*` (ar + en), no version bump. `npx ng build --base-href /app/` green. No deploy.

### Out of scope (G2b)
New module components, fertility/OB/uro/sex UI, BE.

---

## Report back (each WP)
Files created/edited; installer counts (fields/lookups/options per module); test output with pass counts; TS vector result; Gyn route lines you added (and any Clinic route lines to paste — should be none); CHANGELOG EN/AR; files needed but not touched; Fable decisions (G2b); any p3b field that the JSON lacks or that the summary port could not match exactly.
