medOS ultra

Blood Bank Test Result Entry (3.3.4)

Recording lab test results by specified test item in the blood bank.

15 min read diagramsUpdated 2026-04-28docs/architecture/blood-bank-test-result-entry-3.3.4.md

บันทึกผลตรวจชันสูตรโรคแบบระบุรายการตรวจ (ธนาคารเลือด)

Source: EPHIS Manual §3.3.4 + Vajira screenshots (search form + result-entry form). Master spec: blood-bank-dispense-spec.md — refer there for the full state machine, lane definitions, TORs, and TS types. Binds to states: S17_COMPAT_SYS, S19_BLOOD_TEST_NEW, S20_BLOOD_GROUP, S21_BLOOD_TEST_HIST, transition into S22_MERGE. Binds to TORs: TOR 4.3.7 (LIS integration — this screen is the manual-entry surface for what 4.3.7 promises to automate). Lane: Officer (O) — specifically the lab-tech persona.


1. Purpose

The lab-result entry screen for blood-bank tests. After a doctor places a test order via §3.3.3 (Test Order, “บันทึกขอตรวจชันสูตรโรค”), the lab tech runs the wet-lab tests, then comes here to record each individual result.

This screen is the data-input boundary between the wet-lab world and the EPHIS state machine — every result entered here advances a specimen toward S22_MERGE (compatibility-check merge) and ultimately gates S23_CROSS_MATCH.

It is not the same screen as §3.3.5 (บันทึกผลตรวจเลือด/จ่ายเลือดผู้ป่วยขอเลือด). §3.3.4 records individual test items (e.g. just “Rh Type”). §3.3.5 is the consolidated crossmatch + dispense screen (image 1 from the Vajira photos). Both screens write into the same compatibility/result data model — see §6 below.


2. Position in workflow

3.3.3 Test Order (Doctor)               creates row, status = รอสิ่งส่งตรวจ
       │
       ▼
3.3.4 Test Result Entry (Lab Tech)      ← THIS SCREEN — writes ผลตรวจ per item
       │
       ▼
3.3.5 Crossmatch + Dispense (Officer)   consumes results, drives S23_CROSS_MATCH

3. UI structure

The screen has two regions in one view: a search/filter header that scopes which orders are shown, and a multi-row results table where data entry happens.

3.1 Search / filter header

Field Thai Notes
HN HN Patient hospital number
AN AN Admission number
LN LN Lab Number — auto-generated by EPHIS at order time; the canonical key for a single lab order. medOS code does not yet model an LN — see Implementation Gap §11.
สถานะสิ่งส่งตรวจ Specimen status Lifecycle filter (e.g. รอสิ่งส่งตรวจ = waiting for specimen, before any results recorded). Not modeled in code — see Implementation Gap §11.
วันที่ส่งตรวจ Test date range from / to
เวลาที่ส่งตรวจ Test time range from / to (default visible: 08:00–16:30 — implies a working-day filter)
หน่วยงานส่งตรวจ Sending unit Department that ordered
ประเภทงาน Work type Pinned to ธนาคารเลือด in EPHIS — would be the routing field for a shared lab system. medOS has no shared lab-order infrastructure yet (blood-bank is a standalone vertical); see Implementation Gap §11.
รายการตรวจ Test item Specific test type (e.g. RH TYPE, SCREENING)

The ประเภทงาน = ธนาคารเลือด pin is an EPHIS architectural confirmation: EPHIS uses one shared lab-order system, and the blood bank is just one routing target. medOS does not currently mirror this — blood-bank result entry is implemented as a dedicated module with no work-type routing.

3.2 Results table (body)

Multi-row in EPHIS; each row = one test item on one specimen. EPHIS supports batch save via row checkboxes. medOS today saves per-row only (see §5).

