Inspiration

After Hurricane Helene, 282,711 households in North Carolina held valid FEMA registrations for individual assistance. FEMA ruled 121,666 of them ineligible, 43.0%. (OpenFEMA, disaster DR-4827, re-pulled 4 October 2026.) That figure includes people who really are not eligible. It also includes 29,347 people coded in the two categories a person can act on: 22,087 who "did not respond to FEMA outreach or withdrew," and 7,260 who "need to submit documentation." The paperwork, or a phone call, would have changed their file.

Almost none of them push back. GAO looked at 2016 to 2018 and found that about 3.5% of applicants referred to appeal actually did (153,114 people), and 30.2% of those won (GAO-20-503, Table 1, p.29). Over 2010 to 2019, FEMA approved 25% of the roughly 463,000 appeals it received.

Part of the reason is the letter. FEMA's own ineligible-applicant template says "ASSISTANCE NOT APPROVED" and, in GAO's words, "does not explicitly state that the determination is not final" (GAO-20-503, p.47). GAO recommended FEMA fix the letter. DHS concurred.

Courts have said versions of this before. In ACORN v. FEMA (D.D.C. 2006, 463 F. Supp. 2d 26), Judge Leon ordered FEMA to give Katrina and Rita evacuees "more detailed explanations for the denials ... including the factual and statutory basis for the denial and more fulsome instructions as to how each evacuee may either cure their ineligibility problem(s) or proceed with an appeal," and to restore short-term housing payments in the meantime. That order did not last. The plaintiffs voluntarily dismissed the case in January 2007, and the D.C. Circuit dismissed FEMA's appeals as moot and vacated the injunction orders. I cite it for what the judge found FEMA had failed to tell people, not as precedent.

The harm here is money and a roof, not a count of deaths, and I have kept the numbers to what the sources say.

What it does

Appeal Desk turns a FEMA denial into a ready-to-send appeal: it reads the letter, finds the current rule that applies, works out whether to appeal or send a document, and drafts the letter with the deadline and paperwork attached.

You pick a denial letter. The first thing on screen is the deadline, counted from the letter date, as a bar that goes red when it has passed. Under it:

  • The rule as FEMA states it today. A live Tavily call, scoped to FEMA and the Federal Register, brings back the current appeal passage with its URL and a quote. Every rule in the output carries the source it came from.
  • A decision. Is this an appeal on the merits (the inspector said the home is safe to occupy and the person disagrees), a document to send (ownership, occupancy, insurance), or nothing to fix?
  • A checklist. Each document, where to get it, and the date.
  • A draft of the appeal or cover letter, with the application and disaster numbers, ready for the person to read, sign and mail. Nothing is filed for them.

There are three worked cases. Two use FEMA's own published documents: the "Home is Safe to Occupy" template from GAO-20-503 Appendix V, and FEMA's sample "Your Next Steps" letter graphic, which turns out to be an approval with a document request rather than a denial. The third is a real DR-4827 record from Buncombe County coded "needs documentation," where the missing paper is proof of ownership and the answer points at the county register of deeds and Legal Aid of North Carolina.

The caseworker tab is for the person with a stack, such as a Disaster Recovery Center volunteer or a legal-aid intake desk. It lists Buncombe County homeowner records still coded "needs documentation" (the county has 2,622 of them in OpenFEMA), each with what is missing and the earliest date its window can close.

Pasted letter text also works: Nemotron parses it into the disaster number, letter date and determination codes, and the same pipeline runs.

How I built it

FastAPI serves a small JSON API and the built React app from one process on one port. The state is a few JSON files: the example cases, a sample of OpenFEMA rows, and a cache of every model and Tavily response, so a repeated click spends nothing.

All model calls go to Nebius Token Factory through its OpenAI-compatible endpoint, with the key kept on the server. The Nemotron models are tiered by job:

Step Model Settings
Choose and run the FEMA search Nemotron 3.5-Lightning function calling on a fema_search tool
Parse a pasted letter Nemotron 3 Super JSON output, low reasoning effort
Appeal, document, or nothing to fix Nemotron 3 Ultra one call, high reasoning effort
Draft the letter Nemotron 3 Super plain text

Lightning is the only Nemotron model tagged for function calling, so the wrapper routes any call that carries tools to it. Ultra is spent on exactly one judgment per letter.

Tavily is a runtime call, not decoration. /search runs against an allow-list of fema.gov, federalregister.gov, ecfr.gov, govinfo.gov, disasterassistance.gov, NC state sites and Legal Aid NC, and /extract pulls the full passage to quote. The reason it matters: fema.gov returns HTTP 403 to a plain fetch from the machine I built on (I checked four pages), and deadlines move by press release. In testing, the search returned FEMA's 3 February 2025 release extending the Helene registration deadline for North Carolina to 8 March 2025, and extract returned the full text. A model answering from memory would not know that.

Three decisions worth naming:

  • The rule set depends on the declaration date. Disasters declared on or after 22 March 2024 fall under the IA Program Equity rule (89 FR 3990), which counts the appeal as filed if postmarked within 60 days and makes the written explanation optional. Earlier disasters need a signed written appeal.
  • If Tavily returns nothing usable, the app says so and points to a person. It does not fill the gap from model memory.
  • Reasoning models eat the token budget. With a small max_tokens they return an empty string because the budget went to hidden thinking, and sometimes the answer sits in reasoning_content. The wrapper reads both and sets reasoning_effort per step.

The frontend is a single light page with a cream deadline card and tabs.

What makes it agentic

Perception Decision Action
A denial letter: codes, letter date, disaster number Which rule set applies, from the declaration date Searches FEMA's current guidance through Tavily and extracts the passage to quote
What FEMA's page says now, and what the letter asks for Merits appeal, document to send, or nothing to fix (Ultra) Builds the dated checklist and drafts the letter
A search that returns nothing usable Stop and say so Refers the person to a human instead of guessing
A queue of open records and their dates Which windows close first Sorts the caseworker view by earliest close date

The agent stops at a drafted letter. The person reads it, signs it and sends it.

Accomplishments

  • A live Tavily call that returned FEMA's real 3 February 2025 Helene deadline extension, from a site that blocks ordinary fetchers.
  • A deadline bar on every case, and a decision step that separates "disagree with the inspector" from "send a document," which are different fixes with different paperwork.
  • The queue is built from real OpenFEMA records for one county, not invented rows.
  • Every rule on screen carries its source URL and quote.
  • The whole thing runs on serverless Token Factory calls. No GPU, no dedicated endpoint.

What's next

  • A real letter. I have FEMA's template and FEMA's sample, not a filled-in Helene letter. I want a legal-aid partner to run a redacted real one through it.
  • A nightly re-check of each open case against Tavily, so an extension or a guidance change shows up without anyone looking. This is designed, not built.
  • County pages. Using Tavily map and extract on county register-of-deeds sites to find the exact parcel-lookup page for each county.
  • Better letter reading. A vision step for photographs; NVIDIA has no vision model on serverless, so that part has to come from another model.

Built With

Share this project:

Updates

Submission history