medOS ultra

Blood Bank Dispense (Section 4.3)

Specification for the blood-bank dispense workflow subsystem.

30 min read diagramsUpdated 2026-04-28docs/architecture/blood-bank-dispense-spec.md

ระบบงานธนาคารเลือด — จ่ายเลือด

Status: Living document, assembled from 5 source flow-diagram images covering 4 swim lanes, plus 11 source UI photos covering the Officer cockpit — Img 9 is the legacy “บันทึก Immuno Hematology” pop-up (v0.5.1); Img 10 (3.3.9) and Img 11 (3.3.10) are the discharged-release and ward-pickup screens covered in v0.6. Authoritative source: This document. If code disagrees with anything here, the code is wrong unless this doc is updated first. Audience: Engineers and Claude Code agents implementing or reviewing this subsystem. Reference HIS: EPHIS / Vajira PRD shape — see §11 for how the spec lanes map onto the actual code organization, which is split between encounter-context UI and the /blood-bank operations cockpit.


How to use this document

  1. State IDs are contracts. Use the IDs from §4 verbatim in code (S1_KEY_REQ, never s1KeyReq, KeyRequest, or any other casing). They map 1:1 to the source diagrams and are how this spec is referenced from memory across sessions.
  2. TORs are testable requirements. Every TOR (§6) is bound to one or more states. When implementing a state, all bound TORs must be satisfiable.
  3. Open questions (§9) are explicit. Anything labelled “assumed” or marked with a Q# is a best inference from incomplete diagrams — confirm with the spec owner before relying on it. Several Qs now have code-derived answers added in v0.5 — those are working answers from grepping the codebase, not authoritative spec resolutions.
  4. Lane discipline matters. A transition between lanes (e.g. DN) is a hand-off. Implement these as queues, notifications, or workflow assignments — never as synchronous in-process calls. The lane boundary is also where authorization checks should sit.
  5. Code organization ≠ lane structure. §11 is mandatory reading before “is X missing?” — the Doctor and bedside-Nurse lanes live in encounter/patient context, not on the /blood-bank shell. The shell is the Officer cockpit (plus per-patient dispense embedded via DynamicContentRenderer).
  6. Figma-linked states (✓ in §4) have UI designs; render-time concerns belong on those states, not on internal-only ones.

1. Actors / Swim Lanes

Code Thai English Responsibilities
D แพทย์ Doctor Originates blood request; analyzes patient symptoms during allergic reactions
N พยาบาล Nurse Sends forms; receives blood from officer; dispenses to patient; records symptoms
O เจ้าหน้าที่ห้องเลือด Blood Bank Officer Compatibility/cross-match checks; withdraw & ready blood for dispense; inventory mgmt; analyze allergy history
F การเงิน Finance / Cashier Receives dispensed quantity & price data; calculates cost (TOR 4.3.8)

2. Source Diagram Legend

Symbol Meaning
White rectangle Sub-stage (atomic action)
Orange rounded rectangle Main Stage (subsystem grouping, e.g. “State 1”)
Yellow / green rectangle TOR (requirement note, attached via dashed line)
Pink / red rectangle TOR with external system integration (LIS, finance, reporting)
Underlined text The state has a Figma design link
Green circle Start ([*]) or end terminus
Yellow diamond Decision point (branching)
Yellow circle (small) Merge / join node
Solid arrow Control flow (state transition)
Dashed arrow TOR association (NOT flow)

3. Master State Machine

Lane membership is encoded in §4 because Mermaid stateDiagram-v2 does not natively render swim lanes.

stateDiagram-v2
    %% =========================================
    %% State 1: Reservation (D originates → N processes)
    %% =========================================
    [*] --> S1_KEY_REQ
    S1_KEY_REQ --> S2_SEND_FORMS
    S2_SEND_FORMS --> S3_URGENCY
    S3_URGENCY --> S4A_5MIN: ด่วน 5min
    S3_URGENCY --> S4B_10MIN: ด่วน 10min
    S3_URGENCY --> S4C_20MIN: ด่วน 20min
    S3_URGENCY --> S4D_NOT_URGENT: ไม่ด่วน
    S4A_5MIN --> S5_JOIN
    S4B_10MIN --> S5_JOIN
    S4C_20MIN --> S5_JOIN
    S4D_NOT_URGENT --> S5_JOIN

    %% =========================================
    %% State 2: Reservation Display (O)
    %% =========================================
    S5_JOIN --> S15_RES_DISPLAY_SYS
    S15_RES_DISPLAY_SYS --> S16_RECV_FORMS

    %% =========================================
    %% State 3: Compatibility Check (O)
    %% =========================================
    S16_RECV_FORMS --> S17_COMPAT_SYS
    S17_COMPAT_SYS --> S18_HIST_DEC
    S18_HIST_DEC --> S19_BLOOD_TEST_NEW: ไม่เคยมีประวัติ
    S19_BLOOD_TEST_NEW --> S20_BLOOD_GROUP
    S20_BLOOD_GROUP --> S22_MERGE
    S18_HIST_DEC --> S21_BLOOD_TEST_HIST: เคยมีประวัติ
    S21_BLOOD_TEST_HIST --> S22_MERGE
    S22_MERGE --> S23_CROSS_MATCH

    %% Cross-match outcomes
    S23_CROSS_MATCH --> S24_HOLD: ไม่สามารถใช้งานได้
    S24_HOLD --> S25_NOTIFY
    S25_NOTIFY --> [*]: terminus TBD (Q6)
    S23_CROSS_MATCH --> S26_WITHDRAW: ใช้งานได้
    S26_WITHDRAW --> S27_READY_DISPENSE
    S27_READY_DISPENSE --> S6_RECV_BLOOD
    S27_READY_DISPENSE --> S28_RECORD_DISP

    %% =========================================
    %% Nurse dispense (N)
    %% =========================================
    S6_RECV_BLOOD --> S7_DISPENSE: รับตามที่สั่ง
    S6_RECV_BLOOD --> S29_PENDING: รับบางส่วน
    S29_PENDING --> S13_CHK_REM
    S7_DISPENSE --> S8_ALLERGY
    S8_ALLERGY --> S13_CHK_REM: ไม่มีอาการ
    S13_CHK_REM --> S30_INTEGRITY: คงเหลือ
    S13_CHK_REM --> [*]: ใช้หมด (terminus TBD, Q8)

    %% =========================================
    %% Allergy reaction sub-flow (N → D → N → O)
    %% =========================================
    S8_ALLERGY --> S9_REC_SYMP: มีอาการ
    S9_REC_SYMP --> S10_ANALYZE
    S10_ANALYZE --> S11_ORDER_DRAW
    S11_ORDER_DRAW --> S12_REC_ALLERGY_HIST
    S12_REC_ALLERGY_HIST --> S34_ANALYZE_ALLERGY_HIST
    S34_ANALYZE_ALLERGY_HIST --> S35_REC_ALLERGY_ANALYSIS
    S35_REC_ALLERGY_ANALYSIS --> [*]: terminus TBD (Q9)

    %% =========================================
    %% Integrity check / return / destroy (O)
    %% =========================================
    S30_INTEGRITY --> S31_RETURN: สมบูรณ์
    S30_INTEGRITY --> S32_DESTROY: ไม่สมบูรณ์
    S31_RETURN --> S38_STORE: assumed (Q7)
    S32_DESTROY --> S33_BLOOD_HIST

    %% =========================================
    %% State 4: Blood Inventory Management (O)
    %% =========================================
    S36_BLOOD_MGMT --> S37_RED_CROSS
    S37_RED_CROSS --> S38_STORE
    S38_STORE --> S33_BLOOD_HIST

    %% =========================================
    %% Finance / Cashier (F)
    %% =========================================
    S28_RECORD_DISP --> S40_RECV_BILLING: assumed (Q11)
    S40_RECV_BILLING --> S41_CALC_COST
    S41_CALC_COST --> [*]

