medOS ultra

Nuclear Medicine RIS/PACS Integration

NM integration: NM-RIS, radiopharmacy info system, DICOM MWL/MPPS for NM scanners, dose registry, isotope coding map.

16 min read diagramsUpdated 2026-05-18docs/architecture/nuclear-medicine-ris-pacs-integration.md

Status: Design β€” implementation enters module loop after the general RIS adapter (docs/ris-adapter-loop/) ships. Prerequisite: The HL7v2 RIS adapter (see docs/architecture/ris-adapter-design.md) provides the shared transport layer (MLLP client, dispatch log, config). This document extends it with NM-specific message profiles, radiopharmacy workflow, dose tracking integration, and NM-PACS bridging.


1. Scope

ΰΈ£ΰΈ­ΰΈ‡ΰΈ£ΰΈ±ΰΈšΰΈΰΈ²ΰΈ£ΰΉ€ΰΈŠΰΈ·ΰΉˆΰΈ­ΰΈ‘ΰΉ‚ΰΈ’ΰΈ‡ΰΈ‚ΰΉ‰ΰΈ­ΰΈ‘ΰΈΉΰΈ₯กับระบบ Nuclear Medicine Information System (NM-RIS), Radiopharmacy Information System (RPIS), แΰΈ₯ΰΈ° PACS ΰΈ—ΰΈ΅ΰΉˆΰΈ—ΰΈ²ΰΈ‡ΰΉ‚ΰΈ£ΰΈ‡ΰΈžΰΈ’ΰΈ²ΰΈšΰΈ²ΰΈ₯ΰΉƒΰΈŠΰΉ‰ΰΈ‡ΰΈ²ΰΈ™ΰΈ­ΰΈ’ΰΈΉΰΉˆ β€” covering TOR Β§3.15.7 (NM integration), Β§4.1.11–4.1.14 (imaging RIS/PACS), and NM-specific requirements for dose tracking, radiopharmaceutical dispensing, and safety compliance.

1.1 In scope

# Capability Protocol Direction
NM-01 NM imaging order β†’ hospital NM-RIS HL7v2 ORM^O01 + ZRI segment Outbound
NM-02 NM study result / interpretation β†’ NM-RIS HL7v2 ORU^R01 + OBX (SUV, uptake) Outbound
NM-03 Radiopharmaceutical dispense request β†’ RPIS HL7v2 RDE^O11 Outbound
NM-04 Radiopharmaceutical administration record β†’ RPIS HL7v2 RAS^O17 Outbound
NM-05 Inbound NM order status update ← NM-RIS HL7v2 ORM^O01 (status change) Inbound
NM-06 Inbound NM study result ← NM-RIS/PACS HL7v2 ORU^R01 Inbound
NM-07 Inbound radiopharmaceutical dispense confirmation ← RPIS HL7v2 RDS^O13 Inbound
NM-08 DICOM Modality Worklist (MWL) β€” populate NM scanner DICOM C-FIND SCU Outbound
NM-09 DICOM MPPS β€” scan completion notification DICOM N-CREATE/N-SET Inbound
NM-10 Dose accumulation report β†’ Dose Registry FHIR R4 Observation (deferred) Outbound
NM-11 Per-tenant NM integration config + audit log SQL + Admin UI β€”

1.2 Out of scope (deferred)

  • DICOMweb WADO-RS study retrieval (PACS viewer integration β€” separate PACS viewer module)
  • Cross-facility NM study transfer (TOR Β§4.1.15 β€” defer per RIS-Q7)
  • Cyclotron / generator supplier ordering API (supply chain, not clinical)
  • FHIR ImagingStudy + Observation reverse-mapper (post-HL7v2 stabilisation, same approach as RIS adapter Β§10)