Column Thai Notes
(select) Row checkbox for batch save (EPHIS only)
วันที่ส่งตรวจ Test date Inherited from order
ชื่อ-สกุล Patient name
HN HN
รายการตรวจ Test item E.g. RH TYPE, SCREENING, etc.
ผลตรวจ Result EPHIS: dropdown with shared codes. medOS: free-text / Autocomplete (see §4).
ปกติ Normal? EPHIS-only computed flag. Likely not applicable to blood-bank (categorical, not quantitative tests) — see Implementation Gap §11.
สถานะสิ่งส่งตรวจ Specimen status Updates as rows are saved (EPHIS). Not modeled in medOS.
หน่วยงานส่งตรวจ Sending unit Inherited from order
Field Thai Notes
ผู้รายงานผล Result reporter EPHIS: lab tech who entered the result. medOS: entered_by (from migration 031).
ผู้ตรวจสอบผล Result verifier EPHIS: reviewer (second-eye check). medOS: verified_by (from migration 031).
(lab director approval) Not visible in EPHIS screenshots, but medOS adds a third role approved_by for ISO 15189 §5.9.1 — see §7.2.
วันที่-เวลา Datetime One per role
ราคา Price Per-test pricing (EPHIS). medOS: not modeled at result entry — see §7.1.
เบิกได้ Claimable amount Insurance/reimbursable portion (EPHIS). medOS: not modeled at result entry.
ผลตรวจ Result (footer text) Free-text or final consolidated result

Pagination indicator: 1/3 visible — the EPHIS list paginates. Important for code (don’t assume single-page).


4. Result enums — EPHIS observation vs medOS reality

EPHIS: For test item RH TYPE, the dropdown shows three values with system codes:

Code Value
5 NEGATIVE
6 POSITIVE
7 WEAKLY POSITIVE

The codes are non-sequential and not 0-indexed — they read like identifiers in a global lab-result enum table, not test-specific.

medOS today: Result values are stored as free-text or string literals, not FK-constrained enums:

Gap: Adding a blood_result_codes reference table would make the dropdowns enforceable, support i18n, and reduce typos in audit reports. Captured as Implementation Gap G2 in §11.


5. Workflow (per image annotations)

Three numbered steps shown on the second screenshot:

  1. เลือกรายการที่ต้องการ — Select the row(s) to enter results for (checkbox, often multiple)
  2. เลือกผลตรวจเลือด — Pick the result value from the dropdown for each selected row
  3. กดเพื่อบันทึกข้อมูล — Click save (toolbar save icon, top-left)

EPHIS supports batch save. medOS code today saves per-rowBloodTestResultsModule.handleSave() calls saveBloodTestResult(payload) synchronously for one row, with status: 'final' hardcoded on save. The screenshot’s row checkboxes are not yet wired in medOS.


6. State machine binding (back-reference to master)

Per-row mapping

Each row in the results table represents progress on one of the compatibility-check sub-states from master §3:

Test item value Master state
RH TYPE (when no prior history) S19_BLOOD_TEST_NEW (or S20_BLOOD_GROUP for ABO)
RH TYPE (when prior history exists) S21_BLOOD_TEST_HIST
SCREENING Part of S17_COMPAT_SYS workup
ABO GROUPING S20_BLOOD_GROUP
(other items) All within S17_COMPAT_SYS

The S18_HIST_DEC decision (prior history yes/no) is set at order time on §3.3.3 (ประวัติหมู่เลือด field), not here. This screen receives whichever path was chosen.

Specimen status lifecycle

The สถานะสิ่งส่งตรวจ column tracks one specimen through its lab journey:

รอสิ่งส่งตรวจ            ← order placed in 3.3.3, before any results
   │
   ▼ (results being entered here, 3.3.4)
(intermediate states — see open question Q1 below)
   │
   ▼ (all required items recorded)
S22_MERGE → S23_CROSS_MATCH → onward to 3.3.5

This is a distinct status enum from the bag-disposition status of TOR 4.3.6 (พร้อมใช้งาน / รอการกำจัด / รอจ่าย / จ่ายแล้ว). Two enums need to coexist in the data model — do not collapse them. medOS today only models bag/inventory status — specimen status is a known gap (G3 in §11).


