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.
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.
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.
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 driver | Importance · 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.
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 quantity | Estimate · 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.
| Scenario | Source · 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 |
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.
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.
| Decision | Tactic · 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 |
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
- 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.
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
| Property | Response 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
| Decision | Chosen · 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
| Risk | Consequence · 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
| Decision | Assumption 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.