Slateline — About the project

The advertised film tax credit is never the real number. This computes the real one, live, with receipts.


Inspiration

Every US state advertises a film incentive as a percentage — "Georgia 30%" — and producers pick shooting locations substantially on those numbers. The decision moves 15–25% of a film's budget.

The advertised rate is never what you bank. Four things sit in between, and no public tool prices any of them: whether the annual funding pool is already exhausted, how much of your budget actually qualifies, what it costs to move the production there, and what the credit is worth by the time it is finally paid. A credit is a claim on future money, and pricing it as money is the most expensive mistake in the category.

Answering it properly today takes a specialist two or three days across forty badly organised government websites. The existing tools publish hand-curated rate databases that lag legislative sessions by weeks. We wanted the number a producer can take into a financing meeting and defend line by line.


What it does

Give it a budget — pick a sample, type it in, or upload a real budget PDF that Gemini reads off the topsheet. For each jurisdiction it searches the live web for the statute and recent legislative changes, has Gemini extract the rules into a structured object, computes the benefit in deterministic Python, prices relocation off real routed distances, then runs a second search pass whose only job is to disprove the first. Every figure carries a dated source link, and anything it cannot verify is listed as can't verify rather than guessed.

A \$2M indie drama out of Los Angeles, against hand-verified statutes:

advertised net benefit
Louisiana 25% + 15% = up to 40% \$265,564
New Mexico 25% + 20% = up to 45% \$260,215
Georgia 20% + 10% = up to 30% \$188,825

The advertised order and the real order are not the same. The state with the highest headline rate does not win, and Louisiana and New Mexico advertise the same 25% base yet differ by \$5,349 — for reasons no rate table contains.


How we built it

The core bet: the language model never does arithmetic. It reads and quotes, filling structured objects; every computation happens in pure, unit-tested Python with no I/O and no model calls. That is enforced by configuration — forced function calling with mode: "ANY" — not by asking nicely.

Four things then happen to a headline rate on its way to a bank balance, and the calculator does each explicitly. Qualification: only categories the jurisdiction actually credits, with per-person wage caps, and minimum spend as a cliff, not a ramp — miss it and the credit is \$0. Monetization: a transferable credit must be sold to a taxpayer with in-state liability, clearing around 90 cents. Timing: the credit arrives months after wrap, discounted back at 12% a year, with the mandatory audit subtracted before discounting because it is paid to get the credit. Relocation: flights scaling with real routed distance, plus lodging and shipping.

$$\text{net benefit} \;=\; \underbrace{(\text{cash value} - \text{audit})\,(1.12)^{-\text{months}/12}}_{\text{what the credit is worth today}} \;-\; \text{relocation}$$

Georgia end to end: a \$400,000 face credit sells at 90 cents for \$360,000, loses \$15,000 to the audit, another \$53,933 to waiting eighteen months, and about \$102,000 to fly and house the crew 3,498 km away. \$188,825. Advertised as up to 30%; the production keeps 9.4%. That collapse is the product.

And the thing no rate table can answer: shoot in one state, post in another. Every comparison tool assumes a single destination, because a table has one row per place. Shoot Louisiana, post New Mexico is \$273,911 — \$8,347 more than any single location. The more valuable output is when splitting loses: post in Georgia drops that leg below Georgia's minimum spend and earns nothing, a trap invisible to any single-destination ranking that a producer would discover at audit.

Stack: Gemini 2.5 Pro on Vertex AI (forced function calling, multimodal PDF), Parallel Search API (three discovery plus four falsification queries per jurisdiction, live per request), Google Maps Distance Matrix, an ADK LlmAgent over six tools, FastAPI on Cloud Run, React 19 + Vite + Tailwind on Firebase Hosting, and 313 backend / 92 frontend tests in CI.

Live search is structural, not decoration: you physically cannot answer "is New Mexico's pool exhausted right now?" from frozen weights. And the two passes ask structurally different questions — discovery asks "what is the rate?" and is rewarded for finding an answer; falsification asks "what would prove that wrong?" and looks somewhere else entirely.


Challenges we ran into

Schema validity is not answer validity. Forced function calling guarantees a well-formed object, not a meaningful one. A live run returned qualifying: {} — schema-valid, dataclass-valid — computed a \$0 credit, and raised no error anywhere.

Arithmetic on a hallucinated input is still a hallucination. Keeping the model away from the maths protects less than it looks, because the inputs still come from a model. Adversarial payloads against the running server found base_rate: 5.0 returning a \$9,520,000 credit on a \$2M film with HTTP 200 — the signature of a statute reading "30%" extracted as 30. We now refuse rather than clamp: silently correcting 30.0 to 1.0 would invent a 100% credit and rank it first, which is worse than an honest gap.

Everything downstream of a bad input is impeccable, which is the problem. The agent's first live run recommended the wrong state with flawless reasoning — a misread cap had crushed a \$415,625 credit to \$0, the production still paid to relocate, so its net benefit went negative. Every step after the bad input was computed correctly from it, which is exactly what made the wrong answer persuasive.

A conflict detector's hardest job is staying quiet. The adversarial pass's first run reported four "material contradictions" for Georgia, every one of the form "we have: not stated" against a source saying "no cap" — sources that agree. It had not disobeyed the rule against treating silence as contradiction; it had treated our own missing value as a position a source could contradict. Fixed in code, not in the prompt. Georgia went 4 findings to 1, New Mexico 7 to 2, and the survivors are real: New Mexico's annual pool is reported as \$140M by us and \$100M and \$130M by others.


What we learned

Hand-verifying the statutes changed which jurisdiction the tool recommends. Every number had passed the full test suite — because those tests recorded our own output. That is a regression test, not a correctness test. Reading O.C.G.A. § 48-7-40.26 and NMSA 7-2F-15 directly turned up three real errors in a day, including a missing \$500,000 per-person cap and an exclusion that had overstated New Mexico by \$67,500. A well-tested pipeline computing from unverified inputs is a confident wrong answer, and no amount of testing the arithmetic finds it.

An integration can be live, correct, and entirely decorative. Maps returned real routed distances from day one, drawn on the map and printed on the memo. But airfare above 800 km was flat per person, so New Mexico at 1,266 km priced identically to Georgia at 3,498 km. The distance was fetched, displayed, and could not change a single ranking. There is now a test asserting that flying further costs more — which sounds too obvious to need testing, and was false for months.

Never ask a model a fact about your own pipeline. We asked Gemini for each source's retrieval date. It returned dates up to two years stale for pages fetched that second, silently downgrading fresh data to "stale" confidence. System-time facts belong to the code that did the fetching.

"We checked and found nothing" and "we couldn't check" must never render the same. The most dangerous state in a verification feature is the one that looks reassuring because it is empty.


Known limits

Only three jurisdictions are hand-verified — and checking three produced three real errors, so the rate in the unchecked ones should be assumed similar, not zero. Qualification is a boolean per category, which cannot express New Mexico's real rule, so we understate it by \$16,875 rather than overstate it by \$67,500; rounding toward the smaller error is a choice, and it is stated on the card. Timing defaults are our assumptions, visible and editable for that reason. Non-USD programmes are refused rather than converted silently.

Not tax advice. Every figure is an estimate for comparison, and the tool says so on its face.

Built With

Share this project:

Updates

Submission history