Designing dispatch for in-home care

A software architecture in twelve figures

Designing dispatch for in-home care

An agency has a few hundred clinicians, nurses, therapists, aides, each driving to five to eight patient homes a day. Every morning each clinician needs a route. During the day things change: patients cancel, visits run long, traffic, an urgent add-on. Dispatchers in the office watch a board and a live map and step in. Patients get a text with a time window and an updated arrival time.

LA-WEST-3 · WED 10 SEP Priya N.Marcus T.Dana K. RN · wound carePT · home healthLVN · infusion 08:0009:0010:0011:0012:00 13:0014:0015:0016:00 V-481308:00 V-4820in visit V-483111:00 V-483913:30 V-480208:15 V-4818late 20m V-482711:30 V-484414:00 V-480908:30 V-4822cancelled V-483512:00 V-484113:15 now 10:42 completed in progress running late cancelled scheduled now
The artifact under design. Every figure that follows explains how this slice stays true to the field within about two seconds, for a few hundred rows instead of three.
00

How this document is built

This dossier applies four methods published by the Software Engineering Institute at Carnegie Mellon University to one design problem, dispatch for in-home care. Each numbered section is a step of those methods, and the twelve figures are the views the methods call for.

The Quality Attribute Workshop (Barbacci, Ellison, Lattanze, Stafford, Weinstock, and Wood, 2003) supplies the requirements discipline: name the architectural drivers, then write scenarios in six parts, prioritize them, and refine the top few. Attribute-Driven Design (Wojcik, Bachmann, Bass, Clements, Merson, Nord, and Wood, 2006) supplies the design discipline: rank the drivers by importance and by impact on structure, choose a design concept from patterns and tactics, instantiate elements and allocate responsibilities, define interfaces, and verify. The Architecture Tradeoff Analysis Method (Kazman, Klein, and Clements, 2000) supplies the evaluation: a utility tree, then analysis of the architectural approaches that yields sensitivity points, tradeoff points, risks, and non-risks. Views and Beyond (Clements and colleagues) supplies the shape of the documentation: the relevant views first, then the information that applies across views, ordered how, what, why.

Business and mission driverswhat dispatch serves · what it must not fail at ATAM step 2QAW step 2CMU/SEI-2000-TR-004 Architectural driversfunctional · constraints · quality attributes QAW step 4ADD step 3CMU/SEI-2006-TR-023 Quality attribute scenariossix parts · use case · growth · exploratory QAW steps 5–8CMU/SEI-2003-TR-016 Utility tree(importance, difficulty) at every leaf ATAM step 5CMU/SEI-2000-TR-004 Design conceptpatterns + tactics · instantiate · interfaces ADD steps 4–6tactics catalogs2002 · 2003 · 2009 Viewsmodule · component-and-connector · behavior Views and Beyondhow · what · why Analysissensitivity · tradeoffs · risks · non-risks ATAM steps 6 and 8CMU/SEI-2000-TR-004 the dashed return is ADD step 8: repeat for the next element until every architecturally significant requirement is met
The method. One spine, read downward. The report beside each step is the Carnegie Mellon publication that defines it; the dashed return is the iteration the Attribute-Driven Design method prescribes.

Three catalogs of architectural tactics name the design decisions in this document: the modifiability and performance tactics of Bachmann, Bass, and Klein (2002, 2003), and the availability tactics as refined by Scott and Kazman (2009). The 1995 taxonomy of quality attributes by Barbacci, Klein, Longstaff, and Weinstock supplies the vocabulary of concerns, factors, and methods for performance, dependability, and security. Two Carnegie Mellon courses supply the systems grounding: 15-445 Database Systems for transactions and concurrency control, and 15-440 Distributed Systems for time, synchronization, fault tolerance, and replication. The routing engine follows the constraint-based methods for vehicle routing presented by van Hoeve at the Tepper School of Business.

The views, in the terms of Views and Beyond: a context diagram (figure 02); a component-and-connector view of the running system (figure 03); a module view of the data (figure 04); a process view of planning (figure 05); behavior views as two sequences and a state machine (figures 06, 09, and 10); a module view of the client (figure 08); and two comparisons, transports and messages (figures 07 and 11). The board slice at the top is the artifact whose behavior the rest explains.

01

The problem and its drivers

The Architecture Tradeoff Analysis Method opens with the business and mission drivers, and the Quality Attribute Workshop asks for the same presentation before any scenario is written: the context, the functional requirements, the constraints, and the stakeholders. Here they are for dispatch.

Business and mission goals

  • More completed visits per clinician-day, with less time spent driving between them.
  • Fewer missed visits, and a plan that survives the morning's cancellations without a dispatcher rebuilding it by hand.
  • Patients told the truth about when someone arrives, and told it few enough times to keep reading.
  • Records that pass an electronic visit verification audit and a payer's dispute without anyone editing history.
  • New agencies onboarded by configuration, not by code.

Functional requirements

  • Intake. Visits arrive from the electronic medical record (EMR), a partner API, or manual entry, with patient, service type, duration, time window, required skills, and recurrence.
  • Plan. Each night, assign every visit to a clinician and sequence each clinician's day per territory. Dispatchers review, edit, publish.
  • Board. A timeline per clinician and a map, drag to reassign, conflicts surfaced before publish.
  • Track. Live location, visit status, schedule adherence, estimated arrival at the next stop.
  • React. A cancellation opens a gap and proposes fills. A late visit cascades arrival estimates and notifies patients. An urgent add finds an insertion point.
  • Clinician app. Today's route, hand-off to navigation, check-in and check-out with GPS and time, status, notes back to the EMR, all of it offline-capable.
  • Patient messaging. Confirmation, reminder, morning window, arrival update, two-way replies.
  • Audit and reporting. Who changed what and when; utilization, drive time, missed visits.

Design constraints

Attribute-Driven Design defines a constraint as a design decision with a predetermined outcome. Six are fixed before design begins.

  • Multi-tenant software as a service: hundreds of agencies, each with territories, each fully isolated.
  • The chosen stack: React and TypeScript on the web, React Native on phones, GraphQL with Apollo Client, Postgres, AWS, WebSockets and server-sent events.
  • HIPAA for protected health information (PHI), and electronic visit verification (EVV) under the 21st Century Cures Act, which requires the service, the patient, the date, the location, the provider, and the start and end time of each Medicaid-funded visit.
  • Clinicians lose signal constantly: basements, rural routes, hospital garages. Check-in and check-out must work offline.
  • The EMR remains the clinical record. This platform owns scheduling and logistics, not the chart.
  • Patients are reached by SMS first and voice second, through a messaging vendor under a business associate agreement.

Stakeholders

Dispatchers and schedulers on the web; clinicians in the field on phones; patients on their own phones; agency administrators who set policy; integration partners and EMR vendors; payers and auditors who read the visit record afterward; and the engineers who operate the platform. The method asks that scenarios come from all of them, not from the architect alone.

DispatcherconsoleReact · TypeScriptApollo Client ClinicianappReact Nativeoffline-first Agency EMR ·partner APIEpic · athenahealthWellSky queries · eventsreassign · publish route · changespings · check-ins orders innotes · EVV Dispatch platform Scheduling · source of truth Optimizer Tracking & ETA Notifications Integrations Realtime fan-out Postgres (RLS) · Redis · event bus · GraphQL API · webhooks Maps & traffic providertravel-time matrix · geocodinglive ETA Messaging vendorSMS · voice · under a BAA matrix · geocodetravel times back reminders · ETAsdelivery · replies Patient's phonewindow · ETA · reply C / R texts · calls · replies
Context. Three surfaces the platform builds, two vendors it rents, one system of record it does not own. Everything inside the platform box is the subject of this design; the dashed boxes are contracts with someone else and need a fallback.
02

Architectural drivers

