16 weeks building German billing and dental IGs with agents — and the IP audit that unpublished 115 versions — Aug 20, 2026
Two missing domains in German ambulatory care
Malte Sussdorff (Cognovis) opened by saying he'd overprepared and then spent the next fifty minutes proving it — 16 weeks of work spread across 9 repositories, 2,202 commits, and a fair number of scars. The origin story was simple. German ambulatory medicine has solid FHIR coverage in de.basisprofil.r4, the KBV profiles, ISiK, and the BfArM terminologies, but two domains every practice touches daily were completely absent: billing and dental. Malte started fhir-praxis.de for ambulatory care and fhir-dental.de on top of it on the same day, April 30, 2026, and picked up terminologies as a third repo shortly after. Gematik is pushing FHIR across the German healthcare sector, and DiPag (electronic invoicing for private insurance) is coming next year — Malte sees the FHIR implementation guides as preparation for that transition.
Agent-hallucinated SNOMED codes
The first serious bruise came from SNOMED. An RF2 audit against the German edition surfaced inactive codes, wrong concepts, and fabricated SCTIDs that failed the Verhoeff check digit. Hand-typed or agent-recalled SCTIDs were only around 65% correct. Prompting alone did not fix it. The only thing that worked was a deterministic RF2-verified allowlist (check_snomed_codes.py) running in pre-push and CI — agents checking agents, with a hard gate.
Now, how do any of you prohibit the agents from hallucinating codes? We tried through prompting, and the only thing we ended up with was basically having agents check the agents and have a deterministic guard.
Nikolai's alternative was to push validation out to a real terminology server — verify each code against a $expand or $lookup before it leaves the model. Malte agreed the linter is functionally the same pattern; his version happens to run locally because the German National Edition is already loaded at runtime and the display text is resolved there. The IGs themselves now only reference bare SCTIDs and system URIs.
The IP audit — the day 115 versions came down
Ten days before the call, on August 10, an IP audit found that publicly published IG artifacts contained licensed display texts. Cognovis was allowed to load the catalogs into its own application under the affiliate license but not to redistribute them in transformed form. The cleanup was significant: 22–23 dental terminology packages tombstoned on the 10th, 115 IG versions unpublished on the 11th (76 of them from fhir-praxis), 1.3 GB of site content quarantined, and fhir-terminology-de retired entirely on the 16th.
Just because we had stuff behind authentication and access control doesn't mean we were legally allowed to republish and then give it to customers. An authentication barrier is access control, not a redistribution license.
Three-tier terminology publication policy
Out of the audit came a machine-readable, centrally enforced policy that classifies every code system exactly once. Unclassified defaults to deny, and every tier-two enumeration requires pre-registration with a concept ceiling, a rationale, and a sign-off.
- Tier 1 — codes and displays public. Own IP, HL7/FHIR, BfArM ICD-10-GM/OPS/ATC, GOÄ, GOZ, LOINC, DICOM.
- Tier 2 — bare codes only, curated selection, never the full catalog. SNOMED CT, BEMA, BEL II, EBM, KBV key tables.
- Tier 3 — system URI reference only, no code list. The customer runs their own ETL. HZV/selective contracts, PVS vendor catalogs.
Malte flagged one live curiosity: Germany's official BfArM Zentraler Terminologieserver (ZTS) does not actually speak the FHIR terminology API — the CapabilityStatement 404s and $expand is broken, so it functions as a token-gated download portal in practice.
Dental Core and the odontogram demo
Wiring up the open-source react-advanced-odontogram (MIT) made it obvious that fhir-dental-de had been dragging in too much German-specific billing and contract law. Malte split it into de.cognovis.fhir.dental.core — country-neutral, purely clinical, no German dependencies, FDI/ISO 3950 tooth numbering (the non-US convention). The odontogram in the presentation ran live against Dental Core 0.3.0: a synthetic FHIR Bundle passed through importFhirBundle(), the same call used when the chart is backed by a live FHIR store. Export and upload buttons on the widget round-trip the Bundle through the same profile. Dental Core is already publicly consumable at fhir.cognovis.de/dental-core under Apache-2.0.
Malte is exploring working with two German universities to have the IG medically reviewed and potentially submitted through the official route — but only after the profiles have real deployment behind them.
Billing Core — a jurisdiction-neutral rule engine
Billing Core (de.cognovis.fhir.billing.core) is jurisdiction-neutral and has zero dependencies. It splits into three layers:
- A catalog layer profiling
ChargeItemDefinition(viaBillingCatalogCarrier) — one entry per billable code with valuation. - A rule layer with 14 generic rule classes (via
BillingRuleCarrier) — factor rules, quantity limits, mutual exclusions, once-per-case, service requirements. - A contract layer (
BillingOracle) that ships fixtures so an engine can be proven against the ruleset — accepted, rejected, orneeds-reviewwhen the evidence is incomplete rather than silently defaulting one way.
Rules are stored as canonical JSON with a SHA-256 digest — FHIRPath and CQL are structurally forbidden, so rules can be reviewed, diffed, and licensed like data. The live demo drove the published @cognovis/fhir-billing-engine (v0.4.0) directly in the browser against a synthetic German fee-schedule rule set: factor standard 2.3, hard maximum 3.5, once-per-case limit, mutual exclusion. Flip the factor to 3.0 and the engine returns needs-review; flip the quantity to two and it rejects.
A Swiss country package (de.cognovis.fhir.billing.ch) landed in 223 lines of FSH and 18 commits as proof that adding a new country needs only new rule instances and bindings — zero engine changes.
Nikolai asked why TypeScript over FHIRPath. Malte's answer was pragmatic:
Firepath was considered — the answer was yes-ish, performance. For a single German BEMA charge item we sometimes have 11 rules that have to apply, and those cross 10 different FHIR resources.
Release management, codegen, and switching to the gematik toolchain
Cognovis fell into every release hole in sequence: hand-synced version pins that drifted, a stale publisher jar building against the wrong pin, and one fhir-praxis change cascading serially through ~20 packages. Three ADRs now govern releases — exactly one authored version source (fhir-versions.lock.yaml), declared pins treated as lower bounds not equalities (which kills the cascade), and external packages as exact curated selections. No IG repo has publish rights anymore; cognovis-fhir release cut is the full runbook in 12 idempotent gated steps. On August 17 they switched to the official gematik toolchain (fhir-ig-template, fhir-scripts, publish-tools) to reduce pipeline maintenance and align with gematik's infrastructure.
Types are generated from the IGs using atomic-ehr/codegen (MIT), forked as @cognovis/codegen — Malte has already merged four fixes upstream (PRs #138, #155, #165, #166). The model shifted from consumer-side generation (each app running its own generate.ts with hard-coded pins) to producer-side: cognovis-fhir types build generates once centrally, producing one aggregate package (@cognovis/fhir-types) published alongside the IGs. The build is reproducible — 20,256 files, byte-identical across independent cold builds — and a parity gate (types aidbox-parity) blocks installation if the bytes the server validates against differ from the bytes the types were compiled from.
The agent rulebook — every rule is a scar
Cognovis's AGENTS.md isn't philosophical. Every rule maps back to something that actually happened. The current guards include the RF2-verified SNOMED allowlist, a read-only registry-research gate (HL7 IG registry, packages.fhir.org, Simplifier, German publisher prefixes) that returns a reuse/derive/author-new verdict before any authoring begins, the machine-readable tier policy agents read rather than re-derive, a copyright blocklist of 527 patterns (BEMA, BEL II, beb'97) enforced in pre-push and CI, and a build guard that rejects ADR links from published IG pages.
One rule Malte flagged as easy to under-appreciate: "green build ≠ conformant." Only $validate against the installed profile counts. Generated types are not validators, however satisfying it feels when the build turns green.
The commentary problem, and where this goes
Billing rules in Germany live partly in statutes (§5 UrhG, public domain) and partly in annotations and commentaries that are copyright-protected — but customers effectively need both to bill correctly. The rule contract from Billing Core helps because the structure of a rule (class, parameters, effective period) is Cognovis's own work, but operationalising the reasoning from a protected commentary is still unsolved. Malte's open question was whether a rule ETL analogous to the catalog ETL is the answer, or whether an in-house editorial team is the only honest option.
The longer arc is a standardised FHIR path from dental hardware — scanners, imaging devices — through AI-assisted interpretation to structured FHIR observations that can drive prior authorisation and payer billing. Malte is already working with a doctor who passes FHIR data alongside scan images to an AI model for interpretation. Aleksandr Kislitsyn's recent LinkedIn piece on FHIR as a substrate for coding agents came up here as a data point in the same direction.
Malte hasn't approached HL7 Germany or gematik about making the IGs official yet — he wants real deployment and medical partner sign-off first, and plans to start those conversations at FHIR Camp and at the IOP Summit in Berlin in September. Nikolai suggested announcing at the Zurich event to find other people already working on dental FHIR, and drew the analogy explicitly:
HL7 v2 was built like that — the guys built exchange of lab results just internally, and then they gave it to the community. That's how HL7 was started. I think this is a big deal, that you are kicking this off bottom-up, not top-down. Things are usually much better if they're growing bottom-up.