4. State ID Dictionary

4.1 Doctor (D)

ID Thai English Type Figma
S1_KEY_REQ คีย์ Request เลือด Key blood request action
S10_ANALYZE วิเคราะห์อาการผู้ป่วย Analyze patient symptoms action

4.2 Nurse (N)

ID Thai English Type Figma
S2_SEND_FORMS ส่งใบจองเลือด+ใบตรวจเลือด Send reservation + blood-test forms action
S3_URGENCY ความเร่งด่วน Urgency check decision
S4A_5MIN 5 นาที 5-min response window (urgent) action
S4B_10MIN 10 นาที 10-min response window (urgent) action
S4C_20MIN 20 นาที 20-min response window (urgent) action
S4D_NOT_URGENT ไม่ด่วน Not urgent action
S5_JOIN (merge) Join after urgency split merge
S6_RECV_BLOOD รับโลหิต Receive blood (from officer) decision
S7_DISPENSE จ่ายเลือดผู้ป่วย Dispense to patient action
S8_ALLERGY ผู้ป่วยมีอาการแพ้ Patient has allergic reaction? decision
S9_REC_SYMP บันทึกอาการ Record symptoms action
S11_ORDER_DRAW สั่งเจาะเลือด Order blood draw action
S12_REC_ALLERGY_HIST บันทึกประวัติการแพ้โลหิต Record blood-allergy history action
S13_CHK_REM เช็คจำนวนคงเหลือ Check remaining quantity decision

4.3 Blood Bank Officer (O)

ID Thai English Type Figma
S15_RES_DISPLAY_SYS ระบบการแสดงผลการจองเลือด State 2 Reservation Display System main stage
S16_RECV_FORMS รับใบตรวจเลือดใบจองเลือด Receive blood-test/reservation forms action
S17_COMPAT_SYS ระบบการตรวจสอบความเข้ากันได้ของเลือด State 3 Compatibility Check System main stage
S18_HIST_DEC (decision: prior history?) Has prior blood history? decision
S19_BLOOD_TEST_NEW ตรวจสอบเลือด Blood test (no-history path) action
S20_BLOOD_GROUP ตรวจหมู่เลือด Blood-group test action
S21_BLOOD_TEST_HIST ตรวจสอบเลือด Blood test (has-history path) action
S22_MERGE (merge) Merge after history-branch tests merge
S23_CROSS_MATCH Cross match Cross-match decision decision
S24_HOLD HOLD Hold (cannot use / no such blood) action
S25_NOTIFY แจ้งเหตุผล Notify reason (to requester) action
S26_WITHDRAW เบิกโลหิต Withdraw blood from inventory action
S27_READY_DISPENSE พร้อมจ่ายเลือด Ready to dispense action
S28_RECORD_DISP บันทึกการจ่ายเลือด Record dispense event action
S29_PENDING บันทึกโลหิตค้างจ่าย Record pending/unfulfilled blood action
S30_INTEGRITY ตรวจสอบความสมบูรณ์ของโลหิต Blood integrity check decision
S31_RETURN ส่งคืนโลหิต Return blood to inventory action
S31D_DISCHARGE_RELEASE บันทึกการปลดเลือดจากผู้ป่วยที่จำหน่าย Release reserved bags for discharged patients (mode of S31_RETURN) action
S6P_PICKUP_REQUEST บันทึกแจ้งขอรับเลือด Ward-nurse pickup ticket — HN → pending bags → “เลขที่รับ” mint (precedes S6_RECV_BLOOD) action
S32_DESTROY ทำลาย Destroy (compromised blood) action
S33_BLOOD_HIST บันทึกประวัติโลหิต Record blood history action
S34_ANALYZE_ALLERGY_HIST วิเคราะห์ประวัติการแพ้โลหิต Analyze allergy history action
S35_REC_ALLERGY_ANALYSIS บันทึกวิเคราะห์ประวัติการแพ้โลหิต Record allergy-history analysis action
S36_BLOOD_MGMT ระบบบริหารจัดการโลหิต State 4 Blood Management System main stage
S37_RED_CROSS โลหิตจากสภากาชาติ Blood from Red Cross action
S38_STORE จัดเก็บโลหิต Store blood (inventory) action

4.4 Finance (F)

ID Thai English Type Figma
S40_RECV_BILLING รับข้อมูลจำนวนและราคา Receive quantity & price data action
S41_CALC_COST คำนวนค่าใช้จ่าย Calculate cost action

5. Sub-System Flows

Each sub-flow below is a slice of §3. They are not separate state machines — they share IDs with the master.

5.1 State 1 — Reservation (D + N)

[*] (D) ──► S1_KEY_REQ (D) ──► S2_SEND_FORMS (N) ──► S3_URGENCY (decision)
                                                     ├─ ด่วน  ──► S4A_5MIN  ──┐
                                                     ├─ ด่วน  ──► S4B_10MIN ──┤
                                                     ├─ ด่วน  ──► S4C_20MIN ──┼──► S5_JOIN
                                                     └─ ไม่ด่วน ► S4D_NOT_URGENT ┘
S5_JOIN ──► S15_RES_DISPLAY_SYS (O)  // hand-off to officer

5.2 State 2 + 3 — Officer Compatibility Check (O)

