MCP App Store
by KeenEthics
USAFacts Spending Explorer icon

USAFacts Spending Explorer

By USAFacts

Get New App Alerts

Overview

Understanding what your school district actually spends money on usually means digging through PDFs and state portals. USAFacts Spending Explorer puts that information in one place, right inside ChatGPT. Ask specific questions: how much goes to instruction versus administration, how per-student spending compares to a nearby district, or how funding for a particular program has changed over time. USAFacts Spending Explorer gives clear answers alongside an interactive chart — select any category to see its exact share and click to dig even deeper. On the other side, explore how revenue is generated from a mix of state and local taxes, federal sources, and more. Then, ask a follow-up the same way you'd ask a person: “What about last year?” or “How does that compare to Dallas?” You'll still get a sourced answer, paired with USAFacts' way of turning complex government finance data into something easy to follow. Try: - "What does Los Angeles Unified spend the most money on?” - "Is Austin ISD's spending going up or down?” - "How does Austin ISD compare to Dallas ISD?" This is a beta. To start, it covers school districts in Texas and California, but it won't stay that way for long. We're expanding to more states and adding new ways to explore the data with every update. Follow our progress here: https://usafacts.org/spending-explorer/

Tools

call_source_tool

ChatGPT
Run one of a source's tools and get its rows back to reason over — never to answer a spending or revenue question with. Use get_source first for the tool's name, then get_source_tools for the exact schema for its arguments, which are the only valid values here. This renders NOTHING and skips the data-decisions checks entirely, so its result is never itself the answer. Use it ONLY for a lookup that isn't the figure you'll report: resolving a district name to the id other tools filter on, checking what a code covers, or comparing two candidate calls before committing to one. The moment you have the query that produces the figure you intend to report, call prepare_answer with it directly — composing the answer from a call_source_tool result instead runs the same query twice AND skips the checks a real answer requires. The rows come back in structured_content.rows, all of them, exactly as the source returned them, with its citation and its pragmatics caveats. Read the caveats: they fired for the rows you just got, and are the source telling you how its own numbers can be misread. Each is prefixed with its severity. A [correctness] one means the figure is wrong, not merely imprecise — if it describes the rows you were about to sum, re-filter and call again rather than footnoting it. [interpretive] means the number is right but easy to read wrongly, and [informational] is context. The source's own playbook, from get_source, defines these; their absence is not a clearance. Some sources also return metadata, facts about the result rather than columns of it: row_counts is the detail records behind each row, in rows order. Use it to check what your filters matched, never as a figure or a role.

data_decision_rules

ChatGPT
USAFacts data-decisions guidance, served from the current rule set: how to label a query's roles and declare an adjustments entry so it satisfies whatever data standard applies. Call this ONCE PER TURN, before that turn's first prepare_answer call — not after being refused for a rule. A refusal names the rule; this is how you satisfy it. It returns the same content every time within a conversation, so do not call it again before a second or third prepare_answer in the SAME turn. A new turn calls it again, once.

generate_sankey

ChatGPT
Show one place's whole finances — money in and money out — as a flow diagram, live. This is the renderer for a question that names no year, category or comparison: 'show me <place>'s finances', 'where does <place>'s money go', 'how much does <place> spend' with nothing further qualifying it. The moment a year, a category or a second place is named, prepare_answer is the right tool instead. Pass an entity_ref taken verbatim from a resolve_entity candidate. Do not invent refs; if you don't have one, call resolve_entity first. This renders the panel directly. structured_content.brief carries the figures and pragmatics the data publisher's own caveats about how they were computed — signed sums, what is and isn't excluded. Each is prefixed with its severity, and that decides what it asks of you. A [correctness] caveat means the figure is wrong rather than merely imprecise; state what it says about the number, and never report the figure as though it didn't apply. [interpretive] and [informational] ones are not a reason to write more or less: never contradict one, and mention one only where it changes how a figure should be read. If the result reports instead that the chart is unavailable or the source holds no finance data for the place, say so plainly in a sentence or two and stop.

get_source

ChatGPT
Get one source's playbook: what it covers, which of its tools answers which question, what its codes mean, and how its own numbers can be misread. Pass a source_id from list_sources. Read the playbook before composing any query. Several defensible definitions of 'spending' exist in this data; the playbook says how to choose between them, and the result tells you what your choice counted — state that when you report a figure. The response also lists the source's tools by name and summary. That list is how you pick one; its argument schema comes from get_source_tools, which you call before composing anything.