2. Architecture

                        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                        β”‚  medOS Nuclear Medicine Subsystem                β”‚
                        β”‚                                                  β”‚
                        β”‚  imagingRequest (dept=NUCLEAR_MEDICINE)           β”‚
                        β”‚  nuclear_med_doses (Supabase)                    β”‚
                        β”‚  dose_safety_alerts (Supabase)                   β”‚
                        β”‚  radioisotope_library (Supabase)                 β”‚
                        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                                β”‚ Moleculer events
                                                β–Ό
                        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                        β”‚  nm-dispatcher.service.ts (NEW)                  β”‚
                        β”‚  Extends ris-dispatcher with NM-specific logic:  β”‚
                        β”‚   β€’ Radiopharmacy dose request (RDE^O11)         β”‚
                        β”‚   β€’ Dose administration event (RAS^O17)          β”‚
                        β”‚   β€’ NM-enriched ORM with ZRI segment             β”‚
                        β”‚   β€’ Dose accumulation push to registry           β”‚
                        β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                β”‚            β”‚             β”‚
                   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜            β”‚             └────────────────┐
                   β–Ό                         β–Ό                             β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ medos-to-orm-nm.mapper β”‚ β”‚ medos-to-rde.mapper   β”‚ β”‚ medos-to-ras.mapper     β”‚
    β”‚ (ORM^O01 + ZRI)        β”‚ β”‚ (RDE^O11 dispense)    β”‚ β”‚ (RAS^O17 admin record)  β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                β”‚                         β”‚                          β”‚
                β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              β–Ό
                β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                β”‚ mllp.client.ts (SHARED)  β”‚     ← from RIS adapter
                β”‚ Outbound MLLP TCP        β”‚
                β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                            β”‚ TCP
                β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                β”‚                                               β”‚
                β–Ό                                               β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ Hospital NM-RIS      β”‚                     β”‚ Radiopharmacy Info System  β”‚
    β”‚ (MLLP server)        β”‚                     β”‚ (RPIS β€” MLLP server)      β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

   Inbound flow (NM-RIS / RPIS β†’ medOS):

   NM-RIS / RPIS  ──ORM/ORU/RDS──▢  mllp.listener.ts (port 2575, SHARED)
                                                β”‚
                                                β–Ό
                                β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                                β”‚  hl7v2-parser.service.ts (SHARED)  β”‚
                                β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                               β–Ό
                                β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                                β”‚  Routing by message type + prefix: β”‚
                                β”‚  ORM + NM-prefix β†’ imagingRequest  β”‚
                                β”‚  ORU + NM-prefix β†’ nm result       β”‚
                                β”‚  RDS            β†’ dose dispense    β”‚
                                β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                               β–Ό
                                      Moleculer actions:
                                      imagingRequest.update
                                      nuclearMed.doseDispensed
                                      nuclearMed.resultFiled

   DICOM integration (scanner worklist + completion):

    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚  NM Scanner (PET/CT,     β”‚
    β”‚  SPECT/CT, Gamma Camera) β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
              β”‚ DICOM C-FIND (MWL query)
              β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚  dicom-mwl-scp.service.ts (NEW) β”‚
    β”‚  Serves scheduled NM worklist   β”‚
    β”‚  from imagingRequest entities   β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
              β”‚
              β”‚ DICOM N-CREATE / N-SET (MPPS)
              β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚  dicom-mpps-scp.service.ts (NEW)β”‚
    β”‚  Receives scan-started /        β”‚
    β”‚  scan-completed events β†’        β”‚
    β”‚  updates imagingRequest status  β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

3. Files to add / extend

3.1 HL7v2 NM-specific mappers

File Status LOC est. Purpose
services/interoperability/.../modules/hl7v2/mappers/medos-to-orm-nm.mapper.ts NEW ~250 mapNmRequestToOrm(req, dose, isotope): string β€” ORM^O01 with NM-specific ZRI custom segment (radioisotope, dose, route, decay-corrected activity)
services/interoperability/.../modules/hl7v2/mappers/medos-to-rde.mapper.ts NEW ~200 mapDoseToRde(dose: NuclearMedDose, isotope: Radioisotope): string β€” RDE^O11 radiopharmaceutical encoded order (RXE segment with isotope code, calibrated activity, calibration time, route)
services/interoperability/.../modules/hl7v2/mappers/medos-to-ras.mapper.ts NEW ~180 mapDoseAdminToRas(dose: NuclearMedDose): string β€” RAS^O17 administration record (RXA segment with administered dose, time, site, lot number)
services/interoperability/.../modules/hl7v2/mappers/medos-to-oru-nm.mapper.ts NEW ~200 mapNmResultToOru(req, result, measurements): string β€” ORU^R01 with NM-specific OBX segments (SUVmax, SUVmean, uptake ratio, ROI measurements)
services/interoperability/.../modules/hl7v2/mappers/rds-to-medos.mapper.ts NEW ~150 Inbound RDS^O13 β†’ nuclearMed.doseDispensed β€” dispense confirmation from RPIS (lot, calibrated activity, expiry, QC pass/fail)
services/interoperability/.../modules/hl7v2/mappers/orm-to-medos.mapper.ts EXTEND +40 Add NM-prefix routing: when placer order number matches NM-*, route to imagingRequest with dept=NUCLEAR_MEDICINE + populate dose metadata
services/interoperability/.../modules/hl7v2/mappers/oru-to-medos.mapper.ts EXTEND +60 NM result routing: parse quantitative OBX (SUV, uptake) into nuclear_med_doses.measured_dose + imagingRequest.result fields