An architectural driver, in the glossary of Attribute-Driven Design, is any functional requirement, constraint, or quality attribute requirement that has a significant impact on the structure of the architecture. The method's third step ranks candidate drivers twice, once by importance to stakeholders and once by expected impact on structure, using high, medium, and low, and then picks five or six as the focus of design. The Quality Attribute Workshop reaches a similar list in its fourth step by distilling the business and architecture presentations.

Candidate driverImportance · Impact · Why it shapes the structure
Board freshness
ImportanceHigh
ImpactHigh
Why it shapes the structureA status change must show within two seconds and a location within thirty. That forces a push channel, a normalized client cache, and a fan-out path that never touches the transactional store.
Correct assignments under concurrent dispatching
ImportanceHigh
ImpactHigh
Why it shapes the structureTwo dispatchers and an optimizer write the same rows. One writer of assignments, versioned writes, and conflicts returned as data follow from this driver alone.
Operate without the optimizer
ImportanceHigh
ImpactMedium
Why it shapes the structureThe manual board must never depend on the solver being up. The optimizer becomes an advisory component behind a versioned apply step.
Offline check-in with EVV integrity
ImportanceHigh
ImpactHigh
Why it shapes the structureThe phone is the only witness to when and where a visit began. A durable outbox, idempotent events, and two clocks are structural, not features.
Intraday repair within seconds
ImportanceHigh
ImpactHigh
Why it shapes the structureA cancellation at 10:42 cannot wait for a territory-wide solve. Planning splits into a nightly solve and a repair mode over a locked prefix.
PHI protection and tenant isolation
ImportanceHigh
ImpactMedium
Why it shapes the structureRow-level security keyed on the agency, channel authorization at subscribe time, telemetry scrubbed before it leaves, and vendors under agreements.
Agencies differ
ImportanceMedium
ImpactHigh
Why it shapes the structurePolicies, EMR vendors, and state EVV aggregators vary. The objective weights and the auto-apply rules become configuration; integrations become adapters behind one interface.
Patient messages that are right and rare
ImportanceMedium
ImpactMedium
Why it shapes the structureMessaging is a consumer of events the system already emits, with hysteresis and idempotent sends; it introduces no new source of truth.
Cost of location pings at scale
ImportanceMedium
ImpactLow
Why it shapes the structureThe one hot stream gets its own ingest path and a time-series store with a time to live. Nothing else in the system is a throughput problem.

Following the method's guidance to select five or six, the drivers rated high on both axes, board freshness, concurrent correctness, offline check-in, and intraday repair, together with operating without the optimizer and protecting PHI, are the architectural drivers that section 05 decomposes against. The rest are carried as requirements allocated to the elements that result.

03

Quality attribute scenarios

The Quality Attribute Workshop insists that a quality goal is not a requirement until it is a scenario with a stimulus and a measurable response, and its eighth step refines each important scenario into six parts: the stimulus, the source of the stimulus, the environment, the artifact stimulated, the response, and the response measure. Attribute-Driven Design requires the same six parts before design can start. The workshop also asks that three kinds be covered: use case scenarios for anticipated uses, growth scenarios for anticipated change, and exploratory scenarios for stresses nobody planned for.

Performance scenarios need one more input. The 1995 taxonomy of quality attributes separates the concerns, latency, throughput, capacity, and modes, from the factors that determine them, chiefly the demand placed on the system as arrival patterns and execution times. The demand for dispatch is small everywhere but one place.

Event stream or quantityEstimate · Consequence for the architecture
Active clinicians, peak weekday
Estimate50,000
Consequence for the architectureTerritories of 30 to 40 clinicians mean roughly 1,400 independent optimization problems each night.
Visits per day
Estimate300,000
Consequence for the architectureAbout six per clinician. Small for any database.
Visit status events
Estimate1.5 M/day ≈ 17/s
Consequence for the architectureA sporadic stream with a trivial rate. Postgres is the source of truth without argument.
Location pings, one per 30 s over an 8-hour shift
Estimate48 M/day ≈ 555/s, about 1,700/s at 8 a.m.
Consequence for the architectureThe one periodic stream that matters. It gets its own ingest path and never touches the transactional database.
Ping payload about 150 bytes
Estimate~7 GB/day · ~50 GB hot for 7 days
Consequence for the architectureA time-series store with a time to live; a per-visit path is summarized after check-out.
Concurrent dispatcher sessions
Estimate~2,000
Consequence for the architectureFan-out is per territory: 35 clinicians coalesced to about one message a second per client.
Travel-time matrix per territory
Estimate280 points → 78,000 pairs
Consequence for the architectureToo many to fetch live. Cache by grid cell and time-of-day bucket; live traffic only for the next hop.
Visit rows with their events
Estimate~2 KB each → ~220 GB/year
Consequence for the architecturePartition by month, index on tenant, territory, and date.

The scenarios below are the refined set. Each carries the six parts, its type, and the quality attribute it exercises. They are the leaves of the utility tree in section 04 and the test cases of the analysis in section 11.

ScenarioSource · Stimulus · Environment · Artifact · Response · Response measure
S1 · A check-in reaches the board
use case · performance
SourceA clinician in a driveway
StimulusTaps Arrived; a status event is recorded
EnvironmentNormal operation, 2,000 dispatcher sessions open
ArtifactIngest, event bus, realtime gateway, console
ResponseThe block and the adherence chip repaint on every subscribed console
Response measureUnder two seconds from check-in to paint at the 95th percentile
S2 · A marker follows a car
use case · performance
SourceThe clinician app's location service
StimulusA batch of up to ten GPS points every 30 seconds
EnvironmentMorning peak, 1,700 pings a second platform-wide
ArtifactTracking service, Redis, the map
ResponseThe marker moves and the arrival estimate updates
Response measureWithin 30 seconds; no more than one map update per clinician per frame
S3 · Two dispatchers, one visit
use case · correctness
SourceTwo dispatchers in the same territory
StimulusBoth reassign visit V-4821 within two seconds of each other
EnvironmentNormal operation
ArtifactScheduling service, assignment table, both consoles
ResponseOne write applies; the other is refused with who changed it and when, and that console rolls back its optimistic move
Response measureZero lost updates; the refused dispatcher sees the cause within one second
S4 · The optimizer is down at 8 a.m.
exploratory · availability
SourceA failed solver deployment
StimulusRepair requests time out
EnvironmentPeak dispatching
ArtifactOptimizer, console proposals panel
ResponseManual drag, publish, and tracking continue; proposals read unavailable; the nightly plan falls back to last week's pattern as a draft
Response measureZero blocked dispatcher actions; alert within one minute on solve duration
S5 · Check-in with no signal
use case · availability
SourceA clinician in a basement
StimulusTaps Arrived with no network
EnvironmentOffline for up to a whole shift
ArtifactThe phone's outbox and the visit event table
ResponseThe event is recorded locally with its GPS fix and device time, then delivered in order when the network returns and deduplicated by its client event id
Response measureDelivered within 30 seconds of reconnect; zero duplicates; zero lost check-ins across an app kill
S6 · The socket drops
exploratory · availability
SourceA realtime gateway restart, or a laptop that slept through a meeting
StimulusThe subscription connection closes
EnvironmentEvents continue to flow for the territory
ArtifactConsole subscription link, gateway replay buffer
ResponseThe console resubscribes after its last event id and the gateway replays the gap; beyond the buffer the board refetches once
Response measureZero missed status events; five minutes of replay; banner shows the state within one second
S7 · A cancellation ten minutes out
use case · performance
SourceA patient's text reply, or the office
StimulusA visit is cancelled
EnvironmentMid-morning, routes in progress
ArtifactOptimizer in repair mode, scheduling service, clinician app
ResponseFinished and in-progress stops are locked; a proposal with its reasons appears; the affected phone learns its route changed
Response measureProposal within five seconds; nightly solve within ten minutes per territory
S8 · A query crosses tenants
exploratory · security
SourceA defect or a hostile client
StimulusA request for another agency's visits, or a subscription to its territory
EnvironmentAny
ArtifactPostgres row-level security, gateway authorization, telemetry
ResponseZero rows returned; the subscription is refused; the attempt is logged with identifiers only
Response measureOne policy per table with one test per table; no PHI in logs or error reports, ever
S9 · A new EMR vendor
growth · modifiability
SourceA newly signed agency
StimulusVisit orders must arrive from an EMR the platform has not seen
EnvironmentDevelopment
ArtifactIntegration adapters
ResponseOne adapter is added behind the existing interface; no other module changes
Response measureUnder two person-weeks; one module modified
S10 · A regional board
exploratory · performance
SourceA regional manager
StimulusOpens a view of 400 clinicians and 3,000 visit blocks
EnvironmentAn office laptop
ArtifactThe board, the map, the derived selectors
ResponseRows virtualize, conflict detection moves to a worker, the map clusters
Response measureInteraction under 100 ms; drag at 60 frames a second; first paint under three seconds
S11 · An arrival estimate wobbles
use case · correctness
SourceTraffic
StimulusAn estimate oscillates between 9:52 and 10:08 for half an hour
EnvironmentThe patient has already had a morning window
ArtifactNotification service
ResponseNo message until the window moves by more than fifteen minutes, and then at most once an hour
Response measureAt most one drift message per patient per hour; every message logged in the visit drawer
04