S15_RES_DISPLAY_SYS ──► S16_RECV_FORMS ──► S17_COMPAT_SYS ──► S18_HIST_DEC
                                                              ├─ ไม่เคยมีประวัติ ──► S19_BLOOD_TEST_NEW ──► S20_BLOOD_GROUP ──┐
                                                              └─ เคยมีประวัติ   ──► S21_BLOOD_TEST_HIST ─────────────────────┴──► S22_MERGE
S22_MERGE ──► S23_CROSS_MATCH (decision)
              ├─ ใช้งานได้                            ──► S26_WITHDRAW ──► S27_READY_DISPENSE
              └─ ไม่สามารถใช้งานได้หรือไม่มีโลหิตดังกล่าว ──► S24_HOLD ──► S25_NOTIFY ──► (terminus TBD, Q6)
S27_READY_DISPENSE ──► S6_RECV_BLOOD (N)        // hand-off to nurse
S27_READY_DISPENSE ──► S28_RECORD_DISP          // parallel? sequential? (Q10)

5.3 Nurse Dispense (N)

S6_RECV_BLOOD (decision)
  ├─ รับตามที่สั่ง   ──► S7_DISPENSE ──► S8_ALLERGY (decision)
  │                                       ├─ มีอาการ   ──► (allergy sub-flow §5.4)
  │                                       └─ ไม่มีอาการ ──► S13_CHK_REM
  └─ รับบางส่วน      ──► S29_PENDING (O) ──► S13_CHK_REM
S13_CHK_REM (decision)
  ├─ คงเหลือ ──► S30_INTEGRITY (O, see §5.5)
  └─ ใช้หมด  ──► [*]  // terminus TBD (Q8)

5.4 Allergy Reaction Sub-Flow (cross-lane: NDNO)

S8_ALLERGY (มีอาการ) ──► S9_REC_SYMP (N)
                        ──► S10_ANALYZE (D)
                        ──► S11_ORDER_DRAW (N)
                        ──► S12_REC_ALLERGY_HIST (N)
                        ──► S34_ANALYZE_ALLERGY_HIST (O)
                        ──► S35_REC_ALLERGY_ANALYSIS (O)
                        ──► (terminus TBD, Q9)

5.5 Post-Dispense Integrity / Return / Destroy (O)

S30_INTEGRITY (decision)
  ├─ สมบูรณ์   ──► S31_RETURN ──► S38_STORE  (assumed, Q7)
  └─ ไม่สมบูรณ์ ──► S32_DESTROY ──► S33_BLOOD_HIST

S31D_DISCHARGE_RELEASE (mode of S31_RETURN, triggered without prior S26→S6 cycle)
  filter: discharged-patient date range
  ──► writes blood_returns audit row (reason='patient_discharged')
  ──► flips bag status crossmatched|reserved → in_stock
  ──► same downstream cascade as S31_RETURN → S38_STORE

5.5b Ward Pickup (precedes S6_RECV_BLOOD, between Officer cockpit and bedside)

S27_READY_DISPENSE (O)
  ──► S6P_PICKUP_REQUEST (N at the blood-bank window)
        input:  HN
        shows:  PendingComponentRow[] grouped by component_type
                (status='crossmatched' AND crossmatched_for_patient_hn=HN, FIFO)
        action: enter "จำนวนที่ต้องการ" per component → save
        output: เลขที่รับ NNNN/BE pickup-receipt no. stamped on every bag
                bag.status: crossmatched → issued, issue_type='crossmatched'
  ──► S6_RECV_BLOOD (N at bedside)

The pickup ticket (S6P) is the audit artifact that closes the loop between Officer’s “ready to dispense” and bedside “received & administering”.

5.6 State 4 — Blood Inventory Management (O)

S36_BLOOD_MGMT ──► S37_RED_CROSS ──► S38_STORE ──► S33_BLOOD_HIST

This is the supply-side flow (sourcing blood from the Red Cross into local inventory). It connects to §5.5 because S31_RETURN likely flows into S38_STORE.

5.7 Finance / Cashier (F)

(?) ──► S40_RECV_BILLING ──► S41_CALC_COST ──► [*]  // END

The trigger for S40_RECV_BILLING is unconfirmed (Q11). Working assumption: triggered when S28_RECORD_DISP completes (every dispense event creates a billing event).


6. TORs (Requirements)

Each TOR is bound to one or more states. When implementing or reviewing those states, the TOR must be satisfiable.

TOR 4.3.1 — Blood request data

Bound to: S1_KEY_REQ Requirement: The blood request must capture the following fields:

  • patient_info — patient identity record
  • general_history — general medical history
  • diagnosis (การวินิจฉัย)
  • requesting_unit (หน่วยงานที่ขอเบิก) — ward / department
  • requesting_doctor_name
  • blood_product_type
  • urgencyurgent | routine; if urgent, see Q1 about response window
  • datetime_needed

TOR 4.3.2 — Request list display

Bound to: S16_RECV_FORMS Requirement: System displays the list of blood requests with details: patient name, requesting department (แผนกที่ขอเบิก), requesting doctor, blood product, urgency.

TOR 4.3.3 — Record dispense

Bound to: S28_RECORD_DISP Requirement: System can record every blood-dispense event (บันทึกการจ่ายเลือด). At minimum this should include: blood unit ID, patient ID, dispensed-by user, dispensed-at timestamp, quantity.

TOR 4.3.4 — Cancel reservation (cross-cutting)

Bound to: Cross-cutting — cancel must be reachable from one or more states. Q2 OPEN: which states allow cancel? Requirement:

  • Cancel transition: (active state) → CANCEL → S25_NOTIFY-equivalent (พร้อมแจ้งเหตุผล)
  • A reason is required on cancel.
  • System auto-logs canceller_user_id and cancel_datetime.

TOR 4.3.5 — Return blood to inventory

Bound to: S31_RETURN Requirement: System supports returning blood products into inventory storage (S38_STORE).

TOR 4.3.6 — Inventory status management

Bound to: S38_STORE Requirement: System can specify and change blood-product status. Statuses include:

  • พร้อมใช้งาน (ready)
  • รอการกำจัด (awaiting disposal)
  • รอจ่าย (awaiting dispense)
  • จ่ายแล้ว (dispensed)

The status set is user-configurable — the four above are the named examples in the spec, not necessarily exhaustive.

TOR 4.3.7 — LIS integration

Bound to: S23_CROSS_MATCH Requirement: System integrates with the hospital’s Laboratory Information System (LIS) to perform Group Matching and Compatibility Testing, and to record/display lab test results.

TOR 4.3.8 — Finance / billing integration