3.2 NM dispatcher service

File Status LOC est. Purpose
services/interoperability/.../modules/nm-ris/nm-dispatcher.service.ts NEW ~300 Moleculer service extending RIS dispatcher pattern. Subscribes to: imagingRequest.created (dept=NM), nuclearMed.dosePrepared, nuclearMed.doseAdministered, nuclearMed.resultReported. Routes to appropriate mapper, sends via shared mllp.client, logs to nm_dispatch_log.
services/interoperability/.../modules/nm-ris/nm-ris.module.ts NEW ~30 Module registration
services/interoperability/.../modules/nm-ris/nm-ris-config.dto.ts NEW ~60 NM-RIS + RPIS endpoint config DTO (extends RIS config with RPIS-specific fields)

3.3 DICOM integration (MWL + MPPS)

File Status LOC est. Purpose
services/interoperability/.../modules/dicom/dicom-mwl-scp.service.ts NEW ~250 DICOM Modality Worklist SCP β€” responds to C-FIND from NM scanners with scheduled imagingRequest entries (patient demographics, study description, accession number, scheduled isotope/dose)
services/interoperability/.../modules/dicom/dicom-mpps-scp.service.ts NEW ~200 DICOM MPPS SCP β€” receives N-CREATE (scan started) and N-SET (scan completed) β†’ updates imagingRequest.status to in-progress / completed + records actual dose/duration
services/interoperability/.../modules/dicom/dicom.module.ts NEW ~30 Module registration
services/interoperability/.../modules/dicom/dicom-config.dto.ts NEW ~50 DICOM AE title, port, calling AE whitelist

3.4 Migrations

File Status LOC est. Purpose
infrastructure/medbase/migrations/<date>_nm_dispatch_log.sql NEW ~100 nm_dispatch_log β€” extends ris_dispatch_log pattern with NM-specific columns (radioisotope_code, dose_mbq, dose_round, lot_number)
infrastructure/medbase/migrations/<date>_nm_ris_config.sql NEW ~80 nm_ris_config β€” per-tenant NM-RIS + RPIS endpoint config (extends ris_config with RPIS host/port, DICOM AE title, MWL SCP port)
infrastructure/medbase/migrations/<date>_nm_integration_seeds.sql NEW ~120 Seed data: HL7v2 coding system mappings (radioisotope β†’ LOINC/SNOMED, NM procedure β†’ CPT/ICD-10-PCS), DICOM modality codes

3.5 Tests

File Status LOC est. Purpose
services/interoperability/.../modules/hl7v2/__tests__/medos-to-orm-nm.spec.ts NEW ~180 NM ORM mapper: ZRI segment, decay correction, multi-dose rounds
services/interoperability/.../modules/hl7v2/__tests__/medos-to-rde.spec.ts NEW ~150 RDE mapper: RXE segment, calibration time math, isotope coding
services/interoperability/.../modules/hl7v2/__tests__/medos-to-ras.spec.ts NEW ~120 RAS mapper: RXA segment, lot tracking, waste dose
services/interoperability/.../modules/hl7v2/__tests__/rds-to-medos.spec.ts NEW ~120 Inbound dispense confirmation parsing
services/interoperability/.../modules/nm-ris/__tests__/nm-dispatcher.spec.ts NEW ~180 End-to-end dispatch: event β†’ mapper β†’ MLLP mock β†’ log
services/interoperability/.../modules/dicom/__tests__/dicom-mwl-scp.spec.ts NEW ~150 MWL C-FIND response generation
services/interoperability/.../modules/dicom/__tests__/dicom-mpps-scp.spec.ts NEW ~120 MPPS N-CREATE/N-SET status updates

