Inspiration

When the connection goes down, responders are the people that need it most. If there is a grid outage, a backhaul gets cut, or the networks get congested during mass-casualty events, responders should still have access to the necessary resources they need for their job.

Every "ask the AI" tool we could find assumes a connection, and assumes you'll trust whatever it writes. Both assumptions fail in the field. We wanted a solution with no dependency to lose, and no ability to hallucinate.

What it does

You type a question in plain English. BLACKOUT returns three answers: one verbatim sentence from each of the three best-matching protocols, each labeled with its manual, its page, and a confidence score. Open one and the full protocol follows, part by part, each part linked into the source PDF at the page it's printed on.

  • Fully offline. No API keys, no network calls. The only server is a local one on your own machine.
  • Never generates text. No generative model sits anywhere in the answer path. Every line on screen is the manual's own wording. The only thing the software chooses is which lines to show.
  • Abstains instead of guessing. Below a tuned confidence threshold it returns NOT COVERED IN LOADED PROTOCOLS rather than producing a plausible-sounding answer. The refusal state is the pitch.
  • Searches by meaning, not keywords. "Can't catch their breath" finds "respiratory distress." A bare protocol code like T008 or a CAS number like 7783-06-4 also works.

It ships with three manuals loaded: ERG 2024 (PHMSA) for hazmat, the NIOSH Pocket Guide to Chemical Hazards for chemical exposure, and the West Virginia First Responder Guide for emergency medical. In total, we split those documents into 1,825 text chunks across 1,280 protocols.

How we built it

Documents/*.pdf
   │  ingest.py   PyMuPDF extraction -> one chunk per protocol, chemical or table row
   ▼
backend/data/chunks.json     1,825 chunks in 1,280 protocols across 3 manuals
   │  index.py    sentence-transformers all-MiniLM-L6-v2, on CPU
   ▼
backend/data/index.npy       one normalized 384-d embedding per chunk
   │
   ▼
search.py         question -> embed -> numpy cosine similarity -> pool by protocol
   │              -> top 3 -> confidence threshold -> one verbatim line each
   ▼
server.py         stdlib HTTP server: the page, /api/search, /api/protocol, the PDFs
   ▼
frontend/index.html   one file, no framework: three answers, then the full protocol

Ingestion. Each manual got a dedicated parser, because each is structured differently: ERG orange guides and Tables 1–3, 677 NIOSH chemical records, 88 WV protocols plus a 41-drug formulary. A generic bookmark-and-heading parser catches anything else dropped into Documents/. Ingestion is strict on purpose, it rejects duplicate chunk IDs, duplicate passages, and duplicate section labels, and repeated runs produce a byte-identical chunks.json.

Search. Cosine similarity over the chunk embeddings, with three adjustments: bystander wording gets the manuals' clinical terms appended before embedding ("passed out" → unconscious), naming an identifying keyword adds +0.12, and an exact UN number, CAS number or protocol code adds +0.2 with a 0.6 floor. Chunks are then pooled into whole protocols, searches match passages but answers with protocols, and the users actionable options are the lines in the protocol that best match the question including headings, banners, and colon-terminated lead-ins excluded from candidacy.

Incident Map. Blacksburg drawn offline from a real Overpass-API export of downtown and the Virginia Tech campus core. Incidents are placed on it by category (hazmat, confined space, EMS, structural), and selecting one carries its situation straight into a protocol lookup, so the map and the search are the same workflow rather than two features.

Packaging. One Docker image holds the API, the UI, the prebuilt index, the embedding model and the three manuals. HF_HUB_OFFLINE=1 is set, so if the baked model layer is ever missing the container fails loudly at startup instead of quietly reaching for the network. Both data files are committed, so a fresh clone runs with no build step.

Challenges we ran into

Some of the challenges we ran into were making the offline claim true took more than removing API calls: anything that silently falls back to a download is not an offline tool, so we committed the index, baked the model into the image, and set HF_HUB_OFFLINE=1 so a missing model crashes loudly. Creating the parsers for the unique protocol documents and making sure no protocols repeated. Tuning the refusal threshold meant building a 45-question evaluation set to prove that covered and uncovered questions actually separate, rather than picking a number that felt right.

Accomplishments that we're proud of

  • It cannot hallucinate. There is no generative model in the answer path at all. Every character of every answer is traceable to a page in a public-domain federal or state manual.
  • The refusal actually works. 16 out of 16 of our non determinant questions were refused, with a measured margin rather than a hopeful guess.
  • No build step. Clone it, pip install, run. Or docker compose up --build. Nothing about the demo depends on the venue's wifi.
  • 45 automated checks covering protocols consistently even when given unique and non professional level searches.

What we learned

We learned which parts of a web application actually need a network connection and which ones only appear to. Pulling the wifi dependency out piece by piece turned out to be a question of where the data and the model live, not a question of the code that uses them. We learned how to host a local server and package it inside a container, so the whole thing runs the same way every time instead of depending on what happens to be installed on the machine. And researching the problem before we built anything taught us how much the people on the front line depend on their tools at exactly the moment those tools are most likely to fail them. That is the reason the whole project is built the way it is.

What's next for Blackout

  • An online mode that degrades instead of disappearing. With a connection, the map becomes a shared object a whole team can see and manage together. When the link drops, the app keeps working from a snapshot it has been taking on a timer all along.
  • A mobile app with device-to-device sync. Responders carry phones, not laptops. Letting two phones talk directly to each other, with no wifi or cell service in between, would keep a team on the same incident picture in exactly the conditions that knock every other tool out.
  • An agent to make answering more consistent. Not a writer: an agent that reformulates a badly phrased question and chooses among candidate protocols, while the text it returns stays verbatim and the abstention threshold still sits in front of it. Better at understanding the question, with no ability to invent the answer.
  • A deeper, more accessible map. More functionality on the incident view, plus routing that accounts for who is being moved. Ramp-accessible paths for anyone who cannot take stairs, and best-available egress away from the hazard rather than the shortest line on the grid.
  • Offline voice input. Hands are busy and gloved in the field. Typed input is the single biggest usability gap.

Built With

Share this project:

Updates

Submission history