7. Implications for the master spec (deltas to consider for v0.5)

These are real differences from master v0.4 that this screen reveals:

  1. Pricing-at-entry is unimplemented; billing currently disconnected from dispense. EPHIS captures ราคา and เบิกได้ during result entry, implying lab work has billable units distinct from the dispense event. medOS today has no price/claimable columns on either result table, and /blood-finance reads blood_bank_demo_lab_orders (a separate demo table) which is disconnected from BloodCrossMatchDispense.saveCrossmatchRow writes (see master Q11). The Finance flow (S40 / S41) should receive two streams of billable events (one per test, one per dispense) but currently receives neither. Capture as Implementation Gap G4 in §11.

  2. 3-role audit chain on result entry — already richer than EPHIS shows. EPHIS screenshots only show ผู้รายงานผล + ผู้ตรวจสอบผล. medOS migration 031_blood_lis_provenance.sql implements an ISO 15189 §5.9.1 three-role chain: entered_byverified_byapproved_by, enforced by SQL constraints:

    • blr_approved_requires_verified — approval requires prior verification, with verified_at <= approved_at
    • Optional blr_separate_verifier — verifier ≠ enterer (per-tenant flippable via blood_bank_config)
    • blr_external_requires_metadata — external-lab rows need external_lab_name + external_report_ref
    • blr_manual_requires_kit — bench/rapid-kit entries need kit_lot + kit_expiry + reader_id

    The migration also adds result_source enum (lis_integrated / external_report / manual_entry / unknown_legacy), lab_batch_id, and attachment_refs jsonb. This means audit-trail design is per-screen-and-per-source, not uniform — code already models this; spec needs to catch up.

  3. Specimen status enum is a known schema gap. The สถานะสิ่งส่งตรวจ lifecycle is observable from EPHIS but never enumerated in master §6 / §7, and not modeled in medOS at all. medOS has bag/inventory status (waiting_lab, crossmatch_pending, dispensed, …) but no specimen-state column on result tables. Add a SpecimenStatus enum to the TS types in master §7, alongside InventoryStatus, and a blood_specimens table (or columns on the order/result tables) to track it.

  4. ประเภทงาน is the routing field for the shared lab system — aspirational in medOS. Master §1 mentions the architectural insight that S1–S5 live in encounter context. EPHIS’s routing mechanism is a single field on the shared lab-order entity. medOS has no shared lab-order infrastructure — blood-bank is a standalone vertical, no specialty_code / work_type field exists. Document this in master’s “Code organization” note (planned for §11 in v0.5) as a future-state rec, not a current-state observation.

  5. Amend / correction flow is implemented and stricter than EPHIS. Migration 033_blood_lab_batching_amend.sql introduces:

    • blood_lis_results_amends versioned-edit log (before/after JSONB snapshots, retention floor 15 yrs)
    • amend_blood_lis_result(...) RPC — the only sanctioned way to modify a finalised row
    • fn_blood_lis_block_finalised_update trigger — direct UPDATE on approved_at IS NOT NULL rows is blocked
    • Reason text required, ≥8 chars; reason_code structured enum (transcription / instrument_correction / reanalysis / other)

    This answers F5 (formerly open) — amend semantics are append-only with second-approver gate, never overwrite.

  6. Lab batches as a first-class concept. Migration 033 also introduces blood_lab_batches (24/48/96-well plates) with FK from blood_lis_results.lab_batch_id. The “Batch view” toggle in §3.1 should resolve to v_blood_lab_batches_rollup (provides bag_count, pending_verify, pending_approve, disqualified_count per batch). Not in the current spec — add to §10.


8. Open questions specific to this feature

