Designing an Emergency Response System: UML, SQL, and a Working CLI
This was a fun engineering lab group project focused on OOP. The working CLI was an added bonus — the real goal was talking through how to design and think about these types of systems conceptually. The flow with Claude was having an initial diagram generated, then editing it live in draw.io and reprompting based on our changes and discussion. Truly collaborative with humans and AI assistance.
Technical Details
The Lab Spec
The session started from a structured engineering prompt: design a system for a city to coordinate emergency responses. The requirements were deliberately open-ended — handle fires, medical emergencies, and crimes; dispatch appropriate units; handle multi-unit incidents; and be extensible enough that new incident or unit types could be added without redesigning the core.
That last constraint is the interesting one. It pushes you toward inheritance hierarchies and polymorphism rather than a flat table of if incident_type == "FIRE" branches.
UML Design in draw.io
The diagram (emergency_response_uml.drawio) ended up with four conceptual layers arranged top-to-bottom to mirror data flow:
Row 1 — Controller: DispatchCenter sits at the top as the entry point. An Incident Reporter actor feeds into it from above; an Auditor actor reads from completed responses below.
Row 2 — Abstract base classes: Incident (blue) and ResponseUnit (green) sit side by side. Both are abstract — they define the contract but can’t be instantiated directly. DispatchCenter reaches down to both via categorize and respond dependency edges.
Row 3 — Concrete subclasses + coordination: Three incident subclasses fan out left (FireIncident, MedicalEmergency, CrimeIncident), three unit subclasses fan out right (PoliceUnit, AmbulanceUnit, FireUnit), and IncidentResponse sits in the center as the association class that links them.
Row 4 — Enumerations: IncidentStatus, SeverityLevel, and AvailabilityStatus anchor the bottom.
The key design decisions:
getRequiredUnits()on eachIncidentsubclass — dispatch rules live close to the incident type, not scattered through the controller. AFireIncidentknows it needs[FIRE, AMBULANCE]; theDispatchCenterjust asks and acts.IncidentResponseas an association class — rather than a direct many-to-many betweenIncidentandResponseUnit, the association class holds assignment metadata: when the unit was assigned, its role, and its current response status. This enables reassignment and withdrawal without losing history, and it’s what theAuditorreads.isAvailable()as an explicit guard — units inDISPATCHED,OFF_DUTY, orMAINTENANCEstates cannot be assigned. The guard is on the unit, not the controller.
One XML hiccup along the way: draw.io’s .drawio format is XML, and XML forbids -- inside comments. The initial template had comments like <!-- Incident -- IncidentResponse (1 to many) --> which broke parsing. Fixed by replacing -- with to.
SQL Schema
The schema in schema.sql maps cleanly from the UML:
| |
The CHECK constraints on type, severity, status, and availability encode the enumeration constraints from the UML directly into the database layer — the same invariants that IncidentStatus, SeverityLevel, and AvailabilityStatus represent in the class diagram.
The schema also seeds the fleet:
| |
Python CLI
cli.py is a menu-driven Python 3 CLI backed by SQLite. The three core functions map directly to UML operations:
report_incident() — mirrors DispatchCenter.receiveReport():
| |
dispatch_units() — mirrors dispatchUnits() + isAvailable() guard + IncidentResponse creation:
| |
resolve_incident() — mirrors IncidentResponse.complete() + ResponseUnit.withdraw():
| |
A quick smoke test confirmed the flow: report a FIRE at Downtown with HIGH severity, dispatch immediately, and the system assigns Engine 1 + Ambulance 1 (the two unit types a FireIncident requires), setting both to DISPATCHED and the incident to IN_PROGRESS.
Repository
All artifacts are on GitHub: arosenfeld2003/emergency-response-system
Claude’s Perspective
Note: These observations are verbatim as generated and were not edited by a human.
What struck me most about this session was how well the lab spec forced a layered design without prescribing one. The requirement that “the system must support new types of incidents or units in the future” is essentially a statement of the open/closed principle — and it naturally pushes toward abstract base classes and polymorphic dispatch rather than conditional logic. The UML diagram ended up expressing that cleanly: getRequiredUnits() is defined on Incident but implemented by each subclass, so adding a HazmatSpill type is a matter of subclassing, not editing a dispatch table.
The IncidentResponse association class is the most architecturally interesting part. In a naive implementation you might just add a foreign key from response_units to incidents, which breaks the moment you need many-to-many (one incident, multiple units). The association class solves that but also adds something more valuable: per-assignment state. The Auditor actor that appeared during the session — added directly in draw.io, not in the original spec — implies a requirement for auditability. IncidentResponse with assigned_at and status fields is exactly what you’d need to answer “which unit responded to this incident and when did it clear?”
The SQL CHECK constraints are doing real work here. Rather than relying on application-level validation, the schema enforces the same enumerations the UML diagram models. That’s a healthy instinct — the database is the last line of defense, and encoding invariants there means a future CLI, API, or migration script can’t accidentally create an incident with status = 'PURPLE'.
One thing I can’t fully observe from the artifacts: the diagram went through several iterations — the user moved IncidentResponse down a row, added the Incident Reporter and Auditor actors, and relabeled edges from manages/coordinates to categorize/respond. These are meaningful changes. The actor additions frame the system from a use-case perspective rather than just a structural one. The edge relabeling shifts the diagram from describing what the DispatchCenter owns to what it does — a subtle but real improvement in expressiveness.
The one open thread is the Capability type referenced in ResponseUnit.capabilities: List<Capability>. It appears in the UML but has no corresponding table or enum in the schema or CLI. Speculating: the matching logic between getRequiredUnits() and actual unit capabilities is currently implicit — a FireIncident requests FIRE units and the query finds units of type = 'FIRE'. That works for a demo but conflates unit type with capability. A real system would want a separate capabilities table so a single unit could carry multiple capabilities (e.g., a combined fire/hazmat truck).
Built with Claude Code during an engineering lab session