Total estimate: ~3,000 LOC including tests (NM-specific mappers + DICOM layer + dispatcher + migrations).


4. HL7v2 Message Templates

4.1 Outbound ORM^O01 β€” NM imaging order (with ZRI custom segment)

MSH|^~\&|MEDOS|{sending_facility}|{nm_ris_app}|{nm_ris_facility}|{timestamp}||ORM^O01|{msg_id}|P|2.5
PID|1||{patient.hn}^^^MEDOS^MR||{patient.last_name}^{patient.first_name}||{patient.dob}|{patient.gender}|||{patient.address}||{patient.phone}
PV1|1|{patient_class}|{encounter.location}||||{ordering_doctor.id}^{ordering_doctor.last_name}^{ordering_doctor.first_name}
AL1|1|DA|{allergy_code}^{allergy_name}|{severity}||{reaction}
ORC|NW|NM-{imagingRequest._id}|||SC|{priority}|||{request.created_at}|||{requester.id}^{requester.last_name}^{requester.first_name}
OBR|1|NM-{imagingRequest._id}||{procedure.code}^{procedure.name}^MEDOS|{priority}|{request.created_at}|||||A||{clinical_indication}||||{accession_number}
ZRI|1|{radioisotope.code}^{radioisotope.name}^SNOMED|{ordered_dose_mbq}|MBq|{administration_route.code}^{administration_route.name}|{calibration_datetime}|{decay_corrected_activity_mbq}|{dose_round}|{total_rounds}|{preparation_instructions}

ZRI segment definition (custom, medOS-to-NM-RIS):

Field Name Type Description
ZRI-1 Set ID SI Sequential (1 for single-dose, 1…N for multi-round)
ZRI-2 Radioisotope CWE SNOMED code + name (e.g., 349358000^Technetium-99m^SCT)
ZRI-3 Ordered Dose NM Activity in MBq at calibration time
ZRI-4 Dose Unit CWE MBq (default) or mCi
ZRI-5 Administration Route CWE IV, oral, inhaled, etc.
ZRI-6 Calibration DateTime DTM When the dose was calibrated
ZRI-7 Decay-Corrected Activity NM Activity at scheduled admin time (calculated)
ZRI-8 Dose Round NM Current round number (1-based)
ZRI-9 Total Rounds NM Total planned rounds for this study
ZRI-10 Preparation Instructions TX Free text (prep, fasting, hydration, etc.)

4.2 Outbound RDE^O11 β€” Radiopharmaceutical dispense request

MSH|^~\&|MEDOS|{sending_facility}|{rpis_app}|{rpis_facility}|{timestamp}||RDE^O11|{msg_id}|P|2.5
PID|1||{patient.hn}^^^MEDOS^MR||{patient.last_name}^{patient.first_name}||{patient.dob}|{patient.gender}|||{patient.address}
ORC|NW|NM-{imagingRequest._id}-DOSE-{dose_round}|||SC|||{request.created_at}|||{requester.id}
RXE|1|{radioisotope.code}^{radioisotope.name}^SNOMED|{ordered_dose_mbq}||MBq|{administration_route}|{calibration_datetime}|{scheduled_admin_time}||{special_instructions}
RXR|{route.code}^{route.name}||{site.code}^{site.name}

4.3 Outbound RAS^O17 β€” Dose administration record

MSH|^~\&|MEDOS|{sending_facility}|{rpis_app}|{rpis_facility}|{timestamp}||RAS^O17|{msg_id}|P|2.5
PID|1||{patient.hn}^^^MEDOS^MR||{patient.last_name}^{patient.first_name}||{patient.dob}|{patient.gender}
ORC|RE|NM-{imagingRequest._id}-DOSE-{dose_round}
RXA|1|1|{admin_start_time}|{admin_end_time}|{radioisotope.code}^{radioisotope.name}^SNOMED|{measured_dose_mbq}|MBq|{administration_route}|{admin_site}|{administering_provider.id}^{provider.last_name}^{provider.first_name}|{lot_number}|{expiry_date}|{manufacturer}
RXR|{route.code}^{route.name}||{site.code}^{site.name}
OBX|1|NM|WASTE-DOSE^Waste Dose^MEDOS||{waste_dose_mbq}|MBq|||||F
OBX|2|NM|RESIDUAL^Residual Activity^MEDOS||{residual_activity_mbq}|MBq|||||F