ID Question Status
F1 Between รอสิ่งส่งตรวจ (waiting) and the merge into S22, what intermediate สถานะสิ่งส่งตรวจ values exist? 🔴 OPEN — and now upgraded from “need enum dump” to schema gap (G3 in §11). Need to design the enum, not just observe EPHIS’s.
F2 Are some test items mandatory before S22_MERGE allows progression, or is the merge per-row? 🔴 OPEN — Probable: a checklist of required items per request type; merge waits for all.
F3 How does ราคา / เบิกได้ per test relate to the cashier flow S40_RECV_BILLING / S41_CALC_COST? 🟡 PARTIAL — Confirmed as a separate billing stream conceptually, but unwired in medOS (G4 in §11).
F4 Does saving multiple rows (batch) advance specimen status atomically, or per-row? 🟡 N/A in medOS today — per-row saves only; revisit when batch UI is added.
F5 What happens if a result is later corrected? Is there an amend flow with audit, or just overwrite? ✅ ANSWERED by migration 033 — append-only amend log, RPC-gated, second-approver required, ≥8-char reason, structured reason_code, 15-yr retention. Direct UPDATE on approved rows blocked by trigger.
F6 Where does LN (Lab Number) get minted? 🔴 OPEN / NEW — No lab_number column or sequence exists in medOS. EPHIS auto-generates at order time; we need to decide on format (per-day prefix? per-tenant? global-sequence service?) and add it as the cross-reference key between order/result/specimen/charge tables.
F7 When result_source = 'lis_integrated', who fills entered_by? 🟡 NEW — Likely a service-account user representing the LIS bridge. Spec needs to define the convention.

9. Cross-references

  • Master state machine: blood-bank-dispense-spec.md §3
  • Master TORs: blood-bank-dispense-spec.md §6 — especially TOR 4.3.7
  • Sister features (planned):
    • 3.3.1 — Patient Queue (the Officer worklist; image 7 in our photo set)
    • 3.3.2 — Request / Allergy / Return (3-tab dialog; collapses S8 → S35 + S31 UI)
    • 3.3.3 — Test Order (creates the rows that this screen consumes)
    • 3.3.5 — Crossmatch + Dispense (consumes the results saved here)
    • 3.3.9 — Release blood from discharged patient (the ปลด state, distinct from S31_RETURN)
    • 3.3.10 — Bedside nurse pickup (S6_RECV_BLOOD interface)
  • Migrations:
  • UI: BloodTestResultsModule.tsx — current per-row entry surface

10. Implementation hints for code

If duplicating EPHIS for Vajira (and aligning with what migrations 031/033 already provide):

  • The order entity (created in §3.3.3) and the result entity (written by this screen) should be separate tables keyed by LN. Today only result tables exist (blood_test_results for legacy/manual, blood_lis_results for the provenance-aware path); a dedicated order entity is a gap (G1 in §11).
  • Each result row has — per migration 031: id uuid, bag_number, donation_date, panel results (nat_qualified, sero_qualified, antibody_qualified, blood_group, antibody_screen), result_source, entered_by/at, verified_by/at, approved_by/at, lab_batch_id, attachment_refs jsonb, plus source-specific metadata: external_lab_name, external_report_ref (for external-lab rows); kit_lot, kit_expiry, reader_id (for manual/bench rows).
  • The shared lab-order infrastructure is future state — not present today. Treat the React result-entry component as blood-bank-specific until a specialty_code / work-type field exists. Generic-by-ประเภทงาน is a v2 refactor target, not a v1 implementation hint.
  • The ผลตรวจ dropdown options must be loaded based on the row’s รายการตรวจ — i.e. context-dependent dropdown population, not a static list. Today this is hardcoded in BloodTestResultsModule; migrating to a blood_result_codes reference table (G2) would let the UI fetch valid options per test_item_code.
  • Do not copy EPHIS’s “ปกติ” (normal?) column for blood-bank tests. Blood-bank panels are categorical (compatible / incompatible, Rh+/Rh−), not quantitative — there is no “normal range” to compare against. The column is a chemistry/hematology concept that doesn’t apply here.
  • For amend / correction flows, use the amend_blood_lis_result RPC (migration 033) — never UPDATE the row directly. The trigger trg_block_finalised_update will reject direct edits to approved rows. The RPC requires: p_changes JSONB, p_reason (≥8 chars), p_reason_code (transcription | instrument_correction | reanalysis | other), p_approved_by (second-approver UUID).
  • For the “Verify queue” UI, use the view v_blood_lis_pending_verification (migration 031) — already filters to rows entered but not yet verified.
  • For batch / plate-view UI, use the view v_blood_lab_batches_rollup (migration 033) — gives per-batch counts of pending verify/approve and disqualified rows.
  • ISO 15189 separate-verifier enforcement is per-tenant, controlled by blood_bank_config (migration 032). Read the flag before deciding whether to allow entered_by == verified_by.