The utility tree

The fifth step of the Architecture Tradeoff Analysis Method arranges the scenarios as a utility tree: utility at the root, the quality attributes beneath it, refinements beneath those, and scenarios at the leaves, each leaf rated twice, for importance to the success of the system and for the difficulty or risk of achieving it, on a high, medium, low scale. The tree tells the evaluation where to spend its limited time. Here it is for dispatch, with the leaves numbered as in section 03.

  • Performance
    • Freshness of the board: S1, status to paint (H, H); S2, location to marker (H, M).
    • Responsiveness of the console: S10, the regional board (H, M).
    • Latency of repair: S7, a cancellation ten minutes out (H, H).
  • Availability
    • Degraded modes: S4, the optimizer is down (H, M).
    • Connection loss: S6, the socket drops (H, M); S5, check-in with no signal (H, H).
    • Vendor outage: the messaging vendor is down, from the walkthroughs (M, M).
  • Correctness and usability
    • Concurrent edits: S3, two dispatchers, one visit (H, H).
    • Honesty of the display: a marker that says how old it is, from the walkthroughs (H, L).
    • Message discipline: S11, the wobbling estimate (M, M).
  • Security
    • Tenant isolation and PHI in telemetry: S8 (H, M).
    • PHI on a lost phone: remote revocation and cache purge, from the walkthroughs (H, L).
  • Modifiability
    • Integrations: S9, a new EMR vendor (M, H).
    • Policy: a new agency's auto-apply rules and objective weights as configuration (M, M).
    • Compatibility: phones that update weeks late against an additive schema, from the walkthroughs (M, M).

Five leaves are rated high on both axes: S1, S3, S5, S7, and the pairing of S1 with S6 that makes the realtime path both fast and resumable. The deep dives in sections 06 through 10 follow those leaves, and the analysis in section 11 records what was found at each.

05

Design concept: patterns and tactics

The fourth step of Attribute-Driven Design chooses a design concept: the major types of elements that will appear in the architecture and the relationships among them, selected from patterns and from architectural tactics that address each driver's design concerns. The fifth step instantiates those types and allocates responsibilities; the sixth defines interfaces as the provides and requires assumptions elements make about one another. A tactic, in the definition of Bachmann, Bass, and Klein, is a design decision that helps achieve a specific quality attribute response and that is motivated by an analysis model for that attribute. Patterns package several tactics; the tactic is the unit that can be analyzed.

The concept

Five patterns compose the design. A layered client-server arrangement puts a GraphQL gateway between every surface and every service, so authentication, tenant resolution, rate limiting, and idempotency live in one place. A publish-subscribe event bus carries facts, never commands: services publish what happened, and the realtime gateway, the push service, the notification service, and analytics consume. A single transactional writer, the scheduling service on Postgres, owns visits, assignments, and plan versions. A separate compute-bound process, the optimizer, proposes plans and repairs but applies nothing itself. Per-territory channels carry live events to consoles, authorized once at subscribe time.

The decisions that make those patterns meet the drivers are tactics from the Software Engineering Institute catalogs, named in the table. The modifiability and performance tactics are those of Bachmann, Bass, and Klein (2002, 2003); the availability tactics are the refined catalog of Scott and Kazman (2009); concurrency control follows the treatment in Carnegie Mellon's 15-445.

DecisionTactic · Attribute · Analysis model
An event bus of facts between services
TacticBreak the dependency chain with an intermediary: a publish-subscribe pattern removes the consumer's dependence on the producer's identity
AttributeModifiability
Analysis modelDependency analysis: a new consumer adds no dependency to any publisher
One writer of assignments, with a version on every row
TacticTransactions, and optimistic concurrency control: conflicts are assumed rare and validated at commit rather than prevented with locks
AttributeCorrectness and availability
Analysis modelSerializability: the write-write conflict called the lost update in 15-445 is the failure this prevents
Conflicts returned as data, not exceptions
TacticException handling with structured exception information: who changed the row, when, and to what
AttributeAvailability and usability
Analysis modelFault correlation: the console can act on the cause without parsing a message
Pings ingested on their own path, never through Postgres
TacticIncrease physical concurrency and balance resource allocation: the one periodic stream gets its own resources
AttributePerformance
Analysis modelQueuing: 1,700 arrivals a second at peak kept off the queue that serves 17 writes a second
Location coalesced, status never
TacticManage the event rate and control the frequency of sampling external events; forward a location only when it moved more than 50 meters or its estimate drifted more than two minutes
AttributePerformance
Analysis modelQueuing: per-client arrival rate falls to about one message a second
A five-minute replay buffer and a resume cursor
TacticBound queue sizes; rollback to a checkpoint and state resynchronization on reconnect
AttributePerformance and availability
Analysis modelRecovery time: a resume costs one request; a refetch costs one page load
Heartbeats on every socket
TacticSystem monitor with a heartbeat; detection time is the heartbeat period times the missed count
AttributeAvailability
Analysis modelFault detection latency versus heartbeat overhead
The manual board independent of the optimizer
TacticDegradation: the solver can be removed from service while dispatching continues; escalating restart applies to the solver alone
AttributeAvailability
Analysis modelSteady-state availability of dispatching is decoupled from the solver's mean time to repair
A durable outbox on the phone with client event ids
TacticTransactions for the local write; checkpoint-based rollback across an app kill; at-least-once delivery made exactly-once by idempotent receipt
AttributeAvailability and correctness
Analysis modelDelivery semantics from 15-440: retry without identity duplicates, retry without durability loses
Device time recorded beside receipt time, with a skew flag
TacticException detection by sanity checking: a difference above five minutes is flagged, not trusted
AttributeCorrectness
Analysis modelClock synchronization from 15-440: physical clocks drift; the record keeps both readings
Row-level security keyed on the agency
TacticAuthorize at the storage boundary; one policy per table, one test per table
AttributeSecurity: confidentiality and integrity
Analysis modelEvery path to a row passes one check that cannot be forgotten by a caller
Objective weights and auto-apply rules as configuration
TacticRaise the abstraction level: a module general enough that anticipated change arrives as input, not code
AttributeModifiability
Analysis modelA new agency policy modifies zero modules
Integration adapters behind one interface
TacticUse an intermediary, with information hiding of each EMR's syntax and semantics
AttributeModifiability
Analysis modelA new vendor is one module added, none changed
A normalized client cache that all views read
TacticHide information and use an intermediary on the client: views depend on the cache, not on each other or on transport
AttributeModifiability and correctness
Analysis modelOne event updates one entity; the number of modules that can disagree is zero
Virtualized rows and a worker for conflict detection
TacticControl the demand for resources: render only what is visible; increase logical concurrency: move heavy work off the painting thread
AttributePerformance
Analysis modelFrame budget: 16 milliseconds a frame, held at 3,000 blocks
DispatcherconsoleReact · TS · Apollo Clinician appReact Native Agency EMR ·partner systemsEpic · athena · WellSky Realtimegatewaygraphql-ws · SSE GraphQLgatewayauth · tenant · limits IntegrationadaptersFHIR/HL7 · webhooks boardEvents queriesmutations idempotentmutations · pings FHIR / HL7webhooks Tracking & ETAingest pings · adherenceETA to next stop RedisGEO · presence Ping history7-day TTL Scheduling servicevisits · assignments · planssource of truth PostgresRLS by tenant · audit Notificationstemplates · quiet hourstwo-way replies Messaging vendorSMS · voice → patient OptimizerVRPTW solve nightlyrepair in seconds Maps & trafficmatrix · geocode orders innotes out board · reassignpublish pings · check-ins proposeapply Event bus · Kafka or SNS + SQS assignment.changed · visit.status clinician.located · eta.changed plan.published · patient.replied consumes repair triggers located · eta publishes territory channels Analyticswarehouseutilization · drive time nightly extract Push (APNs / FCM)→ clinician appno PHI in the body assignment.changed Datadog · SentryPHI scrubbed · RUM
Component-and-connector view. Solid boxes are the platform's own elements; dashed boxes are rented or downstream. The highlighted connectors, console to realtime gateway to event bus, are the path the freshness scenarios follow. The scheduling service is the only writer of assignments; everything else learns about them from the bus.