4.4 Outbound ORU^R01 β€” NM study result with quantitative measurements

MSH|^~\&|MEDOS|{sending_facility}|{nm_ris_app}|{nm_ris_facility}|{timestamp}||ORU^R01|{msg_id}|P|2.5
PID|1||{patient.hn}^^^MEDOS^MR||{patient.last_name}^{patient.first_name}||{patient.dob}|{patient.gender}
OBR|1|NM-{imagingRequest._id}||{procedure.code}^{procedure.name}^MEDOS||{request.created_at}|||||{result_status}||{clinical_indication}||||{accession_number}|||{result_status}
OBX|1|TX|FINDINGS^Findings^MEDOS||{report.findings}|||N|||F
OBX|2|TX|IMPRESSION^Impression^MEDOS||{report.impression}|||N|||F
OBX|3|NM|SUV-MAX^SUVmax^MEDOS||{suv_max}|{SUV}|||||F
OBX|4|NM|SUV-MEAN^SUVmean^MEDOS||{suv_mean}|{SUV}|||||F
OBX|5|NM|UPTAKE-RATIO^Uptake Ratio^MEDOS||{uptake_ratio}||||||F
OBX|6|NM|ROI-1^{roi_name}^MEDOS||{roi_measurement}|{roi_unit}|||||F
OBX|7|CWE|RADIOISOTOPE^Radioisotope Used^MEDOS||{radioisotope.code}^{radioisotope.name}^SNOMED||||||F
OBX|8|NM|ADMIN-DOSE^Administered Dose^MEDOS||{actual_dose_mbq}|MBq|||||F
OBX|9|DTM|ADMIN-TIME^Administration Time^MEDOS||{admin_datetime}||||||F
OBX|10|NM|SCAN-DURATION^Scan Duration^MEDOS||{duration_minutes}|min|||||F

4.5 Inbound RDS^O13 β€” Radiopharmaceutical dispense confirmation from RPIS

Expected from RPIS when the radiopharmacist has prepared and QC-passed the dose:

MSH|^~\&|{rpis_app}|{rpis_facility}|MEDOS|{medos_facility}|{timestamp}||RDS^O13|{msg_id}|P|2.5
PID|1||{patient.hn}^^^MEDOS^MR
ORC|RE|NM-{imagingRequest._id}-DOSE-{dose_round}|||CM
RXD|1|{radioisotope.code}^{radioisotope.name}^SNOMED|{dispensed_activity_mbq}|MBq|{calibration_time}|{lot_number}|{expiry}|{manufacturer}|{qc_result}

4.6 Cancel control

Reuse ORM^O01 with ORC|CA|NM-{imagingRequest._id}... β€” same as RIS adapter Β§4.3, but with placer order number prefix NM-.


5. Configuration Model

-- Extends ris_config with NM-specific endpoints
CREATE TABLE IF NOT EXISTS nm_ris_config (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  tenant_id UUID NOT NULL,
  
  -- NM-RIS endpoint (may be same as general RIS or separate)
  nm_ris_enabled BOOLEAN NOT NULL DEFAULT FALSE,
  nm_ris_host TEXT,
  nm_ris_port INT DEFAULT 2575,
  nm_ris_sending_app TEXT NOT NULL DEFAULT 'MEDOS',
  nm_ris_sending_facility TEXT,
  nm_ris_receiving_app TEXT,
  nm_ris_receiving_facility TEXT,
  
  -- Radiopharmacy Information System (RPIS) endpoint
  rpis_enabled BOOLEAN NOT NULL DEFAULT FALSE,
  rpis_host TEXT,
  rpis_port INT DEFAULT 2576,
  rpis_sending_app TEXT NOT NULL DEFAULT 'MEDOS',
  rpis_sending_facility TEXT,
  rpis_receiving_app TEXT,
  rpis_receiving_facility TEXT,
  
  -- DICOM Modality Worklist SCP
  mwl_enabled BOOLEAN NOT NULL DEFAULT FALSE,
  mwl_ae_title TEXT NOT NULL DEFAULT 'MEDOS_NM_MWL',
  mwl_port INT DEFAULT 4242,
  mwl_allowed_calling_aets TEXT[] DEFAULT '{}',   -- whitelist of scanner AE titles
  
  -- DICOM MPPS SCP
  mpps_enabled BOOLEAN NOT NULL DEFAULT FALSE,
  mpps_ae_title TEXT NOT NULL DEFAULT 'MEDOS_NM_MPPS',
  mpps_port INT DEFAULT 4243,
  
  -- Dose tracking
  dose_unit_preference TEXT NOT NULL DEFAULT 'MBq' CHECK (dose_unit_preference IN ('MBq','mCi')),
  auto_decay_correction BOOLEAN NOT NULL DEFAULT TRUE,
  send_dose_to_registry BOOLEAN NOT NULL DEFAULT FALSE,
  dose_registry_endpoint TEXT,                     -- FHIR endpoint for national dose registry
  
  -- MLLP shared settings
  mllp_timeout_ms INT NOT NULL DEFAULT 30000,
  retry_attempts INT NOT NULL DEFAULT 3,
  retry_backoff_ms INT NOT NULL DEFAULT 1000,
  encoding TEXT NOT NULL DEFAULT 'UTF-8',
  hl7_version TEXT NOT NULL DEFAULT '2.5',
  
  created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
  updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
  UNIQUE(tenant_id)
);

