Meridian Field Services: instructional example, not project evidence
The shape in one exec-readable paragraph, the decisions that made it (each a link, never restated rationale), and the tier declaration: this template covers the most complex case, and every chapter it scales down states the trigger value that justified it. Silence is never a tier.
A modular web system around one live board: a browser dispatch board, one backend API, a recommendation module behind a provider gateway, a telematics adapter, and a notification worker. Chosen over microservices for a five-hub scale (ADR-1[7]). The shaping decisions: build the board and the recommendation core, reuse and buy the rest (ADR-2[7], from the BRD options analysis[3]); propose-only assignment posture (ADR-3[7]); distance proxy over live traffic (ADR-4[7]); one provider gateway for every model call (ADR-5[7]).
| Chapter | Disposition | Trigger value |
|---|---|---|
| Landscape diagram | omitted | 4 systems in scope: one context view reads; landscape engages past ~5 to 7 |
| §7 Data architecture | full | Job system-of-record is contested until cutover |
| §8 Integration architecture | engaged | 3 external integrations, one asynchronous |
| §9 API surface | collapsed | One consumer class beyond the team (technician mobile clients); partner webhooks re-engage it at cutover |
| §6 Component sourcing | full | Two vendor rows with exit costs, two internal-reuse rows, one extension, one elimination |
Quality goals, ranked: board liveness · assignment integrity · feed-loss resilience; each carries a numeric scenario in §11. Deliberately not built: event sourcing beyond the assignment log, multi-region; the five-hub scale does not ask for them.
The system as one box with every user role and external system around it, generated from the committed model workspace, so this view and the container view can never drift apart. Every arrow states intent; the dependency index points each external system at its full contract in §8.
System context, generated from meridian.dsl (one Structurizr workspace produces every view), auto-laid-out, stamped 2026-07-16, commit-linked. Legend: teal round = person, teal box = the system, grey box = external system. Seven elements, within the ~9-element cap. Fallback: Dana and Priya dispatch via the board; Marcus works and confirms via mobile; telematics feed in, notifications out, legacy scheduler transitional.
| External system | What flows | Owner | Posture | Contract |
|---|---|---|---|---|
| Telematics vendor feed | Vehicle location data, inbound | S. Grewal (fleet) | Load-bearing · degradation defined | INT-1 |
| Notification gateway | Assignments out, confirmations back | T. Fontaine (accounts) | Replaceable · standard webhook surface | INT-2 |
| Legacy scheduler | Job master, nightly, pilot only | N. Duval (legal & ops) | Transitional · retired at cutover (decision[8]) | INT-3 |
Every container, its single responsibility, and what it owns that nothing else does. Overlap between containers is a finding, not a style. Component detail is earned, not defaulted; one container has it here. No code-level diagrams, ever: they rot.
| Container | Technology | Single responsibility | Owns exclusively |
|---|---|---|---|
| Dispatch board | Browser SPA, typed web stack | Render jobs, suggestions, assignment actions | The dispatcher experience |
| Dispatch API | Typed backend service | Job intake, assignment recording, event log | Assignment integrity |
| Recommendation module | In-process module behind the provider gateway | Rank qualified technicians with reasons | Suggestion logic (FRD[1]) |
| Telematics adapter | Queue-fed worker | Normalize the vendor feed | Location staleness flags |
| Notification worker | Async worker | Deliver and confirm technician assignments | Delivery retries |
| Operational store | PostgreSQL 16 | Event log and operational data | Durability |
The container view from the same model workspace as §2 (same source, no drift), plus one sequence per critical runtime flow. Not ten sequences: the two that carry the system's promises. Per-requirement behavior stays in the FRDs, referenced not restated.
Container view, generated from meridian.dsl, auto-laid-out, stamped 2026-07-16. Technology label on every box; every relationship states intent. Twelve elements, inside the 15-element hard ceiling; past it this view splits by domain, it does not shrink. Fallback: board ↔ API (authenticated calls); API → recommendation module (in-process, synchronous); adapter → API (normalized positions); API → store (events); worker ← store events → technician notifications.
Sequence generated from committed source, stamped 2026-07-16. Solid arrows = synchronous calls; dashed = responses and async events. The closing note carries flow 2 in miniature: recommendation module down → the API serves the cached technician list and the board never blanks; full failure semantics in the FRD error catalog[1].
Per container: language, framework, and the load-bearing dependencies with versions. Every non-obvious choice carries its decision link. AI capabilities are stated model-agnostically: the provider abstraction is the architecture; vendor names are configuration behind it.
| Container | Stack | Load-bearing versions | Decision |
|---|---|---|---|
| Dispatch board | Typed web SPA on the Meridian design system | design tokens v4.2 | reuse · §6 |
| Dispatch API | Typed backend framework, event log on PostgreSQL | PostgreSQL 16.3 | ADR-7 boring storage[7] |
| Recommendation module | Same runtime as the API; model calls only through the provider gateway. Capability contract: ranking with reasons, sub-second budget; the vendor behind it is configuration | gateway policy v1 | ADR-5[7] |
| Telematics adapter | Queue-fed worker on the platform queue | queue runtime v3 | default-obvious |
| Notification worker | Async worker over the vendor gateway's webhooks | vendor API 2024-11 | ADR-9[7] |
Where every piece comes from: built from scratch, an extension of something that exists, a commercial product, an adopted open-source project, or another team's sub-system reused. The default order is reuse before buy, buy before build; a Build row must show what it evaluated and rejected. This table is the counter to reinventing what the company already has.
| Component | Class | Evidence the class demands | Decision |
|---|---|---|---|
| Dispatch board | build | Owner: dispatch product team. Differentiator: the union-aware assignment workflow no vendor console models. Evaluated and rejected: two vendor dispatch consoles from the BRD options analysis[3] (fee curve, no propose-only mode) and the legacy scheduler's screens (no live suggestions). Maintenance cost accepted in the BRD totals. | ADR-2 |
| Recommendation module | build | Core differentiating logic, the thing the project exists to create. Rejected: vendor console ranking (opaque, no reason surface); maintenance carried with the board. | ADR-2 |
| Mobile day view | extend | Base system: the existing Meridian field app (field-tools team). Debt taken on: its aging offline sync. Migration trigger: if the 2027 sync rework lands, revisit (§16). | ADR-8 |
| Notification gateway | vendor | Enterprise messaging platform already under contract. License tier covers pilot volume; term to March 2028; exit cost low; standard webhook surface, data portability trivial. Feature-gating risk: delivery analytics sit behind the next tier (§16). | ADR-9 |
| Telematics feed | vendor | Fleet tracking vendor; exit cost material: hardware in 300 vehicles. Held at the adapter seam so swap cost stays contained; contract renewal January 2027 (§16). | INT-1 |
| PostgreSQL 16 | open-source | Permissive license; managed service; healthy community; fork risk negligible. Inventory entry SBOM-041[9]. | ADR-7 |
| Meridian design system | internal reuse | Owning team: platform experience. Stability contract: versioned design tokens, breaking changes announced a release ahead. Integration cost: days; a rebuild would be months. | ADR-2 |
| Meridian SSO | internal reuse | Owning team: identity. Interface stable; the mobile API consumes standard tokens (§9). | ADR-2 |
| Legacy scheduler | eliminate | Estate disposition: job master during pilot only; retired at cutover with its nightly transfer (INT-3). | cutover |
Who owns each entity, how data lives and dies, and the contracts on every exchange with more than one consumer. Engaged in full here because job ownership is contested until cutover; below roughly five shared entities and with no contest, this chapter is one table and says so. Physical schemas stay in the low-level design.
Conceptual entities: Job, Assignment, Technician, Position, ReasonCode: the business-readable model; the attributed logical model travels with the LLD.
| Entity | System of record | Everyone else holds | Copy freshness |
|---|---|---|---|
| Job | Legacy scheduler during pilot; dispatch API at cutover: the contested case that engaged this chapter | API references job records until cutover day | nightly (INT-3) |
| Assignment | Dispatch API event log, sole writer | Board and day view render projections | push, ≤ 5 s |
| Technician | Meridian HR system | Board holds a read copy | 1 hour |
| Position | Telematics adapter (normalized) | API cache | 15 s |
| ReasonCode | Dispatch API configuration | none | n/a |
| Entity | Created | Updated | Archived / purged | Authority |
|---|---|---|---|---|
| Position | On feed ingest | Never (immutable readings) | Purged at 30 days | Telemetry retention policy |
| Assignment | At acceptance or override | Never (event-sourced entries only) | Archived at 7 years | Enterprise contracts (PRD constraints[2]) |
| Job | At intake | Status transitions | Archived with its assignments | Operations policy |
| DC-1: telematics feed → adapter (full contract attached[5]) | |
|---|---|
| schema | Types, nullability, and enums in the attached contract. Field additions are non-breaking; removals and type changes are breaking |
| semantics | Terms tied to the project glossary: "position" is a vehicle reading, never a technician's assignment state |
| freshness | 15 seconds end-to-end; positions older than 60 seconds carry a stale flag the board must render |
| owners | Producer: vendor integration team · consumer: dispatch platform · escalation: S. Grewal |
| change rule | Breaking changes owe consumers a 90-day deprecation window |
Lineage. Positions: vendor feed → adapter (normalize, staleness flag) → API cache → board and recommendation inputs, an owner at every hop, so a contract violation's blast radius is traceable. The idle-time KPI reads from assignment events, not positions; measurement methodology lands with the BRD's success criteria[3].
An integration is a contract, not an arrow. One full contract card for the load-bearing flow, the register for all three: protocol, auth, format, trigger, volume, expectations, owner, failure and retry semantics. A blank column is a decision not yet made and is flagged as such. Zero to two synchronous integrations would collapse this chapter to a paragraph; nothing integrates by shared database here.
| INT-1: telematics vehicle feed | |
|---|---|
| source → target | Vendor cloud → telematics adapter |
| style | Asynchronous messaging: vendor push into the platform queue; chosen over polling for freshness at volume |
| protocol · auth | Authenticated webhooks · signed tokens rotated quarterly |
| format | Vendor JSON, normalized at the adapter, the seam that contains swap cost (§6) |
| trigger · volume | Continuous push · ~400 positions/minute peak · vendor rate cap 1,000/minute |
| expectations | 15-second freshness · 99.5% monthly availability (DC-1[5]) |
| owner | S. Grewal (fleet) |
| failure & retry | Queue buffers 10 minutes, then the stale-flag path engages (FRD degradation[1]); exponential backoff, dead-letter after six attempts, alert to engineering |
| status | Contracted |
| Id | Flow | Style | Trigger / volume | Expectations | Owner | Failure semantics | Status |
|---|---|---|---|---|---|---|---|
| INT-1 | Telematics feed → adapter | Async messaging | Push · 400/min peak | 15 s · 99.5% | S. Grewal | Buffer 10 min → stale flags; dead-letter at 6 | contracted |
| INT-2 | Worker → notification gateway | Async messaging | Per assignment event | Delivery ≤ 60 s | T. Fontaine | Backoff retries; dead-letter alerts dispatch; a day-long outage degrades to board-only confirmation; impact bounded (FRD[1]) | contracted |
| INT-3 | Legacy scheduler → API (job master) | File transfer · transitional | Nightly batch | Pilot only | N. Duval | Missed drop re-runs at 06:00; pilot proceeds on prior master | retires at cutover |
Engaged whenever an API is consumed outside the team that builds it. Here that is one consumer class (technician mobile clients on the public internet), so the chapter runs in collapsed form under its full heading: catalog, versioning promise, gateway shape. Partner webhooks at cutover re-engage it in full.
| API | Specification | Owner | Stability | Auth | Consumers |
|---|---|---|---|---|---|
| Dispatch API: mobile surface | published spec[6]: the spec is the contract; this catalog indexes it | Dispatch platform team | stable | Meridian SSO tokens | Technician mobile clients (public internet) |
Versioning promise: path-versioned; a deprecated version lives 180 days with announce → migrate → remove dates published to the field-tools team (ADR-11[7]). Gateway: external mobile traffic enters through the managed edge; in-process calls stay internal; no per-client shaping yet (one client class).
Re-engagement note: partner webhooks land at cutover, and this chapter then runs in full: per-partner registration, rate limits (§17), and the external consumer register.
The index, not the decisions: every record referenced anywhere in this document appears here, and every row resolves to its full record in the decision log[7]. Superseded decisions stay listed with their successor; history is never rewritten.
| Id | Decision | Status | Date | Drivers, one line |
|---|---|---|---|---|
| ADR-1 | Modular monolith over microservices | accepted | 2026-07-04 | Five-hub scale; one team |
| ADR-2 | Build board & recommendation core; reuse and buy the rest | accepted | 2026-07-02 | Union rules, vendor fee curve (BRD[3]) |
| ADR-3 | Propose-only assignment posture | accepted | 2026-07-02 | Union clarification pending; revisit Aug 1 |
| ADR-4 | Distance proxy over live traffic | accepted · pending shadow trial | 2026-07-06 | Cost; validated by RQ-041[11] |
| ADR-5 | Provider gateway for all model calls | accepted | 2026-07-08 | One policy, guardrail, and audit point |
| ADR-6 | Synchronous suggestions | accepted | 2026-07-08 | A suggestion is worthless late |
| ADR-7 | Boring storage: PostgreSQL 16 | accepted | 2026-07-04 | Operational familiarity |
| ADR-8 | Extend the Meridian field app for the day view | accepted | 2026-07-10 | Reuse over rebuild; offline-sync debt accepted (§16) |
| ADR-9 | Notification via the contracted messaging vendor | accepted | 2026-07-10 | Undifferentiated; exit cost low |
| ADR-10 | Point-to-point integration, no bus | accepted | 2026-07-12 | Three integrations |
| ADR-11 | Mobile API path-versioning, 180-day deprecation | accepted | 2026-07-12 | Field app release cadence |
Stimulus-response scenarios with numbers; an attribute without a numeric response measure is an aspiration, not architecture. Each scenario names the structural mechanism that satisfies it and the requirement it supports.
| Attribute | Stimulus (environment) | Response | Measure | Mechanism | Supports |
|---|---|---|---|---|---|
| Board liveness | Job created at peak intake (normal ops) | Job visible on every hub board | ≤ 5 s, p95 | Push updates, no polling | PRD experience constraints[2] |
| Feed-loss resilience | Telematics feed silent 10 minutes | Assignment flow continues; proximity dropped and flagged | 0 blocked assignments | Cached last-known list | FRD degradation[1] |
| Assignment integrity | Duplicate acceptance race | Exactly one assignment stands | 0 double-assignments over the pilot | Idempotent event log | FRD interface behavior[1] |
Scale frame: five hubs, 1,500 jobs/day, 400 technician positions/minute; first expected bottleneck at adapter fan-in (§13). Attributes deliberately not driving the shape: multi-region availability, sub-100-millisecond anything.
Where data enters, moves, is stored, and leaves, with every privacy boundary marked and the data class named at each crossing. Drawn at boundary altitude and consistent with §7's lineage; the model provider sits behind one gateway so every call inherits the same policy, guardrails, and audit trail.
Data flow with privacy boundaries, generated from committed source, stamped 2026-07-16. Legend: dashed amber = the residency zone; PB-1 feed ingress (location = personal data, Canada residency), PB-2 provider boundary (anonymized codes only). Cylinders are stores with their retention. Fallback: feed → adapter → cache → board; board → event log → worker → devices; cache → gateway → model provider, anonymized.
Obligations on the diagram: location data stays in Canada (enterprise contracts[2]); retention matches §7's lifecycle: positions purged at 30 days, assignment history archived at 7 years.
The load shape in numbers, the first bottleneck beyond it, and what stays up when each load-bearing dependency is down. Caching lives here because caching is a scaling decision, not a separate discipline.
Built for: five hubs, 1,500 jobs/day, 400 positions/minute, with tenfold headroom at the API. First bottleneck: adapter fan-in beyond 2,000 positions/minute, the revisit trigger, tied to the hub-six question (§17). Caching: the technician list is cached client-side (the never-blank fallback) and positions are cached in the API for 60 seconds; invalidation is push-on-assignment-events. Degradation: per dependency, reconciled with the FRD error catalog[1] and §8's failure columns: feed silent means proximity drops with flags, never blocked assignments; notification outage degrades to board-only confirmation.
Every quality scenario's measure has a signal that observes it; an unobserved scenario is untestable. One correlation id tells a job's whole story.
Correlation: an id minted at job intake carries through suggestion, assignment, and notification. Signals: suggestion latency distribution, feed staleness, assignment event lag, notification delivery rate, dead-letter depth per integration; §11's three measures each map to one. Paging: feed silent > 10 minutes pages the engineering lead; suggestion latency breach surfaces per the FRD threshold. Procedures live in the operations runbook[4].
One table, pointer-first: where each container runs and what environments exist. Everything operational (deploy, rollback, credentials) lives in the runbook, linked never duplicated.
| Container | Runs on | Environments |
|---|---|---|
| Board · API | Managed cloud, hub region | dev · pilot (Toronto) · production: operations runbook[4] |
| Adapter · notification worker | Beside the API | |
| PostgreSQL 16 | Managed service |
Each item with the trigger that makes it bite, an owner, and a revisit condition; none is a vague worry. Sourcing-borne exposures from §6 surface here. An empty risk section is omitted, never padded.
| Item | Type | Trigger | Owner | Revisit |
|---|---|---|---|---|
| Propose-only posture may need rework if the union clarification permits automation; contained in the recommendation module by design | debt | Union answer (PRD open questions[10]) | A. Reyes | Aug 1, 2026 |
| The field app's aging offline sync rides into the extended day view (ADR-8[7]) | debt | 2027 sync rework landing | Field-tools team | 2027 planning |
| Single telematics vendor with material exit cost (hardware in 300 vehicles); mitigation is the adapter seam and the dead-letter buffer (§6, §8) | risk | Contract renewal | S. Grewal | Jan 2027 |
| Notification delivery analytics gated behind the vendor's next license tier (ADR-9[7]) | risk | Delivery disputes exceed one per month | T. Fontaine | on trigger |
Owned and dated, with the decisions or containers each blocks; an unowned question is not tracked.
| Question | Owner | Answer by | Blocks |
|---|---|---|---|
| Does the adapter need vendor-push or polling at hub six and beyond? | E. Sandoval (engineering lead) | Aug 15, 2026 | The adapter scale commitment only (PRD[10]) |
| Do partner webhooks at cutover require per-partner rate limits? | E. Sandoval | Sep 30, 2026 | §9's re-engagement, not the pilot |
Omission note, how a scaled-down chapter renders on a simpler project: "§8 Integration Architecture (collapsed): this system has one synchronous integration (payments provider), contracted in the dependency index. Trigger: below the three-or-async threshold. Rationale recorded in adaptation event #3."
Package items travel in the same export bundle; workspace items link into Specira where the governed record lives.