Full-Stack Crisis Communication for Public Authorities
At a crisis-communication company: a reporting module in Angular and Java, an Android app for vehicle crews on a radio link, a multilingual emergency web app for residents on local Wi-Fi, and a public display system.
Local authorities sent situation reports to their district control room on paper and in Word files, so the reports arrived without a shared structure, status or history.
- role:
- Senior fullstack developer across web, backend, Android and display software
- stack:
- Angular · Java · Play Framework · MySQL · TypeScript · PrimeNG · WebSockets · Android · Python · SQLite
- when:
- Since 2025
- published:
Problem & constraints
I work at a crisis-communication company whose software serves public authorities. One gap stood out: when something happened, local authorities still sent situation reports to their district control room on paper and in Word files. The reports needed a shared structure, a visible status and a history.
The reporting module has been through a field test with the municipalities of one district and is not yet in production, so its outcomes below are from that field test, not from live operations.
My role covers the whole stack, and more than one product: web, backend, Android tablets, a web app for residents and public displays. I run technical decisions, and other developers work from my plans and reviews.
Architecture
The reporting module is an Angular and PrimeNG client over a Java and Play Framework backend with MySQL. On one side sits a multi-page reporting form with validation, plausibility warnings, draft autosave and rule-based priority suggestions. On the other sits the control room’s dashboard, with combinable filters, report processing, change history and CSV export, plus a read-only review mode. Accounts, role-based navigation and permissions cover each authority.
The public-information display system has an Angular operator interface, a Java and Play middleware and a Python rendering layer that draws the notice, including its maps, for an e-paper panel. Rollout is driven by configuration: a new location needs a site file and a one-off map fetch, with no source change.
The decisions behind all of these are logged below, each with the alternative it beat.
The field tablets
The tablet app is for vehicle crews working through a blackout, when public networks may be gone. The tablet joins a Wi-Fi access point in a kit carried with the vehicle, and a private radio link carries the traffic to the control room. A crew reports that it is ready, receives an assignment with its stops, chooses the next stop, sees the route on an offline map, and reports position, status and results back. Text chat and emergency reports use the same link.
The app keeps working without a connection. Map and routing run on the device, and apart from position updates, outgoing messages wait in SQLite and are sent again until they are acknowledged.
The app is older than my time on it. My part was to carry it from Android 6 to 13 with a full UI overhaul, where I made the decisions on every part, to build a redesign line with sidebar navigation, turn-by-turn guidance, an updated map and quoted replies in chat, and to build backup and restore, so a technician can fully re-provision a device from one file. I sometimes take feedback from customers directly.
A web app for residents
When the power and the phone network are out, residents can join a local Wi-Fi network from a station on site and open a web app in their browser. I built it and made the decisions on every part of it.
They can file a guided emergency report: the address, a few short questions from a decision tree, a review, then send, and the report reaches the emergency services. They can describe how things stand where they are and then see help locations on a map, or offer help themselves. The app is multilingual.
It is a web app whose pages call a Java and Play backend. If the backend cannot be reached, the browser keeps the report and tries again.
Working across two clients
The Angular client exists in two forms: the current client, and a version migrated to a newer Angular with a full redesign. I keep the two in step, so the same feature behaves identically in both. The tablet app also has its redesign line alongside the current design on Android 13.
Outcomes
Authorities can now submit structured reports that the control room sees live, processes, and can trace back through their history, instead of handling paper and Word files. The module went through its field test, with accounts, permissions and a realistic exercise scenario prepared for each participating municipality.
The display system was tested on real hardware. Because of the delivery layer, swapping its whole hardware stack did not touch the application. The tablet app has been tested on hardware, and its backup and restore has been released to field tablets, after scripted end-to-end checks that included fault injection.
The residents’ web app is built, and its reports have been shown to reach the emergency services. I have no figures from real use, so I claim none.
Lessons
Three lessons stayed with me. First, test concurrency on the real database: running the scenarios on MySQL told me more than reasoning about the locks had. Second, an abstraction proves itself when the thing behind it actually changes; the hardware swap was the test of the delivery layer, and it held. Third, in public communication, wording is part of the system: status has to be carried by text and symbol rather than colour alone, and a technical delivery confirmation must never be presented as proof that someone has checked the display.
Key decisions
01 Layered protection against lost updates
Optimistic locking, transactional row locks and field-level conflict resolution together.
Several people can work on the same report at once, and one person's save must not silently erase another's changes.
I reproduced the concurrent scenarios on MySQL itself instead of reasoning about them on paper, and those runs shaped the final locking design.
rejected: Letting the last save win
02 Live updates over WebSockets
Pushing changes to the dashboard over WebSockets, with polling kept only as a fallback.
A control room should see a new or changed report as it happens, without waiting for the next refresh.
rejected: Polling as the only update path
03 Drafts and report numbers owned by the server
Keeping drafts on the server, with limits, and assigning report numbers there.
Many authorities submit at once, so numbering needs one source of truth, and a draft should not depend on one browser.
rejected: Keeping drafts in the browser and numbering reports on the client
04 Versioned reports
Treating a resubmitted report as a new version that keeps its processing state and full history.
A control room needs to see what changed and must not lose the work it has already done on a report.
rejected: Overwriting the report on resubmission
05 One renderer for preview and display
Producing the operator's preview and the physical panel's image through the same code path.
The output is a public notice, so what the operator approves has to be what the panel shows.
rejected: A separate preview rendered in the browser
06 A delivery layer behind one interface
Isolating how an image reaches the panel behind a transport abstraction.
The hardware was still likely to change, and the application should not have to change with it.
It did change: the whole hardware stack was replaced, with no change to the controller, request handling, error mapping or client code.
rejected: Building the delivery path into the application
07 Backups that survive an app-data wipe
Writing tablet backups outside the app's private storage.
Private storage disappears with the app's data, and a backup has to outlive that.
A technician can restore a device from one validated file, choosing what to restore item by item.
rejected: Keeping backups in the app's private storage
08 A restore must not replay old traffic
Marking messages still waiting in a restored database so they are not sent again.
A restore should return a device to a known state, not repeat traffic from before it.
rejected: Restoring the queue of outgoing messages as it was
09 Telling the crew what a missing permission costs
A banner on the tablet's dashboard that names each missing location permission and what it stops, and opens the settings when tapped.
A dialog is easy to dismiss, and a crew should see that the control room is no longer receiving its position.
rejected: Relying on Android's permission dialogs alone
10 A guided emergency report
A decision tree of short questions, with one review page before sending.
Someone in an emergency should not have to work out what matters: the questions collect it, and the review lets them correct it.
rejected: One free-text form
11 Translate the screen, keep the record in German
Showing the questions in the resident's language while storing the answers in German.
Residents may not read German, and stored reports should stay in one language.
rejected: Storing answers in the language they were given in
12 Keeping a report on the phone if the server is out of reach
An outbox in the browser's IndexedDB that retries on the next page load or reconnection.
Connections are unreliable in a blackout, and nobody should have to enter an emergency report twice.
rejected: Showing an error and asking the person to try again
Key figures
| Metric | Value | Status |
|---|---|---|
| Fields in the reporting form | 74 | reported |
| Unit tests for the reporting logic | 128 | reported |
| Automated tests for the display system | 70 | reported |
| Nodes in the emergency report's decision tree | 77 | reported |
Reported: The form as built, with validation and plausibility warnings · Tests written for the module's core logic · Tests across its interface, middleware and rendering code · The decision tree as built; one path takes 3 to 6 answers