COMMENT ON TABLE nm_ris_config IS 
  'Per-tenant Nuclear Medicine integration config β€” NM-RIS, RPIS, DICOM MWL/MPPS, dose registry';

Admin UI: /admin/integrations/nm-ris-config β€” extends the RIS config page with NM-specific tabs.


6. Dispatch Log

-- NM-specific dispatch log (extends ris_dispatch_log pattern)
CREATE TABLE IF NOT EXISTS nm_dispatch_log (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  direction TEXT NOT NULL CHECK (direction IN ('outbound','inbound')),
  target_system TEXT NOT NULL CHECK (target_system IN ('nm_ris','rpis','dicom_mwl','dicom_mpps','dose_registry')),
  imaging_request_id TEXT,               -- mongo _id reference
  dose_id UUID,                          -- FK to nuclear_med_doses.id
  message_type TEXT NOT NULL,            -- 'ORM^O01', 'ORU^R01', 'RDE^O11', 'RAS^O17', 'RDS^O13', 'C-FIND', 'N-CREATE', 'N-SET'
  message_control_id TEXT NOT NULL,      -- MSH-10 (HL7v2) or DICOM message ID
  raw_message TEXT,                      -- HL7v2 string or DICOM dataset summary
  
  -- NM-specific audit fields
  radioisotope_code TEXT,                -- SNOMED code of radioisotope involved
  dose_mbq NUMERIC(12,4),               -- dose activity at time of message
  dose_round INT,                        -- which round (1-based)
  lot_number TEXT,                       -- radiopharmaceutical lot (from dispense)
  qc_result TEXT CHECK (qc_result IN ('pass','fail','pending',NULL)),
  
  status TEXT NOT NULL CHECK (status IN ('pending','sent','acked','naked','timeout','error')),
  ack_code TEXT,                         -- AA / AE / AR
  error_message TEXT,
  attempt INT NOT NULL DEFAULT 1,
  created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
  responded_at TIMESTAMPTZ
);

CREATE INDEX idx_nm_dispatch_log_imaging_request ON nm_dispatch_log (imaging_request_id);
CREATE INDEX idx_nm_dispatch_log_dose ON nm_dispatch_log (dose_id);
CREATE INDEX idx_nm_dispatch_log_status_pending ON nm_dispatch_log (status) WHERE status IN ('pending','error');
CREATE INDEX idx_nm_dispatch_log_target ON nm_dispatch_log (target_system, created_at DESC);

7. State Machines

7.1 NM Order Dispatch (ORM)

NM imagingRequest created (dept=NUCLEAR_MEDICINE)
        β”‚
        β”œβ”€β”€ nm_dispatch_log row (target=nm_ris, type=ORM^O01, status=pending)
        β”‚
        β–Ό
nm-dispatcher composes ORM^O01 + ZRI via medos-to-orm-nm.mapper
        β”‚
        β–Ό
Send via mllp.client β†’ NM-RIS
        β”‚
        β”œβ”€β”€[ACK AA]──▢ status=acked β†’ imagingRequest.status='accepted'
        β”œβ”€β”€[ACK AE]──▢ status=naked β†’ log error, manual review
        β”œβ”€β”€[timeout]──▢ retry up to N β†’ status=timeout
        └──[exception]─▢ status=error

