Inspiration

Switzerland's list of reimbursed medicines, the Spezialitätenliste (SL), attaches a condition to many drugs: who qualifies, at which dose, for how long. The Federal Office of Public Health (BAG) publishes 1,688 of these "limitations" in German, French and Italian, and each language version is binding.

Gleichlaut is for BAG's Sektion Arzneimittelaufnahmen, which keeps these texts, and the language service that translates them. Declared maturity: the audit is a deployable prototype; drafting is a feasibility study running in shadow mode.

In today's list, Nemluvio is reimbursed "every 8 weeks" in German and French and "ogni 4 settimane" in Italian. At the published price, the two readings differ by CHF 13,577 a year per patient. Diaphin's surcharge is 6% in German and French and 12% in Italian. Amvuttra's French stop rule says an NIS increase "≤ 10" points where German and Italian say "> 10"; the stop rule governs CHF 338,743 a year per patient at list price. The Federal Supreme Court has ruled on such a divergence: the Federal Supreme Court followed the French and Italian over a German-only wording in the rule BAG uses to price SL medicines (BGE 144 V 14).

What it does

The audit, today, with no model. Gleichlaut reads every limitation in German, French and Italian and turns the list into 18 issues to decide (38 findings in all: 27 from the checks plus 11 terminology notices), each with the three texts side by side and the conflicting values marked. Two blind Claude annotators, with a third on disagreements, confirm 12 findings that change a reimbursement condition (ten were already on our own earlier census of the register, from which the rules were built): Nemluvio's interval, Diaphin's surcharge, the 16 kg limit missing in Cresemba's French, a French-only 62.5 kg rule in three etanercept products, Dupixent's weight tiers. Its operator and negation module (opt-in: 6 of its 31 register findings confirmed so far) found divergences that appear on no list we could find: Amvuttra's French stop rule says an NIS increase "≤ 10" points where German and Italian say "> 10", and the Italian of Beyonttra and Vyndaqel says "on deve" (for "non deve"), so a ban on combining reads as an obligation. New ones keep arriving: since 1 August 2026, Keytruda's Italian field has held the French text.

Drafting, with Apertus. When BAG changes the German, Apertus drafts the French and Italian. The old text stays locked outside the edit, numbers never reach the model (code writes them back), and frozen deterministic checks, not a model, decide which drafts are ready and explain each flag in the reviewer's own language.

  • Apertus 8B on an ordinary CPU, fully offline: on 166 held-out amendments its drafts need 33.1% fewer edits to reach BAG's published text than the old text with code-updated numbers (a draft takes 200.5 s on a 4-core CPU, so even ten times today's volume fits one server), and its ready drafts carry 0 meaning errors in the amendment (0 of 62) (25 of the 62 still want one BAG house term).
  • On the live feed: on BAG's August and September 2026 lists the 8B's drafts needed 28.4% fewer edits (September alone, every item new to us: 24.6%).
  • Apertus 70B as Public AI served it, optional (Public AI is the hackathon's shared gateway; we could not verify which weights it serves): 42.4% fewer edits, at CHF 0.0024 per draft at a Swiss provider's price; at ten times today's volume, CHF 1.36 a month.

Maps onto BAG's process. One audit run takes about a minute of CPU and no model. Translators get XLIFF for STAR Transit and a TBX termbase mined from BAG's own wording; decisions export as CSV for the federal records system (GEVER); drafts come out in the shape of BAG's own FHIR publication feed; the audit gives BAG its own check of the "translations congruent?" question in its triennial review, for the whole list. At runtime it contacts nothing but BAG's public feed (and, with the 70B, the office's own endpoint). A German phase-1 pack (decision memo, project mandate, protection-needs analysis, one case sheet per issue) is ready to hand over.

How we built it

  • Numbers are code's job. Every number and unit is replaced by a slot before Apertus sees the text and written back by code afterwards; the 8B runs with a logit mask that blocks digits outright.
  • Apertus does the wording, nothing else. We measured where Apertus is strong and where code is stronger: free translation, judging equivalence, typing numeric conditions and filtering findings all went to code, each with its measurement (failure modes).
  • Pre-registered evaluation. Every rule was written down before the runs it judges (the pre-registration states the one exception), the checks were hashed before any output existed, and the 166 held-out amendments come from SL changes never seen during design. Labels come from two blind Claude annotators and a third on disagreements, under a published protocol anyone can re-run.
  • Model size by measurement. The 8B (Q8_0, on CPU) passes our pre-registered rule, so it is the default; the 70B and a smaller quantisation were measured on the same items.
  • Every number traced. make run (Docker, CPU, no network) replays every recorded Apertus call and re-derives the results; claims.tsv maps each number in the README, the report and this page to its results file.
  • Stack: Python standard library for the audit, llama.cpp with Apertus 8B GGUF builds, an optional OpenAI-compatible 70B endpoint, GitHub Actions CPU runners for the 8B runs, a single-file review register, Remotion and ElevenLabs for the film.

Challenges we ran into

  • Free translation was not good enough. Asked to translate the new German paragraph, the 8B matched BAG's published text 0 times in 120; the answer was a locked rewrite that changes only what the German change requires, with numbers taken away from the model entirely.
  • Our first out-of-sample run failed its own rule. The 70B left German unflagged in 4.1% of items, so we added checks and drew a new held-out set.
  • Live data is harder than a test set. On the live August and September lists the checks flag 81.8–92.7% of drafts, above the 70% our pre-registered acceptance rule allows; as a review aid drafting still saves 28.4% of edits there.

Accomplishments that we're proud of

  • Real divergences in a binding federal list, found with no model, each with the three texts side by side.
  • Apertus 8B on an ordinary CPU, fully offline, with 0 meaning errors in its ready drafts on held-out data.
  • 69 hashed predictions frozen (34 confirmed findings, 35 unconfirmed ones as a base rate), to be scored against BAG's own next corrections.

What we learned

Use the model where it is strong and let code do the rest: with the work split that way, the 8B on an ordinary CPU passes the same pre-registered rule as the 70B on held-out data; on the live months neither passes it yet. And write the rule down before the test.

What's next for Gleichlaut

The audit and shadow-mode drafting are scheduled to run on the live feed on the 2nd of every month (GitHub workflows; systemd timers and Kubernetes CronJobs for an office); its findings are frozen as hashed predictions, to be scored against BAG's own next corrections. Next for drafting: BAG's mined house terms as a pre-registered check, and accepting drafts after only a light check. The same audit already runs on MiGeL and the Analysenliste, BAG's other trilingual lists.

Declared tools: Claude Code for coding, blind Claude annotators for labels; Apertus is the only model at runtime.

Built With

Share this project:

Updates

Submission history