Inspiration

The US tariff schedule shipped three revisions in three weeks: Rev 14 on Jul 29, Rev 15 on Aug 03, Rev 16 on Aug 11. Somewhere in a manufacturer's catalog, a code that cleared customs last month no longer exists. A dead code often has two legal successors, and at eight digits they can be thousands of dollars apart on a single entry. Customs does not tell you which one you picked wrong. The penalty notice does.

Reclassifying one line means chapter notes, section notes, and 218,606 past CBP rulings, none of which join cleanly to a product catalog. A compliance officer does this by hand, line by line, every time the schedule moves. Most lines need no judgment at all. The ones that do are exactly the ones where a wrong guess becomes duty owed, plus penalties, plus a shipment sitting at the port.

What it does

Drop in a catalog export. The control plane splits the work before any model is involved.

A code the official correlation table maps one-to-one is settled by a program and never reaches the model. Sending it there would inflate the autonomy number and buy nothing. What is left is real judgment, and that goes to one bounded classification agent on Gemini 3.7 Flash with four read-only corpora: chapter and section notes, tariff lines, ruling search, ruling texts. On the frozen evaluation set that split sends 288 of 500 items to the agent and settles the rest by table.

The agent's answer is not taken on trust either. Three deterministic gates stand between it and a filing:

  1. Citation check. Every quoted word is re-resolved against the frozen snapshot. Punctuation may differ; words may not. This is what runs when the model hallucinates, and the demo video breaks a citation on camera to show it working.
  2. Duty and cost. Rates, chapter 99 measures, and the money the entry owes are computed outside the model's reach. The agent cannot see duty rates, so it cannot pick a code because the code is cheap.
  3. One signature. Role-gated approval closes the batch. The parts a program can compute never asked for trust; the judgment call left is the officer's, once.

When the description does not say enough to decide, the agent refuses to guess. The question goes out with a named owner, the case waits with its history intact in Cloud SQL, and when a person answers days later the re-run cites the answered fact. It refuses rarely and on purpose: 6 of 500 items, an escalation rate of 1.2% against a design cap of 10%.

How we built it

  • Cloud Run hosts the FastAPI service and the UI, one instance, CPU never throttled because work continues on background threads after the HTTP response is sent.
  • Cloud SQL (Postgres 17) holds every case, transcript, question and answer, so a case waiting days on a person survives container restarts. The same SQL runs against SQLite locally.
  • Vertex AI / Gemini 3.7 Flash through the Google GenAI SDK, on the shared flex tier: half price, several times slower, and nothing here is interactive.
  • The frozen snapshot ships inside the container image on purpose. The legal text a classification cites and the revision that read it are one artifact; a classification that cannot name the text it read is not auditable.

Other data sources used

Every source is a public government endpoint. None needs an API key or scraping.

Source Endpoint Measured 2026-08-17
CBP customs rulings (CROSS) rulings.cbp.gov/api 218,606 rulings, 62.9 MB
Tariff schedule (USITC) hts.usitc.gov/reststop/exportList 35,791 rows, revision 2026HTSRev16
Chapter legal notes (USITC) hts.usitc.gov/reststop/file 98 chapters, 616,847 characters
Consolidated Screening List (ITA) data.trade.gov 25,939 entries
HS2017 to HS2022 correlation UNSD tables + WCO Table II 15,656 code pairs

The four US government sources are uncopyrighted (17 U.S.C. §105). The correlation tables are not redistributable, so the repository ships fetch scripts and no third-party data: make data rebuilds a snapshot from the originals, which also makes the build reproducible from scratch rather than from a checked-in copy of someone else's file.

The WCO publishes the correlation tables stating they "have no legal status" and "constitute a guide only". The architecture takes that literally: the table decides where to look, never what to conclude.

Challenges we ran into

A wrong filename returns HTTP 200 with an empty body, not a 404. An empty screening list means every party passes screening, which is the most dangerous failure available here. Status codes are never sufficient, so the health gate checks byte and row floors and re-checks the sha256 of what landed on disk.

The evaluation set leaked its own answers. Ruling texts open with sentences like "you request a ruling on the applicability of subheading X", so the description carried the answer. Cutting the text before the first tariff reference would have destroyed 23% of the items. Redacting in place instead kept 100% of the product facts, and a gate re-verifies that no description can read its own answer; it caught three regex mistakes, one of which had taken leakage from 0 items to 58.

Deploying it broke it twice. At 1 GiB the container was killed mid-batch with 1030 MiB used, because the precedent index holds 218,606 rulings, and every case the agent had open came back stranded. Then the screen started going blank with a bare "Lost the server:" and no message, because drawing the flow read four tables per case and opened a connection for each: twenty cases asked Cloud SQL for eighty at once. Both limits are now load-bearing settings documented where they are set.

Accomplishments, and what the numbers are

The number worth reporting is not the accuracy of a model. It is how much of a regulated workflow can be settled without asking one, and what has to hold for the rest to be signable.

The evaluation set is frozen, leakage-redacted, and was run exactly once, on 2026-08-30.

Stratum n 8-digit correct Mechanical floor (carry the old code forward)
DEAD_CODE — the prior heading no longer exists 288 88.9% 21.2%
SCOPE_REVIEW — prior heading maps to several 90 95.6% 97.8%
SURVIVED — prior heading maps one-to-one 122 100% 100%
All 500 92.8% 54.2%

Only DEAD_CODE tests reclassification. In the other two strata the prior code and the answer share their first six digits by construction, so one blended number would hide which part of the problem was actually solved.

Citation fault injection, re-run against the same production checker: 423 answers had one word inserted into a real quote. 423 caught, 0 missed. Escalation rate 1.2%; accuracy on what shipped, 94.0% at 8 digits.

The raw run output and the scoring are committed under eval/results/ so the numbers can be re-derived without spending anything.

What we learned

Two caveats measured before submission and reported because the set is run once. Three of the 500 items have a truth code that no longer exists in the current schedule, because HS2022 merged the heading; those are unanswerable by construction and the citation checker correctly refuses them. And the redaction that removed answer leakage also removed some legitimate context, which pushes the number down rather than up.

What's next

The schedule will move again next week. A table settles what a table can settle; one agent argues what needs arguing; a checker holds every word to the source; a person signs once.

Built With

Share this project:

Updates

Submission history