# ClinicLab

The **ClinicCore ↔ LabCore bridge**. It lets a clinic order LabCore lab tests from a
ClinicCore encounter, surfaces analyzer results on the shared patient timeline, and
(SIFA **P4-T4**) adds the clinic-facing laboratory layer that LabCore — a pure device
engine — deliberately has no concept of: a test catalogue, specimen tracking, a
tech→verify result-validation workflow, and outreach management.

> Ground rule: **all** ClinicLab schema lives in this module (tables prefixed `cll_`,
> plus additive `clc_*`/`lab_*` linkage columns guarded with `Schema::hasColumn`).
> LabCore stays a device engine and is never edited; ClinicCore is only touched
> through guarded, fail-soft `DB::table()` writes.

## Surfaces (under `/clinic-lab`)

| Tab | Route name | Permission |
|-----|-----------|------------|
| Worklist (legacy order/results + specimen/review status) | `clinic-lab.index` | legacy clinic/lab perms |
| Specimens | `clinic-lab.specimens.index` | `cliniclab.specimen.manage` |
| Reviews | `clinic-lab.reviews.index` | `cliniclab.result.review` / `.verify` |
| Catalog | `clinic-lab.catalog.index` | `cliniclab.catalog.manage` |
| Outreach | `clinic-lab.outreach.index` | `cliniclab.outreach.manage` |

## Tables (all `business_id` scoped)

- `cll_test_catalog` — clinic-facing tests/panels; links an ERP `products` row
  (pricing/billing) to specimen requirements. `code` unique per business.
- `cll_test_catalog_analytes` — panel definition + reference/critical ranges per
  parameter. `analyte_code` maps LabCore vendor codes.
- `cll_specimens` — barcoded specimen (`SPX-{biz}-{seq}`) + full status chain, linked
  to a `lab_worklist` row and (when routed) the `clc_order_items` row.
- `cll_result_reviews` — one tech→verify record per `lab_test_sessions` row.
- `cll_outreach_providers` / `cll_outreach_orders` — external reference-lab providers
  and the external-patient samples they send through the specimen flow.

## The closed loop

```
ClinicCore order (Wave C)
  → OrderRoutingService inserts a lab_worklist row (sample_no = ORD-…-{item},
    downstream_type/id back on the clc_order_item)
  → ClinicLab "Generate specimen" creates a cll_specimens row (barcode label,
    CODE128 via the ERP milon/barcode lib), resolving the clc_order_item linkage
  → collect → receive → processing transitions (reject writes a note back to the
    source clc_order_item, guarded/fail-soft)
  → the analyzer (LabCore) reports results into lab_test_sessions / lab_test_results
  → Reviews tab lazily creates a cll_result_reviews (pending_review) per session
  → REVIEW (cliniclab.result.review): technician sign-off
  → VERIFY (cliniclab.result.verify):
        • apply catalog critical bounds (cll_test_catalog_analytes, falling back to
          the analyzer's own ref range / abnormal roll-up)
        • ANY critical analyte →
            – MedicalAlertService->raise(alert_type=critical_value, severity=critical,
              patient-linked when clc_patient_id present, idempotent per session via
              source_type=cll_lab_session / source_id=session.id)
            – MedNotify->notify('lab.result.ready', …) to the patient contact
        • write back to the linked clc_order_item: status=resulted, abnormal_flag,
          result_summary  ← closes the Wave C order loop
```

### Critical-value behaviour

On verify, each numeric analyte is checked against, in priority order: (1) the
catalogue's `critical_low`/`critical_high`, then its `ref_low`/`ref_high`; (2) the
analyzer's own `ref_lower`/`ref_upper`; (3) the analyzer's normalized `is_abnormal`
roll-up. A `critical_low`/`critical_high` breach raises exactly one medical alert per
session (idempotent — re-verifying refreshes it, never duplicates) and, for a
patient-linked session, fans out through MedNotify. Every cross-module call is
fail-soft: a missing/erroring ClinicCore or MedNotify never blocks a lab tech from
verifying.

## Install

ClinicLab is entitlement-gated per business (`module_entitled('ClinicLab')`, via
SaasSwitcher). The P4-T4 menu + permissions are surfaced by the ERP's
`ModuleUtil::getModuleData()`, which only invokes a module's `DataController` once
`isModuleInstalled()` sees a lowercase **`cliniclab_version`** system property — so the
`InstallController` writes it (mirroring LabCore) at `/clinic-lab/install`. Non-production
installs also run `module:seed ClinicLab` → `ClinicLabDemoSeeder` (10 common tests +
one outreach provider), guarded to the **Shifa One** demo business and linking
`product_id` by name to the seeded "laboratory tests" products.