Three allocations of responsibility follow from the drivers and are worth stating as decisions in the sense of the method's fifth step. Pings never touch Postgres: they land in Redis for the latest position and in a time-series table with a seven-day time to live; the transactional database sees a visit's path only as a summary after check-out. The bus carries facts, not commands: nobody instructs the scheduling service over it, and commands arrive through the gateway with an actor and a version. The console holds two connections on purpose: queries and mutations over HTTP, where retries and caching are simple, and events over a socket that the page can lose without losing the page.

Agency (tenant) idname · timezonepolicy:auto-apply limitssso config Territory id · agency_idnamegeofence (polygon)depot / home base Clinician id · agency_idhome_base geoskills[] · credentials[]credential expiryshift start/end · breaksmax_drive_min PlanVersion id · territory_idservice_datestatus draft|publishedsolver_run_idobjectivecreated_by · created_at Assignment id · visit_idclinician_idplan_version_idseq (stop order)planned_start · endversion ←optimistic lockupdated_by · updated_attravel_from_prev_min Visit id · patient_idterritory_idservice_typeduration_minwindow_startwindow_endrequired_skills[]status (state machine)source EMR|API|manualexternal_ref(EMR order id)recurrence rulepriority · notes Route (derived) clinician_idservice_dateplan_version_idstops[] orderedmaterialized fromassignments VisitEvent (append-only) id · visit_id · typeactor clinician |dispatcher | systemdevice_timereceived_atgps lat/lng · accuracyclient_event_id(idempotent)plan_version_idskew_flag: |Δt| > 5 min LocationPing clinician_id · tslat · lng · accuracyspeed · headingbatteryTTL 7 days · time-series Patient id · agency_idaddress · geo (geocoded)phone · consent_smsaccess notes (PHI)preferred clinicians(continuity)preferred windows AuditEntry id · tenant_id · atactor · actionentity · entity_idbefore → after (diff)prev_hash · hashappend-only Notification id · patient_idvisit_idchannel sms|voicetemplate_idfirst name onlyscheduled_for · sent_atdelivery_statusreply rawparsed intent one plan perservice day clinician_id 1..n n..1 materialized per clinician-day append-only history patient_id 1..n notifications
Module view of the data. The highlighted fields are the two that carry correctness: the assignment version rejects stale writes, and the client event id lets a phone retry a check-in indefinitely without recording it twice. Pings reference a clinician but live in their own store. The audit table is hash-chained so a dispute can prove nothing was edited after the fact.

Interfaces

Attribute-Driven Design's sixth step defines interfaces as the services and properties each element provides and requires, including semantics, information exchanged, quality attribute requirements, and error handling, not signatures alone. The interface between the surfaces and the platform is a GraphQL schema, in the dialect of the chosen stack. Three properties of it carry requirements: mutations from phones carry an idempotency key, mutations from dispatchers carry the version they last saw, and the subscription takes a cursor so a reconnecting client can ask for what it missed.