11. Implementation gaps (medOS code vs EPHIS spec)

Snapshot as of 2026-04-25, validated against branch main. Ordered by impact on the §3.3.4 user journey.

ID Gap Severity Fix sketch
G1 No order entity. §3.3.3 creates the row this screen consumes; today only result tables exist. blood_bank_demo_lab_orders is a UAT fallback, not a canonical order entity. 🔴 High Create blood_lab_orders (HN/AN, ordering doctor, requested test items, urgency, history-flag). Link blood_lis_results via order_id and lab_number.
G2 Result values are free-text. No blood_result_codes reference table; UI Autocompletes don’t enforce. 🟡 Medium Add blood_result_codes(code, test_item_code, label_th, label_en, sort_order, is_normal). Migrate existing free-text values; switch UI to FK-constrained dropdowns.
G3 No specimen-status enum. สถานะสิ่งส่งตรวจ lifecycle is invisible in code; only bag/inventory status exists. 🔴 High Add specimen_status enum (waiting, received, in_progress, partial_results, complete, rejected) and a specimen_id FK from result rows. Drives the §3.1 status filter and the §S22 merge readiness check.
G4 Pricing-at-entry not modeled; billing disconnected. No price / claimable_amount on result tables. /blood-finance reads a separate demo table. 🟡 Medium Add a blood_lab_charges table keyed by result_id, or columns on the result rows. Wire S28_RECORD_DISPS40_RECV_BILLING and add a parallel S17_RECORD_TESTS40 stream for lab charges. (Master Q11.)
G5 No lab_number (LN) anywhere. EPHIS’s canonical key is absent. 🟡 Medium Decide on minting strategy (global-sequence service? BB-YYYYMMDD-XXXX per-day prefix?) and add lab_number UNIQUE on the order entity. Use as cross-reference key for order/result/specimen/charge tables.
G6 Per-row save only. No batch checkbox UI; status hardcoded to 'final' on save. 🟢 Low Wire BloodTestResultsModule row checkboxes to a batch save handler; let user choose draft vs final per row.
G7 No work-type routing. Blood-bank result-entry UI is bespoke; cannot be reused for chemistry / microbiology / etc. 🟢 Low Long-term: refactor to a generic lab-result component parameterised by specialty_code. Not blocking §3.3.4 v1.
G8 ปกติ (normal?) column. EPHIS has it; medOS doesn’t. ⚪ N/A Skip — not applicable to categorical blood-bank tests. Documented in §10 to prevent future regressions.

Change Log

  • v0.2 (2026-04-25) — Validated against branch main. Edits: (a) §4 reframed from “FK enum” assertion to gap; (b) §7.1 pricing claim corrected — billing is disconnected, not richer; © §7.2 audit upgraded to ISO 15189 3-role per migration 031; (d) §7 new items #5 (amend RPC) and #6 (lab batches) added from migrations 033; (e) §8 F5 closed by migration 033, F6 + F7 added; (f) §10 hints rewritten with concrete column lists from migration 031 and explicit “do not copy ปกติ” guidance; (g) new §11 “Implementation gaps” table consolidates G1–G8.
  • v0.1 — Initial drill-down created from EPHIS Manual §3.3.4 + Vajira screenshots (search form + result-entry form).
Ask Anything