Blood Bank Test Result Entry (3.3.4)
Recording lab test results by specified test item in the blood bank.
บันทึกผลตรวจชันสูตรโรคแบบระบุรายการตรวจ (ธนาคารเลือด)
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 |
3.3 Footer / audit fields
| 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:
blood_test_results.rh_result—TEXT('positive' | 'negative' | '')blood_test_results.screening_ab1_result—TEXT; UI offers Autocomplete with Negative / Positive / Weak Positive / Indeterminate / ไม่ทำ but does not enforceblood_lis_results.blood_group—VARCHAR(10)- No
lab_result_codes/result_code_enumreference table exists.
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:
- เลือกรายการที่ต้องการ — Select the row(s) to enter results for (checkbox, often multiple)
- เลือกผลตรวจเลือด — Pick the result value from the dropdown for each selected row
- กดเพื่อบันทึกข้อมูล — Click save (toolbar save icon, top-left)
EPHIS supports batch save. medOS code today saves per-row — BloodTestResultsModule.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:
-
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-financereadsblood_bank_demo_lab_orders(a separate demo table) which is disconnected fromBloodCrossMatchDispense.saveCrossmatchRowwrites (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. -
3-role audit chain on result entry — already richer than EPHIS shows. EPHIS screenshots only show
ผู้รายงานผล+ผู้ตรวจสอบผล. medOS migration031_blood_lis_provenance.sqlimplements an ISO 15189 §5.9.1 three-role chain:entered_by→verified_by→approved_by, enforced by SQL constraints:blr_approved_requires_verified— approval requires prior verification, withverified_at <= approved_at- Optional
blr_separate_verifier— verifier ≠ enterer (per-tenant flippable viablood_bank_config) blr_external_requires_metadata— external-lab rows needexternal_lab_name+external_report_refblr_manual_requires_kit— bench/rapid-kit entries needkit_lot+kit_expiry+reader_id
The migration also adds
result_sourceenum (lis_integrated/external_report/manual_entry/unknown_legacy),lab_batch_id, andattachment_refs jsonb. This means audit-trail design is per-screen-and-per-source, not uniform — code already models this; spec needs to catch up. -
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 aSpecimenStatusenum to the TS types in master §7, alongsideInventoryStatus, and ablood_specimenstable (or columns on the order/result tables) to track it. -
ประเภทงาน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, nospecialty_code/work_typefield 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. -
Amend / correction flow is implemented and stricter than EPHIS. Migration
033_blood_lab_batching_amend.sqlintroduces:blood_lis_results_amendsversioned-edit log (before/after JSONB snapshots, retention floor 15 yrs)amend_blood_lis_result(...)RPC — the only sanctioned way to modify a finalised rowfn_blood_lis_block_finalised_updatetrigger — direct UPDATE onapproved_at IS NOT NULLrows is blocked- Reason text required, ≥8 chars;
reason_codestructured enum (transcription/instrument_correction/reanalysis/other)
This answers F5 (formerly open) — amend semantics are append-only with second-approver gate, never overwrite.
-
Lab batches as a first-class concept. Migration 033 also introduces
blood_lab_batches(24/48/96-well plates) with FK fromblood_lis_results.lab_batch_id. The “Batch view” toggle in §3.1 should resolve tov_blood_lab_batches_rollup(providesbag_count,pending_verify,pending_approve,disqualified_countper 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 fromS31_RETURN) - 3.3.10 — Bedside nurse pickup (
S6_RECV_BLOODinterface)
- Migrations:
- 031_blood_lis_provenance.sql — provenance + 3-role audit
- 032_blood_bank_config.sql — per-tenant policy flags (e.g. separate-verifier on/off)
- 033_blood_lab_batching_amend.sql — lab batches + amend log + amend RPC
- 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_resultsfor legacy/manual,blood_lis_resultsfor 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 ablood_result_codesreference table (G2) would let the UI fetch valid options pertest_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_resultRPC (migration 033) — never UPDATE the row directly. The triggertrg_block_finalised_updatewill reject direct edits to approved rows. The RPC requires:p_changesJSONB,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 allowentered_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_DISP → S40_RECV_BILLING and add a parallel S17_RECORD_TEST → S40 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).