7.2 Radiopharmacy Workflow

NM imagingRequest accepted
        β”‚
        β–Ό
nm-dispatcher sends RDE^O11 to RPIS (dispense request)
        β”‚
        β”œβ”€β”€ nm_dispatch_log row (target=rpis, type=RDE^O11)
        β”‚
        β–Ό
RPIS prepares radiopharmaceutical + QC
        β”‚
        β–Ό
RPIS sends RDS^O13 to medOS (dispense confirmation)
        β”‚
        β–Ό
rds-to-medos.mapper parses β†’ nuclearMed.doseDispensed action
        β”‚
        β”œβ”€β”€ Updates nuclear_med_doses: status='dispensed', lot_number, qc_result, calibrated_activity
        β”œβ”€β”€ Updates dose_safety_alerts if accumulated dose nears limit
        β”‚
        β–Ό
Dose administered by technologist (recorded in medOS)
        β”‚
        β–Ό
nm-dispatcher sends RAS^O17 to RPIS (administration record)
        β”‚
        β”œβ”€β”€ nuclear_med_doses.status='administered'
        β”œβ”€β”€ patient_dose_accumulations updated (via existing Postgres trigger)
        └── dose_safety_alerts evaluated (via existing trigger)

7.3 DICOM Scanner Integration

NM imagingRequest scheduled (status='scheduled')
        β”‚
        β–Ό
NM Scanner sends DICOM C-FIND (MWL query)
        β”‚
        β–Ό
dicom-mwl-scp responds with matching worklist entries
  (patient demographics, study, accession, isotope, dose)
        β”‚
        β–Ό
Technologist selects study on scanner console
        β”‚
        β–Ό
Scanner sends N-CREATE (MPPS: scan started)
        β”‚
        β–Ό
dicom-mpps-scp β†’ imagingRequest.status='in-progress'
        β”‚
        β–Ό
Scan completes
        β”‚
        β–Ό
Scanner sends N-SET (MPPS: scan completed, actual dose, duration)
        β”‚
        β–Ό
dicom-mpps-scp β†’ imagingRequest.status='completed'
        β”‚
        β”œβ”€β”€ Updates nuclear_med_doses: actual scan duration, post-admin residual
        └── nm_dispatch_log row (target=dicom_mpps, type=N-SET, status=acked)

8. Radioisotope Coding System Mapping

For HL7v2 interoperability, medOS maps its internal radioisotope_library to standard coding systems:

medOS Radioisotope SNOMED CT LOINC (dose observation) Half-life Common Procedures
Tc-99m 349358000 10419-2 6.01 h Bone scan, thyroid, renal, cardiac SPECT, DMSA, DTPA, MAG3, HIDA, lung V/Q
I-131 55750003 10420-0 8.02 d Thyroid therapy, thyroid uptake, MIBG therapy
I-123 70076005 10421-8 13.22 h Thyroid imaging, MIBG diagnostic
F-18 FDG 422855000 10422-6 109.77 min PET/CT oncology, cardiac PET, brain PET
Ga-68 423413002 10423-4 67.71 min DOTATATE PET/CT (neuroendocrine), PSMA PET/CT (prostate)
Tl-201 14673006 10424-2 72.91 h Myocardial perfusion (legacy)
In-111 422562002 10425-9 67.32 h WBC scan (infection), octreotide scan
Lu-177 423688004 β€” 6.65 d DOTATATE therapy, PSMA therapy
Ra-223 β€” β€” 11.43 d Bone metastases therapy (Xofigo)
Y-90 423316001 β€” 64.05 h Radioembolization (SIRT), radiosynovectomy

These mappings are seeded in nm_integration_seeds.sql and used by the ORM/ORU/RDE/RAS mappers for the CWE-typed radioisotope fields.


9. Dose Safety Integration Points

The NM integration layer connects to the existing dose safety system (nuclear_med_doses β†’ patient_dose_accumulations β†’ dose_safety_alerts β†’ acknowledgement_requests):

