← projects

CSVAI

Plain-English questions over the federal HMIS CSV export, for non-profits housing people experiencing homelessness. An LLM on Workers AI writes the SQL from the schema; the data is queried where it lives.

year
2025
stack
Python, FastAPI, PandasAI, Workers AI, AI Gateway

Every federally funded emergency-shelter and supportive-housing program keeps its client and service data in the same format: the HMIS CSV export, about two dozen linked files. Agencies can produce the export themselves. Answering a real question across it is another matter: it takes someone who knows the spec and can join the files by hand. The questions people need answered look like this:

  • Which clients entered without income or non-cash benefits and left with them, and the reverse?
  • How many clients exited to non-permanent housing, especially from permanent housing programs?
  • Which veterans are being served without benefits?
  • Who lost benefits or income while in a program?
  • How did income, benefits and destinations change from one year's export to the next?

CSVAI lets agency staff ask those questions in plain English and get back a table, a number or a chart, with a short written summary. The data is pseudonymized client and service data, and the contract requires it to stay in sovereign systems. If an agency sends an export that isn't pseudonymized yet, a tool call to another platform pseudonymizes it for them.

Architecture

Diagram: an agency exports its HMIS data in the standard CSV format, pseudonymized by a tool call to another platform if needed. Staff upload it in the orchestrator's web chat, which sends the ZIP to a Python service on sovereign hosting. The service preprocesses the export once, attaches 24 JSON schemas and caches the tables. Staff then ask questions in the same chat; the orchestrator splits them and picks the files. The PandasAI agent sends the schema and question through AI Gateway to Qwen 2.5 Coder 32B on Workers AI, which returns SQL that runs locally. The orchestrator sends results to Gemma 4 26B A4B or gpt-oss-20b on Workers AI for a summary. Follow-ups may send up to 29 result rows.
The data is queried where it lives. Schema and questions go out through AI Gateway, to Workers AI models only.

How it works

  1. Load once. Staff upload the export, a ZIP, in the orchestrator's web chat, and the orchestrator sends it to the Python service (FastAPI) that runs PandasAI. The service reads it in memory, cleans each file, computes derived fields, attaches a JSON schema to every table and caches the result, so each question starts from prepared data.
  2. Split the request. The orchestrator, the first app to give the service a chat interface, sends the user's text to an LLM, which breaks it into specific questions and picks the files each one needs.
  3. Write the query. For each question, PandasAI sends the table schemas and the question through AI Gateway to Qwen 2.5 Coder 32B on Workers AI. The model returns Python that runs SQL, and that code runs locally against the cached tables. Several questions can go in one call.
  4. Follow up. Each conversation keeps its own context. When a follow-up refers to a small earlier result, like “of those five, which one…”, up to 29 rows of that result go into the prompt so the model filters on the right records.
  5. Summarize. The orchestrator sends the results and the original question to Gemma 4 26B A4B or gpt-oss-20b on Workers AI for a short narrative above the table. Charts get a caption only.

Design decisions

  • Schema to the model, data stays put. The model sees column names, types and descriptions, and writes the query; the rows are queried where they live. Every model call goes through AI Gateway to Workers AI models only, and the data isn't used for training.
  • Fix the domain in the schema, not the prompt. HMIS race fields are separate yes/no flags, not one category, so a breakdown has to count each flag on its own. The model kept writing a CASE WHEN that put each person in a single bucket. That rule now lives in the Client schema, so every query gets it. Likewise, HMIS uses LengthOfStay in Enrollment to mean the prior living situation; the service renames it and computes the real length of stay from entry and exit dates.
  • Basic SQL is the point. Nothing here breaks ground on query generation. The queries are basic SQL, so a 32B coder model at temperature 0.1 is enough, and the effort goes into the schemas.
  • Two LLM roles, kept apart. One model writes code against a strict contract: a typed result of a dataframe, number, string or chart. A separate model writes the narrative. The summary is Markdown; styling belongs to the app.
  • Models move; the gateway stays. The orchestrator ran on Gemma 4 on local inference before moving to Workers AI, and the query model moved the same way. Behind AI Gateway, a move is an endpoint and a model name.
  • Built as a service. The Python service takes data access and a schema and runs with it; the orchestrator was the first app on top of it. An admin endpoint accepts a custom system prompt and schema overrides per request and returns the generated code and SQL, so tuning doesn't need a redeploy.

What's next

Diagram: planned design. Every file in the export is loaded once into a single semantic layer with better definitions and declared one-to-many relations: Client and Project to Enrollment, and Enrollment to Exit, IncomeBenefits and Services. The PandasAI agent joins across the whole model and runs the SQL locally. A chat app replaces the first orchestrator interface; staff still upload the export there, and it still goes to the Python service. Every model call goes through AI Gateway with DLP to Workers AI.
Planned: one semantic layer over every file, a chat app, and DLP on AI Gateway.
  • One semantic layer. Today the model makes simple joins over the fewest files that answer the question, and it struggles with the many one-to-many relationships between HMIS files. Users then ask more questions. Loading every CSV once into a single view, with the right schema and declared relations, lets each question and its follow-ups reach across all of them.
  • Better schema definitions in PandasAI, to go with it.
  • A chat app. A proper conversation UI, with the orchestrator inside it. Uploads work the same way: the export goes in through the chat and on to the Python service.
  • DLP on AI Gateway, scanning prompts and responses.