Bound to: S40_RECV_BILLING, S41_CALC_COST Requirement: System integrates with the finance/billing system to charge nursing fees against the patient OR feed claim/reimbursement workflows. Cost calculation must align with the actual services provided at every step (i.e. only what was actually dispensed gets billed).

TOR 4.3.9 — UNKNOWN

Q12 OPEN. TOR 4.3.9 was not visible in any source diagram seen so far. Confirm with the spec owner whether 4.3.9 exists, was renumbered, or was intentionally skipped.

TOR 4.3.10 — Reports & statistics (cross-cutting)

Bound to: Cross-cutting — not tied to a single state. Requirement: System can display various reports and statistics for the department/unit (สามารถแสดงรายงานและสถิติต่างๆของหน่วยงานได้).


7. Suggested Data Shapes (TypeScript)

These are starting points — adjust as the database schema solidifies. The intent is that every state transition in §3 corresponds to a function or message that takes one of these entities and produces the next.

// =====================================================================
// Lanes / actors
// =====================================================================
type Lane = 'D' | 'N' | 'O' | 'F';

// =====================================================================
// State enum — keep in sync with §4
// =====================================================================
type FlowState =
  // D
  | 'S1_KEY_REQ' | 'S10_ANALYZE'
  // N
  | 'S2_SEND_FORMS' | 'S3_URGENCY'
  | 'S4A_5MIN' | 'S4B_10MIN' | 'S4C_20MIN' | 'S4D_NOT_URGENT'
  | 'S5_JOIN' | 'S6_RECV_BLOOD' | 'S7_DISPENSE' | 'S8_ALLERGY'
  | 'S9_REC_SYMP' | 'S11_ORDER_DRAW' | 'S12_REC_ALLERGY_HIST'
  | 'S13_CHK_REM'
  // O
  | 'S15_RES_DISPLAY_SYS' | 'S16_RECV_FORMS' | 'S17_COMPAT_SYS'
  | 'S18_HIST_DEC' | 'S19_BLOOD_TEST_NEW' | 'S20_BLOOD_GROUP'
  | 'S21_BLOOD_TEST_HIST' | 'S22_MERGE' | 'S23_CROSS_MATCH'
  | 'S24_HOLD' | 'S25_NOTIFY' | 'S26_WITHDRAW' | 'S27_READY_DISPENSE'
  | 'S28_RECORD_DISP' | 'S29_PENDING' | 'S30_INTEGRITY'
  | 'S31_RETURN' | 'S32_DESTROY' | 'S33_BLOOD_HIST'
  | 'S34_ANALYZE_ALLERGY_HIST' | 'S35_REC_ALLERGY_ANALYSIS'
  | 'S36_BLOOD_MGMT' | 'S37_RED_CROSS' | 'S38_STORE'
  // F
  | 'S40_RECV_BILLING' | 'S41_CALC_COST'
  // terminal
  | 'END';

// =====================================================================
// Blood request (TOR 4.3.1, originated at S1_KEY_REQ)
// =====================================================================
type Urgency =
  | { kind: 'routine' }
  | { kind: 'urgent'; responseWindowMin: 5 | 10 | 20 };  // see Q1

interface BloodRequest {
  id: string;
  patient: PatientRef;
  generalHistory: string;
  diagnosis: string;
  requestingUnit: string;          // หน่วยงานที่ขอเบิก
  requestingDoctor: UserRef;
  bloodProduct: BloodProduct;      // see below
  urgency: Urgency;
  neededAt: ISODateTime;
  state: FlowState;
  createdAt: ISODateTime;
  cancellation?: Cancellation;     // TOR 4.3.4
}

// TOR 4.3.4
interface Cancellation {
  by: UserRef;
  at: ISODateTime;
  reason: string;                  // required
  fromState: FlowState;            // which state was active when cancelled
}

// =====================================================================
// Blood product / inventory (TOR 4.3.6, bound to S38_STORE)
// =====================================================================
type InventoryStatus =
  | 'พร้อมใช้งาน'      // ready
  | 'รอการกำจัด'      // awaiting disposal
  | 'รอจ่าย'           // awaiting dispense
  | 'จ่ายแล้ว';        // dispensed
  // status set is configurable — extend as needed

interface BloodProduct {
  id: string;                      // unit ID
  productType: string;             // PRBC, FFP, Platelets, etc.
  bloodGroup: string;              // ABO + Rh
  source: 'red_cross' | string;    // S37_RED_CROSS or other
  status: InventoryStatus;
  receivedAt: ISODateTime;
  expiresAt: ISODateTime;
}

// =====================================================================
// Cross-match (TOR 4.3.7, bound to S23_CROSS_MATCH)
// =====================================================================
interface CrossMatchResult {
  requestId: string;
  bloodProductId: string;
  outcome: 'compatible' | 'incompatible' | 'no_blood';
  // compatible    -> S26_WITHDRAW
  // incompatible
  // | no_blood    -> S24_HOLD
  lisExternalId?: string;          // reference into LIS
  performedAt: ISODateTime;
  performedBy: UserRef;
}

// =====================================================================
// Dispense event (TOR 4.3.3, bound to S28_RECORD_DISP)
// =====================================================================
interface DispenseEvent {
  id: string;
  requestId: string;
  bloodProductId: string;
  receivedKind: 'รับตามที่สั่ง' | 'รับบางส่วน';   // S6 branch
  quantityDispensed: number;
  pendingQuantity?: number;        // when receivedKind = รับบางส่วน → S29_PENDING
  dispensedAt: ISODateTime;
  dispensedByNurse: UserRef;
  recordedByOfficer: UserRef;
}

// =====================================================================
// Allergy episode (sub-flow §5.4)
// =====================================================================
interface AllergyEpisode {
  id: string;
  dispenseEventId: string;
  symptoms: string;                // S9_REC_SYMP
  doctorAnalysis: string;          // S10_ANALYZE
  bloodDrawOrdered: boolean;       // S11_ORDER_DRAW
  allergyHistoryRecord: string;    // S12_REC_ALLERGY_HIST
  officerAnalysis?: string;        // S34_ANALYZE_ALLERGY_HIST
  officerAnalysisRecord?: string;  // S35_REC_ALLERGY_ANALYSIS
  episodeStartedAt: ISODateTime;
}

// =====================================================================
// Integrity check (§5.5, bound to S30_INTEGRITY)
// =====================================================================
interface IntegrityCheck {
  bloodProductId: string;
  result: 'สมบูรณ์' | 'ไม่สมบูรณ์';
  // สมบูรณ์      -> S31_RETURN -> S38_STORE
  // ไม่สมบูรณ์   -> S32_DESTROY -> S33_BLOOD_HIST
  checkedAt: ISODateTime;
  checkedBy: UserRef;
}