Event Source Integration Action
Dose dispensed by RPIS Inbound RDS^O13 Update nuclear_med_doses.status='dispensed', calibrated_activity, lot_number; triggers accumulation recalc
Dose administered Outbound RAS^O17 nuclear_med_doses.status='administered'; Postgres trigger fires β†’ patient_dose_accumulations updated β†’ dose_safety_alerts evaluated
Critical safety alert dose_safety_alerts Postgres trigger acknowledgement_requests row inserted (template: nuclear_medicine.safety.alert_critical) β†’ AcknowledgementInbox FAB notifies radiopharmacist
Scan completed Inbound DICOM MPPS N-SET Update nuclear_med_doses with actual scan parameters; residual activity logged
Dose registry push patient_dose_accumulations threshold If send_dose_to_registry=true, push FHIR Observation to configured registry endpoint

10. Open Questions

# Question When Default if no answer
NM-Q1 Does the hospital run a separate NM-RIS or is NM handled within the general RIS? Pre-impl Assume separate; if same, NM-RIS config points to same endpoint as ris_config with NM- prefix routing
NM-Q2 Does the hospital have a standalone Radiopharmacy Information System (RPIS)? Pre-impl If no RPIS, skip RDE/RAS/RDS messages; dose tracking stays internal to medOS
NM-Q3 DICOM library choice: dcmjs (pure JS) vs fo-dicom (C# via edge) vs pynetdicom (Python sidecar)? Stage-04 dcmjs for MWL/MPPS SCP β€” keeps everything in Node.js, same runtime as interoperability service
NM-Q4 Custom ZRI segment vs OBR-31 (reason for study) for radioisotope/dose fields? Mapper impl ZRI custom segment (more structured); OBR-31 is free-text and loses structured data. Document in conformance profile.
NM-Q5 Should decay correction use calibration time from RPIS (RDS) or from medOS nuclear_med_doses.calibration_time? Mapper impl Prefer RPIS-provided calibration time (more accurate); fall back to medOS if RPIS not connected
NM-Q6 Dose unit: always MBq or configurable per-tenant (MBq vs mCi)? Config Configurable via nm_ris_config.dose_unit_preference; mappers convert using 1 mCi = 37 MBq
NM-Q7 National dose registry format: FHIR Observation or proprietary? Deferred FHIR R4 Observation with radiation-dose profile; implementation deferred until a specific registry is identified

11. Dependency on RIS Adapter

The NM integration reuses these components from the general RIS adapter (docs/architecture/ris-adapter-design.md):

Component Reuse Type Notes
mllp.client.ts Direct reuse Same outbound MLLP TCP client; NM dispatcher passes NM-RIS/RPIS endpoints
mllp.listener.ts Direct reuse Same inbound listener on port 2575; message routing extended for RDS^O13
hl7v2-parser.service.ts Direct reuse Parser is message-type-agnostic
hl7v2-ack.builder.ts Direct reuse ACK/NAK format is the same
ris_dispatch_log pattern Pattern reuse NM has its own nm_dispatch_log with extra columns but same lifecycle
ris_config pattern Pattern reuse NM has its own nm_ris_config with RPIS + DICOM fields
Order number prefix routing Extend NM- prefix in placer order number distinguishes NM from general imaging (IMG-/RAD-)

Implementation order: RIS adapter first (shared transport), then NM integration (NM-specific mappers + RPIS + DICOM).


12. Module Loop Entry Point

# In a fresh Claude session (after RIS adapter loop completes):
"Run the medOS module-implementation loop for the **nm-ris-integration** module
in interactive mode. Read docs/module-loop/00-MASTER.md and
docs/architecture/nuclear-medicine-ris-pacs-integration.md.

Pre-flight:
- Branch: stay on current branch.
- State file: docs/nm-ris-integration-loop/_progress.json
- Spec source: docs/architecture/nuclear-medicine-ris-pacs-integration.md
- Prerequisite: RIS adapter (docs/ris-adapter-loop/) must be complete β€”
  mllp.client.ts + ris-dispatcher exist and pass tests.

Stage 01 should audit:
  1. The shared HL7v2 module (services/interoperability/.../modules/hl7v2/)
  2. The existing NM data models (nuclear_med_doses, radioisotope_library, etc.)
  3. The RIS adapter output (mllp.client, ris-dispatcher)
Then proceed through stages 02-10 per the module loop framework.
"

Estimated effort: 2–3 days (larger than the RIS adapter due to RPIS messages + DICOM MWL/MPPS layer).


13. Cross-References

Ask Anything