get_source_tools

ChatGPT
Get the exact contract for named tools on one source: each one's argument schema, its description, and response — a line saying what the call gives back. get_source lists a source's tools by name and summary; this is where their schemas live. Name every tool you may call in one request, and only those. Call this before the first time you use a tool in a conversation. Composing call_source_tool or prepare_answer arguments without it means guessing at parameter names, and a guess comes back as a validation error rather than data. response is what prepare_answer's roles, measure_kind and unit are read off — it says what the rows hold, which is the thing you must declare and must never assume. A name that isn't real comes back with the source's actual tool names; use one of those rather than inventing another.

list_sources

ChatGPT
List the school district finance sources this app covers. Call this when you are not sure whether a place is covered, or which source holds it. Returns each source's id, name and coverage. Follow up with get_source for the chosen source's playbook, then resolve_entity.

prepare_answer

ChatGPT
Run a source's tool, check the result against USAFacts data-decisions standards, and build the panel for it. This is the renderer the moment a question names a year, a category or a comparison: 'how much did <place> spend in 2024', 'how has <place> changed since 2019', 'what does <place> spend on transportation', 'compare <place> and <place>'. A plain 'how much did <place> spend in <year>' is this one, not generate_sankey — a year makes it so. This is how EVERY spending or revenue figure gets answered, including a single total that obviously wants no chart. It may come back with no plan_id to render, but it is never instead answered from a call_source_tool result. Takes queries, a LIST of {'source_id', 'tool_name', 'arguments', 'roles', 'measure_kind', 'unit'} (the first three are the fields call_source_tool takes, plus optionally 'subject'). Read the tool's contract from get_source_tools first — roles, measure_kind and unit are read off its schema and its response line, never guessed. The server runs the query itself, so pass the arguments, never rows you retrieved earlier. Nothing is shown to the user yet — this only fetches and checks, so it is safe to call as many times as you need while composing a query or an adjustments entry; a refusal names the rule and what would satisfy it, so retry rather than guess. When the response includes a plan_id, call render_answer(plan_id) ONCE to display it. When it does not — no data matched, no chart could be built for these figures, or the answer draws on several sources with no single chart — the reply already IS the answer. ONE answer is ONE prepared call. Retrying REPLACES what you had; it never adds a second panel. So put everything the question compares into ONE call's queries and render the one plan_id it returns. If a call is refused, fix THAT call and send it again — never answer the same question by making several calls and rendering each. Splitting a comparison into one call per subject does not satisfy the rule that refused it, it only stops the rule from applying, and it produces exactly the side-by-side totals the standards forbid. A REFUSAL IS CORRECTABLE, NOT A DEAD END. It names the rule and what would satisfy it. If it names an adjustment, retry with an adjustments entry: you declare the METHOD, and the server fetches the reference data and does the arithmetic. A retry with nothing declared is refused again, every time. Never compute an adjusted figure yourself and never pass one in — you supply the query, the intent and the method; the server supplies every number and any caveat with it, and you quote both. Some refusals instead say the declared intent doesn't match what the query returned; those are fixed by changing the query or the intent, never by adjustments. Either way, do not repeat an identical call expecting a different result — change something, or ask the user for what is missing.

render_answer

ChatGPT
Show the panel a prior prepare_answer call already built and checked against USAFacts data-decisions standards. Takes only the plan_id from that response — never construct one, and never pass query arguments here: everything shown has already passed the checks against our own fetch, and this tool has no way to check anything new. Each plan_id renders EXACTLY ONCE. Call it the moment you have one you intend to show, and never again — a second call with the same id, or a stale or expired one, has nothing new to show. Never call it speculatively while still negotiating a query.

resolve_entity

ChatGPT
Resolve a place name to candidate entities within one source, live. Pass the source_id and the place's distinctive word ('Austin', 'Houston') rather than its full legal name — the backend matches one contiguous substring against official names, so a partial name returns every match while a fuller one can match nothing. Returns candidates, each with an opaque ref, the source's official display name, and whatever attributes that source carries. If the question names no place at all ('how much is spent on schools?'), ask the user which place before calling this — it needs a name to match against. If exactly one candidate fits, use its ref; if several match, pick using the official name and attributes, or ask the user. Zero matches → retry with a shorter, more distinctive word. Reuse a result you already have rather than resolving the same place twice.

Capabilities

Interactive

Example Prompts

Click any prompt to copy it.

Similar Apps