// =====================================================================
// Billing event (TOR 4.3.8, bound to S40_RECV_BILLING / S41_CALC_COST)
// =====================================================================
interface BillingEvent {
  id: string;
  dispenseEventId: string;         // assumed source per Q11
  quantity: number;
  unitPrice: number;
  totalCost: number;
  patientId: string;
  state: 'received' | 'calculated' | 'submitted_to_finance';
  createdAt: ISODateTime;
}

// =====================================================================
// Common
// =====================================================================
type ISODateTime = string;        // RFC 3339
interface PatientRef { id: string; }
interface UserRef    { id: string; lane: Lane; }

8. Validation Checklist for Code

When implementing or reviewing code for this subsystem, verify each item:

  1. State enum match. Every state name in code matches an ID from §4 exactly (case-sensitive, no abbreviations).
  2. No invented transitions. Every state transition in code corresponds to an arrow in §3. If a transition is needed that isn’t in §3, it’s a spec gap — raise it, don’t invent it.
  3. Lane hand-offs are explicit. Cross-lane transitions (e.g. S5_JOIN D/N → S15_RES_DISPLAY_SYS O) must be implemented as queues or notifications, not as direct calls. Authorization checks belong at lane boundaries.
  4. TOR coverage. For every state with a bound TOR (§6), the code at that state satisfies the TOR’s data and behavioral requirements.
  5. Cancellation (TOR 4.3.4) is uniformly handled — reason required, canceller and timestamp auto-logged. Until Q2 is resolved, cancel is reachable from every active (non-terminal) state.
  6. Decision branches are total. Every decision state (S3, S6, S8, S13, S18, S23, S30) must handle every documented branch — no implicit fall-throughs.
  7. Open questions are flagged. Anywhere code depends on an assumed answer to §9, the assumption is recorded as a comment referencing the question ID (e.g. // ASSUMES Q11: billing triggered by S28_RECORD_DISP).
  8. Figma-linked states have UI. States marked ✓ in §4 are user-facing — UI must exist before those states are considered “done”.
  9. TOR 4.3.10 (reports) is implemented as a separate read-side, not woven into the dispense flow.
  10. §11 mapping respected. Don’t add a new route called /officer-worklist/blood-bag-request is already the Officer worklist. Don’t add a “create blood request” page on /blood-bank — it lives in encounter dialogs. See §11.

9. Open Questions and Assumptions

Status legend: 🟢 resolved (spec owner confirmed), 🟡 working assumption, 🔵 code-derived working answer (v0.5; needs spec-owner confirmation), 🔴 unresolved.

ID Question Status Assumption / code-derived answer
Q1 Under ด่วน, are 5/10/20 นาที SLA tiers (system enforces response time) or user-picked response windows? 🔵 Code answer: treated as a flag on the request (urgency: 'urgent' | 'routine' + selectable window); no SLA enforcement in code today. Implementation = user-picked window.
Q2 TOR 4.3.4 (cancel) — from which states is cancel reachable? 🔵 Code answer: cancel is wired only from InventoryMatchingView.tsx (officer matching tab) via cancelBagReservation, and from BloodBagRequestDetail.tsx:234 via updateBloodRequestApi(... cancel). Reason field exists but isn’t enforced as required. Needs widening to all active states + reason validation.
Q3 What follows S12_REC_ALLERGY_HIST? 🟢 S34_ANALYZE_ALLERGY_HIST (officer)
Q4 What is S6_RECV_BLOOD’s “not received as ordered” branch? 🟢 รับบางส่วนS29_PENDINGS13_CHK_REM
Q5 What does S13_CHK_REM “remaining” branch go to? 🟢 คงเหลือS30_INTEGRITY (NOT a loop back to S6)
Q6 What is the terminus after S25_NOTIFY (HOLD path)? 🔵 Code answer: blood_bag_disposition.decision = 'hold' is a schema option but no UI consumer; effectively a flow end with no notification fan-out yet. Needs design.
Q7 Where does S31_RETURN send returned blood? 🔵 Code answer: confirmed — /blood-return (BloodReturnModule) writes back to blood_separation_entries, status flips to return then to ready per stock movement. → S38_STORE is correct.
Q8 What is the terminus of S13_CHK_REM ใช้หมด branch? 🔴 Treating as flow end ([*]); not modeled in code.
Q9 What is the terminus after S35_REC_ALLERGY_ANALYSIS? 🔴 Treating as flow end; blood_allergy_reports table exists but no S34/S35 UI to populate the analysis result.
Q10 S27_READY_DISPENSE has two outgoing arrows (to S6 and S28) — sequential or parallel? 🔵 Code answer: sequential write inside the dispense UI — BloodCrossMatchDispense calls saveCrossmatchRow synchronously on row edit; no parallel branch.
Q11 What state(s) trigger S40_RECV_BILLING? Diagram shows two inbound arrows. 🔵 Code answer: not triggered by S28 today. /blood-finance reads blood_bank_demo_lab_orders (a separate demo table) and is disconnected from BloodCrossMatchDispense.saveCrossmatchRow writes. Exception: autologous donation 250฿ charge IS wired to financial per the changelog tile. The general S28→S40 link is a parity gap.
Q12 TOR 4.3.9 was not visible in any diagram — does it exist? 🔴 Confirmed not present in any source artifact seen so far.

10. Change Log

  • v0.8 (2026-04-26) — DORNO sub-donor end-to-end pipe wired. The blood_bag_donor_sources junction (created in migration 20260424_hosxp_intake_returns.sql) had no service or UI consumers; sub-donor structure existed only as a mock-data prop in IntakeScreen. Fixed across four layers: (a) bloodBankIntake.medbase.ts gained saveBagDonorSources, listBagDonorSources, listBagDonorSourcesByEntries, and getPooledBagsSummary (a single-round-trip aggregate for banner-style displays); (b) IntakeScreen save handler now calls saveBagDonorSources for every unit with a real DB UUID when isHosxpIntakeLive(), idempotent-replacing the junction rows; © S26 SeparateComponentsNewModule gained a DORNO column — chip showing +N DORNO for pooled bags, tooltip lists each seq. bag-number, fed by listBagDonorSourcesByEntries keyed off the visible row IDs; (d) S38 BloodInventoryTab gained a PooledBagsBanner chip below BloodStockAlerts — click-to-open Popover lists the most recent pooled bags + their sub-donors, fed by getPooledBagsSummary. Banner self-hides when there are no pooled bags so single-donor-only inventories see no UI noise. Closes the §12.4 “DORNO sub-donor structure” item; satisfies the BLOOD_BANK_AUDIT_VERIFICATION concern that “S26/S38 don’t surface pooled-donor info.” Photo Img 2 right panel (“Sub-donors per pooled bag [LPPC]”) is now code-covered. Verification was blocked by a pre-existing JSX error in BloodDonorListModule.tsx breaking the blood-bank shell — code is grep-clean (no conflict markers, all 4 new service exports present), needs visual confirmation once the unrelated bug is fixed.
  • v0.7 (2026-04-26) — Officer cockpit consolidation: Test Results merged into Cross-match & Dispense, plus 3-role audit picker. (a) /blood-bank/blood-cross-match-dispense Tab 0 now embeds the full BloodTestResultsModule (Cell/Serum/Rh/Antibody/Crossmatch/Titer) via new patient + embedded props — same recording surface used at /blood-bank/blood-test-results standalone, and the same component the patient-profile dynamic tab renders. Officers no longer hop between two routes to record T&S then dispense. (b) The 3-role audit footer (“ผู้ทำ / ผู้ยืนยัน / ผู้จ่าย”) on the Tab 1 dispense table now uses the new FrequentUserSelect — a per-machine MRU picker against listUser, persisted in localStorage under mds.frequent-users.<slot>, with the performer slot auto-defaulted to the logged-in user from getMe. This is the first wired implementation of the 3-role audit fields visible in source photo Img 1, and satisfies the user-logging half of TOR 4.3.3 (S28_RECORD_DISP). Spec section 11.2 mapping rows for S17–S21 and S27/S28 updated. §12.3 “missing screens” backlog: 3-role audit ✅; cancel-with-reason still TBD (Q2 / TOR 4.3.4 reason field still not enforced). Merged via claude/fix-blood-bank-dashboard-nVtrz (commit 976b0059) on top of WIP checkpoint 5a80b502.
  • v0.6 (2026-04-25) — Two new spec-shaped screens shipped. (a) Spec 3.3.9 “บันทึกการปลดเลือดจากผู้ป่วยที่จำหน่าย” wired into BloodReturnModule as a mode toggle (S31D_DISCHARGE_RELEASE). Filters blood_separation_entries by status∈{reserved,crossmatched} + crossmatched_at window, joins onto patients for HN/AN/name, bulk-release flips bags back to in_stock and writes a blood_returns audit row with return_reason='patient_discharged' so the existing cascade trigger keeps movements/locations consistent. (b) Spec 3.3.10 “บันทึกแจ้งขอรับเลือด” landed as new BloodPickupRequestModule at /blood-bank/blood-pickup-request (S6P_PICKUP_REQUEST). Resolves HN → patient header → groups pending crossmatched bags by component → enter “จำนวนที่ต้องการ” → mints NNNN/BE receive number stamped on every transitioned bag (status crossmatched → issued). Both screens use design-kit wrappers; both verified rendering in the dev server. Spec section 11.2 mapping updated; §12.3 “missing screens” backlog reduced (S31_RETURN discharged-trigger ✅; S6_RECV_BLOOD ward pickup precursor ✅).
  • v0.5.1 (2026-04-25) — MAJOR §11 CORRECTION. The canonical EPHIS-shape Officer worklist isn’t /blood-bag-request — it’s /blood-bank/blood-list-patient (ListPatientBloodModuleMainTabPatientWorkList). Has all 6 EPHIS sub-tabs (INCOMING_REQUEST, STICKER_PRINTED_AND_BLOOD_SAMPLE_COLLECTED, NOT_CROSSMATCHED_YET, CROSSMATCHED, PARTIAL_BLOOD_RECEIVED, BLOOD_FULLY_RECEIVED) plus ยกเลิกสิ่งส่งตรวจ + ประวัติยกเลิกสิ่งส่งตรวจ toolbar buttons (partial TOR 4.3.4). Page renders correctly; data is empty against PH demo backend due to unseeded Mongo collections (frontend not at fault). /blood-bag-request “จับคู่เลือดพร้อมจ่าย” tab is the simpler single-tab matching view — secondary to /blood-list-patient.
  • v0.5.1 (2026-04-25) — §11.2 Immuno Hematology row enriched with route + Supabase tables (blood_lis_results summary, blood_test_results detail, migration 20260424_blood_test_results.sql). Confirms legacy “บันทึก Immuno Hematology” pop-up (ABO forward/reverse, Rh, Ab Screening, ผู้ทดสอบ, ตกลง) is fully replaced by BloodTestResultsModule.tsx at /blood-bank/blood-test-results.
  • v0.5 (2026-04-25) — Added EPHIS framing throughout. New §11 “Code organization vs. spec lanes” mapping every spec state to its actual code location (corrects an earlier “Officer worklist missing” error). New §12 “Screen coverage matrix” capturing what 8 source UI photos confirm vs. what’s still un-photographed. Updated Q1, Q2, Q6, Q7, Q10, Q11, Q12 with code-derived working answers (status 🔵). Added validation rule #10 about respecting §11 (don’t invent routes that already exist).
  • v0.4 — Added Finance/Cashier lane (F), S40_RECV_BILLING, S41_CALC_COST, TOR 4.3.8, TOR 4.3.10. Logged Q11, Q12.
  • v0.3 — Added Officer (O) lane, States 2/3/4, TORs 4.3.2/3/5/6/7. Resolved Q3, Q4, Q5. Corrected S13_CHK_REM “remaining” branch (was incorrectly assumed as a loop back to S6).
  • v0.2 — Added State 2 nurse dispense + allergy reaction sub-flow.
  • v0.1 — Initial: State 1 reservation (D + N), TORs 4.3.1, 4.3.4, legend.

11. Code Organization vs. Spec Lanes (added v0.5)

The state machine in §3 is one continuous flow, but the implementation deliberately splits it across three contexts that mirror EPHIS / Vajira PRD shape. A future Claude session that searches /blood-bank for the Doctor request form will find nothing and conclude “missing”; that conclusion is wrong. The form is in encounter dialogs.

11.1 Three contexts

Context What lives here Spec states covered
Encounter / patient-profile UI (NOT /blood-bank) Doctor’s “Request Blood” dialog inside an open patient encounter; bedside Nurse view for the same patient; per-patient dispense renderer S1_KEY_REQ, S2_SEND_FORMS, S3_URGENCY, S4*, S5_JOIN, S6_RECV_BLOOD (bedside view), S7_DISPENSE (bedside view), most of the allergy episode (S8S12) — though only S1 is currently wired; the rest are gaps
/blood-bank shell — Officer cockpit Officer worklist of incoming requests; cross-match decision + bag reservation; inventory; receive-into-stock; recheck-and-issue; transfusion recording Most of the Officer lane: S15S38 (with several still missing screens — see §12)
/blood-bank shell embeds patient context BloodCrossMatchDispense, BloodBankPatientWorkflow, Bloodbank (the request form) — all rendered into patient-profile via DynamicContentRenderer when configured as a tab The per-patient slice of S23/S26/S27/S28. Same component is also reachable standalone for demo.

11.2 Verified mapping (state → file)

Saved separately in memory at reference_blood_bank_route_to_spec_map.md. Summary:

State File
S1_KEY_REQ web/packages/medical-kit/src/encounter-tools/timeline/dialogs/DialogBloodBank.tsx + web/packages/diagnostics-kit/src/blood-bank/components/blood-bag-request/components/BloodRequestEnhanced.tsx
S15_RES_DISPLAY_SYS, S16_RECV_FORMS Canonical: /blood-bank/blood-list-patientListPatientBloodModuleMainTabPatientWorkList (6 sub-tabs match EPHIS PRD exactly: INCOMING_REQUEST, STICKER_PRINTED_AND_BLOOD_SAMPLE_COLLECTED, NOT_CROSSMATCHED_YET, CROSSMATCHED, PARTIAL_BLOOD_RECEIVED, BLOOD_FULLY_RECEIVED). Simpler view: /blood-bank/blood-bag-requestInventoryMatchingView.tsx (single tab “จับคู่เลือดพร้อมจ่าย”).
S17_COMPAT_SYS, S18_HIST_DEC, S19_BLOOD_TEST_NEW, S20_BLOOD_GROUP, S21_BLOOD_TEST_HIST Route: /blood-bank/blood-test-resultsBloodTestResultsModule.tsx. Also embedded at /blood-bank/blood-cross-match-dispense Tab 0 (“บันทึกผลตรวจเลือด”) as `` (v0.7) — same form, hides its own gradient header + internal tabs, pipes patient HN/name/blood-group through to save payloads. Supabase: saveBloodTestResult writes summary → blood_lis_results, detail → blood_test_results (migration 20260424_blood_test_results.sql). Coverage: legacy “บันทึก Immuno Hematology” pop-up (ABO forward/reverse, Rh, Ab Screening with row insert, ผู้ทดสอบ, ตกลง) ✅ fully replaced — modern module adds Cell↔Serum mismatch warning, Screening Ab1/Ab2 phases, and Titer dilutions.
S23_CROSS_MATCH /blood-bank/blood-order-lab “เครื่อง Crossmatch” tab (LIS-simulated) + /blood-bank/blood-cross-match-dispense (per-patient)
S26_WITHDRAW /blood-bank/separate-components-newSeparateComponentsNewModule.tsx. DORNO column (v0.8): virtual __dorno_count column shows +N DORNO chip for pooled PC/LPPC bags; tooltip lists seq. sub_donor_bag_number. Data via listBagDonorSourcesByEntries (single round-trip, keyed by visible row IDs).
S27_READY_DISPENSE, S28_RECORD_DISP /blood-bank/blood-cross-match-dispenseBloodCrossMatchDispense.tsx (saveCrossmatchRow). Layout (v0.7): Tab 0 = embedded BloodTestResultsModule (Immuno Hematology recording); Tab 1 = crossmatch table + 3-role audit footer (“ผู้ทำ / ผู้ยืนยัน / ผู้จ่าย” + their timestamps). The 3 user-name fields use FrequentUserSelect — a per-machine MRU dropdown backed by listUser, with picks persisted in localStorage under mds.frequent-users.<slot>. Performer slot defaults to the logged-in user (getMe) on first mount. This satisfies the audit-trail half of TOR 4.3.3 (S28_RECORD_DISP requires dispensed-by user logging) and matches the 3-role footer in source photo Img 1.
S6_RECV_BLOOD, S7_DISPENSE (officer side) /blood-bank/blood-receive → BloodRecheck tab → BloodDispenseRecheckPage.tsx
S29_PENDING /blood-bank/blood-receive → BloodReceive tab partial flow (รับเลือดบางส่วน sub-tab)
S31_RETURN /blood-bank/blood-returnBloodReturnModule.tsx (default mode “รับคืน”)
S31D_DISCHARGE_RELEASE /blood-bank/blood-returnBloodReturnModule.tsx mode toggle “ปลดเลือดจากผู้ป่วยที่จำหน่าย” → service.ts:listBagsForDischargedPatients + releaseBagsForDischargedPatients (writes blood_separation_entries.status='in_stock' + blood_returns audit row with return_reason='patient_discharged')
S6P_PICKUP_REQUEST /blood-bank/blood-pickup-requestBloodPickupRequestModule.tsxservice.ts:lookupPatientByHn + listPendingForPatient + commitPickup (mints NNNN/BE receive number into blood_separation_entries.pickup_receipt_no, transitions bag crossmatched → issued)
S32_DESTROY /blood-bank/blood-discardBloodComponentDiscardConfirm
S37_RED_CROSS /blood-bank/inventory-sources-newInventorySourcesNewModule.tsx
S38_STORE (display) /blood-bank/blood-receive → BloodInventory tab → BloodInventoryTab.tsx (this is what user calls “ข้อมูลเลือดคงคลัง”). DORNO banner (v0.8): PooledBagsBanner chip (“DORNO: N pooled bags · M sub-donors”) sits below BloodStockAlerts; click opens a Popover listing the most recent pooled bags + their sub-donor bag numbers. Self-hides when no pooled bags exist. Data via getPooledBagsSummary. Aggregate-style placement (banner not column) chosen because the inventory grid is grouped by (bloodType + group + RH + bagType) and doesn’t expose per-bag IDs.
post-S7 transfusion record /blood-bank/blood-receive → BloodTransfusion tab → TransfusionRecordingForm.tsx (this is what user calls “บันทึกจ่ายเลือด”)

11.3 Lane hand-off implementation

Spec hand-off Code mechanism Verdict
S5_JOIN (encounter) → S15_RES_DISPLAY_SYS (Officer worklist) Doctor POSTs request via bloodRequest.service.ts; Officer worklist reads via listBloodRequestApi + listBloodRequestsEnhanced ✅ Working — single source of truth, no separate “send” step needed
S27_READY_DISPENSE (Officer) → S6_RECV_BLOOD (bedside Nurse) useBloodBankRealtime.ts subscribes to blood_status_notifications; status flip triggers realtime broadcast 🟡 Channel exists; need to verify bedside view actually consumes it for “ready for you” UX
S25_NOTIFY on incompatible/HOLD → bedside No writer emits to blood_status_notifications for this case 🔴 Missing
Allergy bounce N → D → N → O blood_allergy_reports table exists; reaction recording lives in TransfusionRecordingForm’s notes block; no officer-side analysis step 🔴 Episode is dormant past initial recording
S28_RECORD_DISP (Officer) → S40_RECV_BILLING (Finance) /blood-finance reads blood_bank_demo_lab_orders (demo); cross-match-dispense saveCrossmatchRow doesn’t write to a billing queue 🔴 Disconnected (exception: autologous 250฿ flow is wired)

12. Screen Coverage Matrix (added v0.5)

This section tracks what UI screens have been seen so far in source photos. Coverage is currently Officer-side only (9 photos; Img 9 = legacy “บันทึก Immuno Hematology” pop-up, added v0.5.1); Doctor, Nurse-bedside, Allergy, and Finance lanes are un-photographed.

12.1 Confirmed match (state → screen)

State Thai Photo
S15_RES_DISPLAY_SYS ระบบการแสดงผลการจองเลือด Img 7, 8 (worklist)
S16_RECV_FORMS รับใบตรวจเลือดใบจองเลือด Img 7 sub-tab #1 (พิมพ์สติกเกอร์ + รับสิ่งส่งตรวจ)
S17_COMPAT_SYS ระบบการตรวจสอบความเข้ากันได้ Img 6 (compatibility entry) + Img 9 (legacy Immuno Hematology pop-up) ✅ code-covered
S18_HIST_DEC prior history? Img 6 ประวัติหมู่เลือด field
S19_BLOOD_TEST_NEW ตรวจสอบเลือด (no history) Img 6 Cell Grouping section + Img 9 ABO forward ✅ code-covered
S20_BLOOD_GROUP ตรวจหมู่เลือด Img 6 Serum Grouping + Img 9 ABO reverse + Rh + Ab St Pos. ✅ code-covered
S21_BLOOD_TEST_HIST ตรวจสอบเลือด (history) Img 6 (same form, gated by history) + Img 9 ✅ code-covered
S23_CROSS_MATCH Cross match Img 6 Cross Matching field + Img 1 Result column
S26_WITHDRAW เบิกโลหิต Img 3, 4 (จ่ายเลือดจากคลัง)
S27_READY_DISPENSE พร้อมจ่ายเลือด Img 1 (จ่ายเลือดผู้ป่วยขอเลือด tab)
S28_RECORD_DISP บันทึกการจ่ายเลือด Img 1 (footer 3-role audit) ✅ code-covered (v0.7 — FrequentUserSelect for ผู้ทำ/ผู้ยืนยัน/ผู้จ่าย)
S29_PENDING บันทึกโลหิตค้างจ่าย Img 7 sub-tab #4 (รับเลือดบางส่วน)
S37_RED_CROSS โลหิตจากสภากาชาติ Img 2, 5 (source column)
S38_STORE จัดเก็บโลหิต Img 2, 4, 5 (inventory)

12.2 Partial / inferred

State Why partial
S13_CHK_REM ใช้หมด terminus Img 8 “รับเลือดครบแล้ว” tab is the result, but not the check screen itself
S22_MERGE Implicit (a join, not a screen)
S32_DESTROY Img 3 has ตัดถุงเลือดที่หมดอายุ button (expired-bag handling) — but a full destroy disposition flow isn’t visible
S36_BLOOD_MGMT Img 5 top nav names the module; State 4 is a grouping, not a screen

12.3 Spec states with NO photo yet

Doctor lane (D):

  • S1_KEY_REQ (lives in encounter dialog — outside /blood-bank per §11)
  • S10_ANALYZE (allergy)

Nurse lane (N):

  • S2_SEND_FORMS, S3_URGENCY, S4A/B/C/D, S5_JOIN (all bedside / encounter)
  • S6_RECV_BLOOD, S7_DISPENSE (bedside dispense — not officer’s screen)
  • S8_ALLERGY, S9_REC_SYMP, S11_ORDER_DRAW, S12_REC_ALLERGY_HIST (allergy episode)
  • S13_CHK_REM (the actual decision UI, not just the resulting tab)

Officer lane (O) — still missing:

  • S24_HOLD (HOLD state UI)
  • S25_NOTIFY (แจ้งเหตุผล screen)
  • S30_INTEGRITY (integrity decision gate)
  • S31_RETURN (ส่งคืนโลหิต screen — Q7) ✅ covered by BloodReturnModule (default mode + new S31D_DISCHARGE_RELEASE mode in v0.6)
  • S33_BLOOD_HIST (บันทึกประวัติโลหิต)
  • S34_ANALYZE_ALLERGY_HIST
  • S35_REC_ALLERGY_ANALYSIS

Nurse lane (N) — newly covered in v0.6:

  • S6P_PICKUP_REQUEST (BloodPickupRequestModule) — the precursor audit artifact for S6_RECV_BLOOD. The bedside-receive view itself is still out of scope here (encounter-context, not blood-bank shell).

Finance lane (F):

  • S40_RECV_BILLING, S41_CALC_COST — completely absent from photos

12.4 Things in photos NOT in spec yet (candidates for spec extension)

  • ปลด (released) status — Img 1 legend
  • External-hospital dispense — Img 3, 4 (ชื่อโรงพยาบาล + ประเภทการจ่าย)
  • DORNO sub-donor structure — Img 2 right panel ✅ wired in v0.8 (IntakeScreen → service → S26 column + S38 banner)
  • Worklist sub-tabs as state slices (6 of them) — Img 7
  • Autologous blood as a distinct product type — Img 3
  • Expired-bag handling sub-flow — Img 3

12.5 Coverage summary

Lane Coverage
D 0 / 2 states ❌
N 0 / 13 states ❌ (these may live in encounter UI, not blood-bank shell — see §11)
O ~14 / 24 states confirmed; 7 missing screens
F 0 / 2 states ❌

To get true 1-to-1 against the spec, photos still needed of:

  1. Doctor’s request screen from inside the patient encounter (S1)
  2. Bedside nurse receive/dispense view (S6, S7)
  3. Allergy reaction handling screens (S8S12, S34S35)
  4. HOLD / NOTIFY / INTEGRITY / RETURN screens (S24, S25, S30, S31)
  5. Finance/billing screen (S40, S41)
  6. Discard/destroy disposition screen (S32)
Ask Anything