type Query {  # rows, assignments, live status  board(territoryId: ID!, date: Date!): Board!  # exactly what the phone shows  route(clinicianId: ID!, date: Date!): Route!  # optimizer suggestions awaiting a decision  proposals(planVersionId: ID!): [Proposal!]!} type Mutation {  reassignVisit(input: ReassignVisitInput!):    ReassignVisitPayload!  applyProposal(    proposalId: ID!, expectedVersion: Int!  ): ApplyProposalPayload!  publishPlan(planVersionId: ID!):    PublishPlanPayload!  # check-in, check-out, from the phone  recordVisitEvent(input: VisitEventInput!):    VisitEventPayload!  cancelVisit(    visitId: ID!, reason: CancelReason!  ): CancelVisitPayload!} type Subscription {  boardEvents(territoryId: ID!, date: Date!,    after: Cursor): BoardEvent!} union BoardEvent =  | AssignmentChanged | VisitStatusChanged  | ClinicianLocated | EtaChanged  | PlanPublished | ProposalReady input ReassignVisitInput {  visitId: ID!  toClinicianId: ID!  position: Int  expectedVersion: Int!} input VisitEventInput {  visitId: ID!  type: VisitEventType!  clientEventId: ID!  deviceTime: DateTime!  gps: GeoPointInput} # errors as datatype ReassignVisitPayload {  assignment: Assignment  conflict: VersionConflict} type VersionConflict {  currentVersion: Int!  changedBy: Actor!  changedAt: DateTime!}
  • Errors as data. A version conflict is a normal outcome of two people dispatching, so it returns as a typed field the console can render, naming who changed the row and when, rather than as an error the client would have to parse.
  • One day-scoped board query. The console loads one territory-day in one request; everything after that arrives as events. The normalized cache stays small and the subscription filter stays obvious.
  • Partners get REST and webhooks. Visit creation over an idempotent endpoint, completion delivered by signed webhooks with retries. GraphQL is reserved for the platform's own surfaces, where the client is under the same control as the server.
06

Deep dive A · Planning tonight, repairing at 10:42

The routing problem underneath dispatch is vehicle routing with time windows. In the constraint-programming treatment presented by van Hoeve at the Tepper School of Business, each visit is an activity with a start variable and a duration, each vehicle is a unary resource on which activities cannot overlap, travel times are sequence-dependent setup times between activities, and a time window bounds each start. Two facts from that work shape the design. Adding time windows makes the problem far harder than plain routing: exact methods handle on the order of a hundred locations. And constraint-based local search, which first constructs a feasible solution by a savings, sweep, or insertion heuristic and then improves it with moves that cut and reconnect a route, relocate a visit, or exchange visits between routes, finds good solutions to large instances within about a second.

One solver therefore cannot serve both jobs. The nightly plan is the whole territory's problem, allowed minutes and thorough search. The intraday repair is a local-search pass over the few visits still unlocked, allowed seconds, run while a dispatcher waits with a phone in hand.

Nightly plan · minutesbatch · 17:00 for tomorrow Intraday repair · secondsevent-driven · locked prefix Visits + constraintswindows · skills · shifts Travel-time matrixcached · time-bucketed78k pairs · ~95% cachedreused by intraday repair VRPTW solveOR-Tools · local searchsoft continuity penalty2–8 min · 1,400 in parallel Draft plan v1assignments · sequenceobjective + violations Dispatcher reviewedits → v2, v3 …each edit is a versioned write Publishplan.publishedstatus → published Routes to phones+ patient confirmations Triggercancel · running lateurgent add · no-show Lock prefixdone + in-progressstops are fixed Repairinsertion + 2-opt≤ 5 s · same territoryunlocked stops: usually 3–12 Proposaldeltas + why“+12 min drive, 0 late” Policy gatesmall → auto-applyelse → dispatcher Apply vN+1expectedVersionassignment.changed Cascade ETAsevery later stopeta.changed Notifypatients if drift > 15 minand the clinician accept · edit · reject
Process view of planning. The nightly lane is allowed to be slow and thorough. The intraday lane never re-solves the territory: it fixes everything that has already happened, moves a handful of remaining visits with local-search moves, and returns a proposal with a plain-language cost. Both lanes write through the same versioned apply step, so a dispatcher's manual drag and an optimizer's repair cannot silently overwrite each other.

Decisions in the planning element

  • Objective. Minimize drive time and late arrivals, penalize breaking continuity of care, forbid skill and window violations as hard constraints. Agencies weight these differently, so the objective is configuration, not code: the raise-the-abstraction-level tactic applied to the solver.
  • Travel-time matrix. The expensive input. It is cached by origin cell, destination cell, and time-of-day bucket, refreshed weekly, and queried live only for the next hop when an arrival estimate is computed. When the maps provider is unavailable, straight-line distance times a road factor stands in and the plan is marked as estimated travel.
  • Explainability. Every proposal carries the constraint that forced it and the alternatives it rejected with their cost: which window would have closed, which credential was missing. A dispatcher who cannot see the reason overrides the proposal, and the override becomes a soft constraint the nightly plan learns from.
  • Determinism. The solver is seeded and the run identifier is stored with the plan version, so a plan can be reproduced when someone asks why a patient was moved.
  • Policy gate. Agencies set what auto-applies: a reorder that adds under five minutes of drive and moves no window may apply itself; a reassignment to a different clinician waits for a dispatcher. Clinical judgment stays human by configuration.
07

Deep dive B · From a phone in a driveway to a marker on the board

Scenarios S1, S2, and S6 trace this path. In the 1995 taxonomy's terms, the concern is latency and the factors are two event streams with different arrival patterns: status events, sporadic and rare, and location pings, periodic and plentiful. The performance tactics apply to the second stream and the availability tactics to the connection that carries both. The gateway pushes per territory, coalesces location, never coalesces status, and stamps every event with a monotonically increasing identifier so that a client can resume.

Clinicianapp Ingestgateway Eventbus Tracking& ETA Redis Realtimegateway Console ping batch ≤ 10 pts / 30 s JWT · device time · accuracy clinician.located consume (partition = territory) GEOADD latest · last_seen replay buffer (5 min) recompute ETA to next stoponly if moved > 50 m or drift > 2 min eta.changed · located (coalesced) fan-out to channel territory:LA-West-3:2026-09-10 push id: 8812 cache.modify(Clinician:c17)marker moves · adherence chiprecord event-to-paint ms if the socket drops subscribe after: 8812 XRANGE 8812.. from replay buffer buffer exhausted → refetch board query (cache-and-network) · banner: “reconnected, refreshed”
Behavior view: field to board. Status events take the same path without the coalescing step. Two response measures govern it: under two seconds from check-in to paint at the 95th percentile, and a five-minute replay buffer so a laptop that slept through a meeting catches up with one request instead of a full reload.

Why the gateway owns the channel

Clients subscribe to a territory-day and nothing finer. The gateway checks the subscriber's tenant and territory permissions once at subscribe time, then pushes everything on that channel; filtering per clinician on the client is cheap and keeps the authorization surface small. Presence, who is viewing the territory and who is dragging what, rides the same channel, which is what makes the two-dispatcher conflict in Deep dive C visible before it happens.

The transport as a tradeoff point

The chosen stack lists both WebSockets and server-sent events. The Architecture Tradeoff Analysis Method would call the transport a tradeoff point: a single decision to which more than one attribute is sensitive. Freshness and duplex messaging favor the socket; simplicity of infrastructure and behavior behind proxies favor the one-way stream; polling loses on both freshness and load.

Pollingevery 5 s client server mostly empty responses · up to 5 s stale · trivial infra Server-sent eventsone HTTP stream client server GET /events server → client only · auto-reconnect with Last-Event-ID plain HTTP/2, proxy-friendly · mutations still go over HTTP WebSocketgraphql-ws client server pingack full duplex · heartbeat + resume cursor · one socket per board
Transports compared. The design uses GraphQL subscriptions over WebSocket with the graphql-ws protocol as the primary transport and server-sent events as the fallback, with no polling. Because the board needs only server-to-client events, the fallback is a real one rather than a degraded one. Both need heartbeats, exponential backoff with jitter, and the resume cursor from the figure above.
08

Deep dive C · The dispatcher console

The console is a board of clinician rows against a time axis, a map beside it, a drawer for the selected visit, and a strip of alerts. A large territory is 40 rows with 250 blocks; a regional view, scenario S10, is 400 rows and 3,000 blocks. Attribute-Driven Design applies to the client as it does to the server: the module view below is the result of choosing tactics for four drivers. One cache for server truth, with events written straight into it, is the information-hiding tactic. Rendering only what is visible is the tactic of controlling demand for resources. A worker for conflict detection is increased logical concurrency. And an interface that says when it is not sure is the honesty the display owes the dispatcher.

GraphQL APIHTTPS · idempotency · versions Realtime gatewayterritory channel · replay queries · mutationsexpectedVersion on every write boardEvents(after: cursor) Apollo Client · one normalized cacheone source of server truth HTTP linkqueries · mutationsretry · auth WS link · graphql-wsSSE fallback · heartbeatbackoff · cursor results modify InMemoryCache Clinician:c17 {lastSeen, eta, pos} Visit:V-4821 {status: EN_ROUTE} Assignment:a903 {version: 8, seq: 3} ROOT_QUERY.board(LA-West-3, 09-10) typePolicies · keyFields · merge optimistic layer on top, rolled back on error never persisted to disk on web: it holds PHI Error linkVersionConflict is data · Sentry, scrubbed Derived, memoizedselectors rows by clinicianblock x = minutes since06:00 × px per minuteconflicts: overlap · skill· window · shiftadherence = planned vsactual · header counts Web Workerlayout +conflictsabove 1,000blockspostMessagediffs UI statezustand (or Redux)selection · dragfilters · panelconnection:live | reconnecting| stale useQuery + useSubscription socket Boardvirtualized rows(react-window)dnd-kit · pointer+ keyboardblocks absolutelypositioned60 fps drag, nolayout thrash MapMapbox GL ·feature-statemarker updatesbatched per rAFclusters above200 pointsonly the territoryin view Detail drawervisit timeline · EVVproposal diff+ reasonsnotes hand-offstatus Alerts & statusaria-live regionconflict toastsstale banner Presencewho is viewingwho is draggingwhat status · toasts peers reassignVisit · optimisticResponse · expectedVersion
Module view of the client. Server truth lives in the Apollo cache and nowhere else; interface state lives beside it and never duplicates a server field. Subscription events write into the same cache the queries filled, so the board, the map, and the drawer cannot disagree. The highlighted loop is a drag: the board writes an optimistic assignment, the server confirms or returns a conflict, and the cache resolves either way.

Why a normalized cache and not a plain store for server data

The hard problem is identity. A visit appears in a board row, on the map, in the drawer, and in an alert. With a cache normalized by type and identifier, one subscription event updates one entity and every view that reads it re-renders, with no reducer to keep in step. A conventional store keeps interface state: selection, drag ghost, filters, panel layout, connection status. Kept apart, the two can never hold a stale copy of the same server field.

How events land

The subscription handler switches on the event type and modifies the affected entity in the cache, or updates the board query when an assignment moves between rows. Location events are throttled on the client as well, coalesced per clinician and flushed once per animation frame, so 35 clinicians updating cannot cause 35 renders in a burst. Status events bypass the throttle and apply immediately.

The rendering budget

  • Rows are virtualized. A 400-row regional view renders about 25. Row height is fixed so scrolling is arithmetic.
  • Blocks are absolutely positioned by minutes since the day start times pixels per minute, inside a row that is one flex container. Dragging updates a transform on the ghost only; nothing else lays out until drop.
  • Conflict detection moves to a worker above about a thousand blocks: overlap, skill mismatch, window violation, shift overrun. The worker returns a diff of conflict flags, not a new tree.
  • The map batches. Marker positions go through the map library's feature state in one call per frame, clustering begins above 200 points, and off-screen territories are not subscribed at all.
  • Budgets, stated. Interaction under 100 milliseconds, drag at 60 frames a second, event to paint under 500 milliseconds on an office laptop, first board under three seconds on a cold load.
Ana's console Scheduling API Theo's console reassignVisit(V-4821 → Priya, expectedVersion: 7) optimistic: block already moved assignment { version: 8 } event AssignmentChanged v8 Theo started dragging at v7,2 s earlier reassignVisit(V-4821 → Marcus, expectedVersion: 7) conflict { currentVersion: 8, changedBy: Ana } optimistic layer dropped →block snaps back to Priyatoast: “Ana moved V-4821 to Priya4 s ago” · Reapply? UPDATE assignment SET clinician_id=…, version=8 WHERE id='a903' AND version=7 → 0 rows → conflict
Behavior view: two dispatchers, one visit. Optimistic concurrency control, not locks. The database refuses the stale write in the same statement that would have applied it, the API returns the conflict as data, the client discards its optimistic layer, and the interface tells the second dispatcher what happened and offers to redo the move on top of the new state. Presence in the header makes this rare; the version makes it safe.

Concurrency control, by the book

Carnegie Mellon's 15-445 divides concurrency control protocols into pessimistic ones, which prevent conflicts from arising, and optimistic ones, which assume conflicts are rare and deal with them when they happen. The failure both guard against is the write-write conflict, the lost update, in which one transaction overwrites another's change. Dispatching has rare conflicts and long-lived intentions: a dispatcher may hold a drag for seconds, and a laptop may sleep holding a lock forever. The optimistic protocol fits. The version on the assignment row is the validation: a write that names a version no longer current is refused in the same statement that would have applied it, and no lost update is possible.

Honesty about staleness

The connection is a small state machine: live, reconnecting after a missed heartbeat, stale after 30 seconds without a resume. The banner names the state and the age of the data. Each clinician marker carries its own last-seen time, so a phone that died in a basement shows as last seen twelve minutes ago, in amber, rather than as a confident dot in the wrong driveway. Adherence chips are computed from planned versus actual, and a block whose actual start is unknown is drawn hollow, not green.

Accessibility that survives a busy office

Every drag has a keyboard path: select a block, press a key, pick a row, confirm. Critical alerts, a missed check-in or a conflict, go to an assertive live region; location updates never do. Status is never color alone: the late block carries its text and the cancelled block its hatch. Contrast is checked in every page tone, because dispatch offices run dark screens at 6 a.m.

Testing and observability

Components are tested against mocked GraphQL handlers, including a subscription mock that replays a recorded morning. Drag and keyboard reassignment run in a browser harness. Types are generated from the schema, so a schema change fails the build rather than the dispatcher. In production, real-user monitoring records event-to-paint latency and long tasks; error reports arrive with PHI stripped before they leave the browser, and the URL never carries a patient name because routes use identifiers.

Sharing with the phone

The schema, the generated types, the data hooks, and the domain selectors are one package that both applications import. The screens are not shared. The phone needs a list of stops and two large buttons; the console needs a board. The shared layer is where the visit state machine lives, so both surfaces agree on what a transition means.

09

Deep dive D · The clinician app when the signal is gone

The phone is the source of electronic visit verification facts and of nothing else. It does not decide assignments; it records what happened to the visit it was given, with a GPS fix and a timestamp, and it must do that in a basement. Scenario S5 is the driver. The design is a visit state machine that runs locally and an outbox that delivers its transitions in order when the network returns, which is the availability catalog's transactions tactic for the local write and its checkpoint-based rollback for survival across an app kill.

Scheduled En route Arrived In progress Completed Reassigned Cancelled Missed · no-show tap Start · pings every 15 s tap Arrived → EVV:GPS + time tap Begin tap Complete → notes to EMR server-initiated:assignment.changedpush wakes the app“route changed” cancel: patient,office, or EMRwhile driving:notify + repair no answer after 15 minEVV attemptstill recorded pings slow toevery 2 min in a visit terminal ·path summarized Outbox · ordered, durable, idempotent ev-1 EN_ROUTE 08:31acked ev-2 ARRIVED 09:02 +gpsacked ev-3 IN_PROGRESS 09:04sending · retry 2 ev-4 COMPLETED 09:58queued · offline Serverdedupe byclient_event_idreceived_at ·skew flag SQLite or MMKV · survives an app kill · strictly in order exponential backoff · the next stop's buttons keep working while ev-3 retries
Behavior view: the visit state machine and the outbox. Every tap becomes an event with a client-generated identifier, the device time, and a GPS fix, appended to a local queue before the interface updates. The server treats the identifier as the identity of the event, so a retry after a timeout can never double-record a check-in, and it stores its own receipt time beside the device time so clock drift is visible rather than silently trusted.

Decisions in the mobile element

  • Cache the day, not the world. At shift start the app pulls its route and caches it in encrypted storage. If the network dies at 8:05 the whole day is still on the phone.
  • Delivery semantics. 15-440 teaches that a network can deliver a message zero, one, or many times, and that the receiver decides what a retry means. The outbox retries with exponential backoff, strictly in order, and the server deduplicates by client event id, turning at-least-once transport into exactly-once recording.
  • Two clocks. Physical clocks drift and are set wrong. The record keeps the device time and the server's receipt time, flags a difference above five minutes, and lets the verification record carry both rather than choosing.
  • Location is adaptive. Every 15 seconds en route, every two minutes during a visit, off outside the shift, in batches of up to ten points every 30 seconds. This is the control-the-sampling-frequency tactic applied to a battery: the difference between a usable phone at 4 p.m. and a dead one.
  • Reassignment while offline. If the server moved a visit to another clinician at 9:00 and the original clinician, offline, checked into it at 9:02, the server keeps her verification record, marks the visit as an exception, and puts it on the dispatcher's board. Nobody's evidence is discarded.
  • PHI on a phone. Biometric unlock, no patient names in push notification bodies, redacted application-switcher snapshots, tokens in the platform keystore, and a remote wipe that revokes the refresh token and purges the cache on next launch.
10

Deep dive E · What the patient hears, and when

Every message is triggered by an event the system already produces, so the notification service is a consumer with templates and rules, not a new source of truth. Scenario S11 governs its discipline. The tactics are the performance catalog's: manage the event rate through hysteresis, bound the queue through a time to live, and make sends idempotent so a replayed event cannot text twice.

Plan publishedReminderMorning windowDrift > 15 minArriving soonAfter the visit T−1 day · 17:00T−1 day · 18:3007:3009:35 · eta.changedETA ≤ 10 min+1 hour Confirm Wed 9–11 am visit.Reply C to confirm, R to move. Reminder: your nurse Priyacomes tomorrow, 9–11 am. Priya arrives today9:40–10:10. Running behind. New window10:00–10:30. Sorry. Priya is about 10 minutesaway. How was your visit?Reply 1–5. Reply R → patient.replied → ProposalReady on the board Rules · quiet hours 21:00–08:00 · first name only never a diagnosis or the address echoed back · STOP honored at most one drift message per hour · delivery receipts stored voice call after two undelivered texts every message visible in the drawer
Messages compared in time. Six messages, each tied to an event the platform already emits. Hysteresis matters: an estimate that oscillates between 9:52 and 10:08 must not produce six texts, so drift messages fire only when the window moves by more than fifteen minutes and at most once an hour. The dispatcher sees the same log in the visit drawer, so the question of what the patient was told always has an answer.
  • Idempotent sends. The key is the visit, the message type, and the window. A retried event cannot send twice.
  • Consent is data. SMS requires opt-in under the Telephone Consumer Protection Act; the patient row carries it, and the notification service refuses to send without it.
  • Reply parsing is deliberately simple. Single letters and digits; everything else is routed to a person. A free-text reply about a hospitalized spouse is not a parsing problem.
  • Vendor outage. Messages queue with a time to live; a morning window that cannot be delivered by 9:00 is dropped and the dispatcher is told, rather than sent at 10:15 when it is wrong.
11

Analysis: sensitivity points, tradeoff points, risks, and non-risks

The Architecture Tradeoff Analysis Method produces four kinds of finding. A sensitivity point is a property of one or more elements that is critical to achieving a particular quality attribute response. A tradeoff point is a property that is a sensitivity point for more than one attribute, so that changing it improves one response and worsens another. A risk is an architecturally important decision not yet made, or made without its consequences understood. A non-risk is a good decision that rests on assumptions which must remain true. The method asks that each be recorded with the decision, the response it affects, and the rationale.

Sensitivity points

PropertyResponse it governs · Rationale
Granularity of the realtime channel: one territory-day
Response it governsFan-out cost and the size of the authorization surface
RationaleFiner channels multiply subscriptions and authorization checks; coarser ones push more events to clients that filter them away.
Version granularity: one version per assignment row
Response it governsThe conflict rate dispatchers experience and the impossibility of lost updates
RationaleVersioning the whole plan would refuse unrelated edits; versioning below the row could let a lost update through.
Coalescing thresholds: 50 meters or two minutes of drift
Response it governsMarker freshness against bandwidth and render load
RationaleLower thresholds approach forwarding every ping; higher ones let a marker lag a moving car.
Replay buffer length: five minutes
Response it governsWhether a reconnect costs one request or one page load
RationaleMemory per territory grows linearly with the buffer; most disconnections are far shorter.
Matrix cache bucketing: grid cell and time-of-day bucket
Response it governsArrival-estimate accuracy against maps-provider cost
RationaleFiner buckets cost more requests; coarser ones misjudge rush hour.
The locked-prefix rule in repair
Response it governsRepair latency against plan quality
RationaleLocking more of the day shrinks the search and speeds the answer; unlocking more finds better routes and unsettles clinicians.
Hysteresis: fifteen minutes and once an hour
Response it governsAccuracy of what the patient is told against how often
RationaleTighter thresholds tell the truth more often and get ignored sooner.

Tradeoff points

DecisionChosen · Over · Attributes in tension
Server state on the client
ChosenA normalized cache keyed by type and identifier
OverSlices of server data in a conventional store
Attributes in tensionCorrectness of identity against familiarity; the store keeps interface state only.
Transport
ChosenWebSocket with graphql-ws, server-sent events as fallback
OverPolling
Attributes in tensionFreshness and load against infrastructure simplicity; the board's one-way needs make the fallback real.
Concurrent edits
ChosenOptimistic version numbers
OverRow locks or a check-out of a row
Attributes in tensionAvailability against the cost of an occasional redo; a sleeping laptop leaves a lock behind, a version fails fast and explains itself.
Planning model
ChosenNightly solve plus intraday repair over a locked prefix
OverContinuous re-optimization
Attributes in tensionPlan quality against clinician trust; a route that keeps changing stops being followed.
Source of truth
ChosenPostgres rows plus an append-only event table
OverFull event sourcing
Attributes in tensionAuditability against query simplicity; rows stay queryable, events stay complete.
Service shape
ChosenA modular monolith with the optimizer and tracking split out
OverMicroservices everywhere
Attributes in tensionIndependent scaling against operational cost; only those two elements have a different load profile.
Location updates
ChosenCoalesced on the server and per frame on the client
OverForwarding every ping
Attributes in tensionFreshness against bandwidth and render cost; status is never coalesced.
Subscription scope
ChosenOne territory-day channel
OverPer-entity subscriptions
Attributes in tensionSecurity surface and socket count against message volume; client-side filtering is cheap.
Board rendering
ChosenVirtualized rows, absolutely positioned blocks
OverA grid-layout timeline
Attributes in tensionPerformance against layout convenience; a grid relayouts the whole track on every change.
Mobile writes
ChosenA durable outbox with client event ids
OverRetry on failure
Attributes in tensionCorrectness against implementation cost; retries without identity duplicate, retries without durability lose.

Risks and their mitigations

RiskConsequence · Mitigation
The optimizer is down or slow
ConsequenceProposals unavailable; the nightly plan is late
MitigationThe manual board never depends on the solver. The nightly plan falls back to last week's pattern as a draft. Alert on solve duration, not only on failure.
The maps provider rate-limits or fails
ConsequenceArrival estimates marked as estimated; drive times slightly off
MitigationThe cached matrix serves about 95 percent of lookups; straight-line distance times a road factor covers the rest; live traffic is used only for the next hop.
The realtime gateway restarts
ConsequenceA banner reads reconnecting, then live
MitigationClients resume with the last event id; the gateway replays from a five-minute buffer; beyond that the board refetches once.
The event bus lags
ConsequenceThe board shows data age; status still saves
MitigationWrites go to Postgres first, so nothing is lost. Past 30 seconds of lag the console switches to periodic refetch until the socket catches up.
A phone dies in a basement
ConsequenceA marker turns amber with its last-seen time
MitigationAn adherence alert fires ten minutes past the planned start; the outbox delivers the check-in when the phone returns.
The messaging vendor is down
ConsequenceThe dispatcher sees how many patients were not notified
MitigationMessages queue with a time to live; a morning window that cannot go out by 9:00 is dropped rather than sent late; a voice call follows two undelivered texts.
The EMR integration fails
ConsequenceVisits still flow; completed visits show notes pending
MitigationOutbound notes go through an outbox with retries and a nightly reconciliation; an integration health tile tells the office what is stuck.
A device clock is wrong
ConsequenceA check-in flagged for review
MitigationDevice time and server receipt time are both stored; a difference above five minutes is flagged; the verification record keeps both.
The database fails over
ConsequenceA few seconds of failed saves, then retries succeed
MitigationMulti-zone deployment with a synchronous replica; every write carries an idempotency key or a version, so a retry is safe; reporting reads from replicas.
A console deploy is bad
ConsequenceDispatchers switch back to the previous board
MitigationA per-tenant kill switch; the old bundle stays cached; error volume per release is watched against a budget.

Non-risks, and the assumptions they rest on

DecisionAssumption that must hold
Pings bypass the transactional database
Assumption that must holdPeak ping arrival stays near 1,700 a second and a seven-day history suffices; a tenfold increase revisits the ingest path, not the design.
One writer of assignments
Assumption that must holdStatus and assignment writes remain on the order of tens a second; the single writer is a correctness choice, not a capacity limit, until that changes.
One channel per territory-day
Assumption that must holdTerritories stay at roughly 30 to 40 clinicians; a territory of 400 would need finer channels or heavier client filtering.
Server-sent events as a full fallback
Assumption that must holdThe board needs only server-to-client events; a feature that needs client-to-server streaming would demote the fallback.
Optimistic concurrency without a queue of intentions
Assumption that must holdConflicts stay rare because presence makes collisions visible; a territory dispatched by many people at once would raise the redo rate.

Scenario walkthroughs

The eighth step of the Architecture Tradeoff Analysis Method takes the scenarios stakeholders brainstormed, ranks them, and traces each through the architecture as a test case. These thirteen were traced. Each opens to the path the system takes.

A clinician's phone dies halfway through the route.

Within 30 seconds the marker stops moving; after 90 seconds without a ping it turns amber with its last-seen time. If the planned start of the next visit passes with no check-in, an adherence alert lands in the live region at ten minutes late and the dispatcher calls. When the phone returns, the outbox delivers any check-ins that happened offline with their original device times, and the board corrects itself. Tactics exercised: heartbeat, exception detection, checkpoint-based rollback.

Two dispatchers assign the same visit to two people.

Every assignment carries a version. A reassign names the version it saw; the database applies it only if that version is still current, in the same statement. The loser receives a typed conflict with who changed it and when, the optimistic move rolls back, and the interface offers to redo it on the new state. Presence in the header makes this rare; the version makes lost updates impossible.

Why GraphQL subscriptions rather than a plain WebSocket carrying JSON.

The events are typed by the same schema as the queries, so a subscription payload writes into the client cache with no translation layer, and generated types catch a shape change at build time. Authorization happens at subscribe with the same resolver context as a query. The cost is a compliant server, the graphql-ws protocol rather than its deprecated predecessor, and a gateway that maps one socket to territory channels. For a client already speaking this dialect the trade favors the subscription.

The board is slow at 500 rows.

The profiler first: rows re-rendering on every location event indicate a selector or throttle problem, the cheapest fix. Then the element count: if all 500 rows are mounted, virtualize. Then long tasks: if conflict detection runs on the main thread, move it to the worker. Then the map: marker churn goes through feature state once per frame. Event-to-paint is measured before and after each step, and work stops when the budget is met.

A new board ships without disrupting dispatchers at 6 a.m.

Behind a per-tenant flag with a visible toggle back to the old board, rolled out territory by territory. Both boards read the same cache, so the risk is workflow, not data. Event-to-paint, conflict rate, and error volume per release are watched, and the kill switch stays server-side so nobody has to redeploy at dawn.

A patient texts cancel ten minutes before the visit.

The reply is parsed as a cancel intent, a cancel mutation runs with the reason, the visit goes to Cancelled and the event reaches the bus. The clinician's phone gets a push and the stop disappears from the route. The repair pass proposes pulling the next stop earlier only if its window allows it, and that patient hears about it only if the new time differs by more than fifteen minutes. The dispatcher sees the gap and the proposal in the same second.

What is logged, and what must never be.

Identifiers, versions, latencies, actor identifiers, and outcomes are logged. Names, addresses, phone numbers, access notes, and any free text a person typed are never logged. Scrubbing happens at the telemetry boundary so a developer cannot forget it. URLs carry identifiers only. Access to protected information is logged separately in the audit table, because the regulation cares who looked, and that log is itself protected information.

When an arrival estimate is recomputed.

On a ping, if the clinician moved more than 50 meters or two minutes passed since the last estimate. On any status change. On an assignment change, for every later stop in that route. Live traffic is fetched for the next hop only; the rest comes from the cached matrix. Patient messages sit behind hysteresis so an estimate wobbling around 10:00 does not send six texts.

The optimizer explains a decision a dispatcher disagrees with.

Every proposal stores its binding constraints and the alternatives it rejected with their cost deltas: which clinician's next window would have closed, which credential another lacked, how many visits would have run late under the alternative. The dispatcher can still override, and the override becomes a soft constraint the nightly plan learns from.

Multiple regions and data residency.

For a single national healthcare market, one region with multiple availability zones and a warm standby in a second region with asynchronous replication is the honest design; the recovery objective is minutes, not zero. Residency is about state programs more than geography: several Medicaid programs require electronic visit verification records to be submitted to a state aggregator, and that is one more integration adapter behind the same interface, not a new architecture.

Testing the realtime path.

A real morning's event stream is recorded and replayed through a mocked subscription in component tests. Event shapes are contract-tested from the schema. In a staging environment the gateway is killed mid-replay and the client is required to resume from its cursor with nothing missing. A few thousand synthetic sockets and a synthetic clinician that pings load the path, and event-to-paint is measured on an office laptop rather than a developer machine.

What a first release would omit.

The optimizer. A manual board, a live map, visit status, and SMS reminders is a product agencies will pay for, and it proves the data model, the realtime path, and the mobile outbox. Repair comes next, because it is where the intraday value is; the nightly solver last, because every agency already has a plan today and it is called yesterday.

Phones in the field update weeks late.

Additive schema changes only: fields are added, never renamed or removed for 90 days, and the server accepts old event shapes for as long. A client version header lets the server see who is still on the old one. Over-the-air JavaScript updates cover most fixes without a store review. A forced-upgrade screen exists and is the last resort, because a clinician locked out at a patient's door is a patient-safety event.

Method and sources

The method, its steps, and its vocabulary come from the following publications of the Software Engineering Institute and courses of Carnegie Mellon University, all publicly available. The design of the dispatch platform, its scenarios, tables, and figures are this document's own application of them; no text is reproduced from the sources.

Software Engineering Institute technical reports

  • Mario R. Barbacci, Robert Ellison, Anthony J. Lattanze, Judith A. Stafford, Charles B. Weinstock, and William G. Wood. Quality Attribute Workshops (QAWs), Third Edition. CMU/SEI-2003-TR-016, August 2003. PDF. The six-part scenario, the three scenario types, and the eight workshop steps.
  • Rick Kazman, Mark Klein, and Paul Clements. ATAM: Method for Architecture Evaluation. CMU/SEI-2000-TR-004, August 2000. PDF. The nine steps, the utility tree with its two-axis rating, and the outputs: risks, non-risks, sensitivity points, tradeoff points.
  • Rob Wojcik, Felix Bachmann, Len Bass, Paul Clements, Paulo Merson, Robert Nord, and Bill Wood. Attribute-Driven Design (ADD), Version 2.0. CMU/SEI-2006-TR-023, November 2006. PDF. The eight design steps, the two-axis ranking of candidate drivers, design concepts from patterns and tactics, and interfaces as provides and requires assumptions.
  • Felix Bachmann, Len Bass, and Mark Klein. Illuminating the Fundamental Contributors to Software Architecture Quality. CMU/SEI-2002-TR-025, August 2002. PDF. The definition of an architectural tactic and the modifiability and performance tactics.
  • Felix Bachmann, Len Bass, and Mark Klein. Deriving Architectural Tactics: A Step Toward Methodical Architectural Design. CMU/SEI-2003-TR-004, March 2003. PDF. General and concrete scenarios, reasoning frameworks, and the tactics that control their parameters.
  • James Scott and Rick Kazman. Realizing and Refining Architectural Tactics: Availability. CMU/SEI-2009-TR-006, August 2009. KiltHub. The refined availability catalog: fault detection, recovery, reintroduction, and prevention.
  • Mario Barbacci, Mark H. Klein, Thomas A. Longstaff, and Charles B. Weinstock. Quality Attributes. CMU/SEI-95-TR-021, December 1995. PDF. The taxonomy of concerns, factors, and methods for performance, dependability, security, and safety.
  • Software Engineering Institute. Views and Beyond: The SEI Approach to Architecture Documentation. 2018. PDF. Views as the organizing principle; the how, what, why order of documentation.

Carnegie Mellon course material

  • Andy Pavlo. 15-445/645 Database Systems, Lecture 17: Concurrency Control Theory. Fall 2025. Notes; and Multi-Version Concurrency Control slides, Fall 2022. Transactions, the lost update, pessimistic and optimistic protocols, serializability.
  • David Andersen and colleagues. 15-440 Distributed Systems, Spring 2014. Syllabus. Time and synchronization, fault tolerance, replication, and the systems principles of modularity and naming.
  • Willem-Jan van Hoeve. Constraint-based solution methods for vehicle routing problems. Tepper School of Business, EWO seminar, November 2009. PDF. Routing with time windows as scheduling on a unary resource; constraint-based local search for large instances.

Each report carries Carnegie Mellon University's copyright and its no-warranty statement; the Architecture Tradeoff Analysis Method, ATAM, and Carnegie Mellon are marks of Carnegie Mellon University. The files consulted, their hashes, and the date of retrieval are recorded with this edition's source.