Work

Cruor

Blood-donation lifecycle in one relational system: donor → donation → unit → transfer.

Problem & Solution

Problem

Blood-donation work fragments naturally: scheduling, donation records, unit inventory, and cross-hospital requests drift into disconnected processes, partial writes corrupt the lifecycle, and a single status field cannot represent several hospitals responding to one request.

Solution

Cruor unifies the lifecycle in one relational system — donor → appointment or campaign → donation → blood unit → inventory → inter-hospital request. Donation writes are transactional, inventory is derived from unit-level rows, and each hospital's offer is its own record. It ships as a modular monolith with a Next.js client; concurrency hardening is queued as future work.

Architecture

Cruor donor user flow diagram
Cruor — donor user flow — click to expand
Cruor hospital user flow diagram
Cruor — hospital user flow — click to expand

Recording a donation wraps the donation record, its blood unit, and the source appointment or registration update in a single transaction — the workflow can never persist a donation without its unit. A hospital's inventory is derived from its blood units rather than stored as a separate count.

Engineering Decisions

Transactional donations

Donation creation, blood-unit creation, and the source-state update commit in one transaction. A failure anywhere rolls everything back — no orphans, no unit-less records.

Units, not counters

Each physical unit is its own BloodUnit row; inventory views filter and group those rows. Availability reasons over persisted data, and every unit traces to its donation.

Independent offers

Each hospital's response is its own BloodRequestResponse row; ACCEPT is an offer, the requester selects one, allocation is a request state transition. Row-level locking is future hardening.

Challenges & Learnings

Two hospitals can accept the same request independently, so allocation had to become a state transition on the request instead of a field update. The lesson: model the conflict in the schema first — independent response rows plus request state — before reaching for locks. Reservation at accept-time remains queued as future work.

Limitations & next steps

  • Offer-acceptance concurrency hardening
  • Blood-unit expiry background processing
  • Caching, workers, and notifications
  • Observability and fuller automated testing