Home Ai Tools Article 3
Part 3 Generative AI

himi-ai: Reading Medical Aid Contracts Automatically

Image: AI-generated


himi-ai: Reading Medical Aid Contracts Automatically

A digital clerk reads contract PDFs, checks itself, and delivers an importable table.


1. What it's about

In Germany, individual medical aid contracts (Hilfsmittel-Einzelverträge) between statutory health insurers and providers are typically delivered as multi-page PDFs – with a contract header, several annexes, price lists, supply areas, approval rules, and aid identifiers (Hilfsmittelnummern). Transferring this content into a master data system by hand is tedious and error-prone.

himi-ai automates this step: a PDF becomes structured JSON and finally an importable CSV file for the GFI system. The human only reviews the result in the browser instead of typing every line.

In one sentence: Instead of reading contracts line by line, we let a digital clerk (AI + classical software) prepare the contract – and only check the result at the end.


2. The idea in one picture

Three stations, each with a clear responsibility:

[PDF] ─► 1. Extract ─► 2. Understand ─► 3. Classify ─► [CSV]
         (classical)   (AI)              (classical)

This separation is intentional: the AI does what rules cannot (understand language, recognise hierarchies). Everything that can be computed reliably stays classical software.


3. Station 1 – Extracting (classical software, no AI)

PDFs are not tables but "printed paper in digital form". Before any AI sees anything, a classical extraction step runs:

🟦 Analogy: An intern who can read very carefully but doesn't understand anything. They deliver clean raw text and clean raw tables.


4. Station 2 – Understanding (HERE the AI enters)

Only now does the language model come into play – and it is strictly framed:

🟦 Analogy: An experienced clerk with an empty, clearly structured form. They only fill in what they find, and nothing that doesn't belong on the form.


5. Station 3 – Classifying (classical software, rules)

What the AI delivers is cleanly structured – but not yet professionally validated. Classical software takes over again:

🟦 Analogy: The quality inspector who matches the filled-in form against the catalog and flags typos.


6. Who does what?

AI does Classical software does
Recognise hierarchy (annexes, sections) Extract PDF text and tables
Assign values to the right fields Stitch tables across page breaks
Handle language variants ("AC/TK", "Abrechnungscode") Regex validation (HiMi, PZN, IK, LEGS)
Summarise connected text Map into the domain model
GKV catalog lookup
Apply approval rules from free text

Rule of thumb: Language understanding = AI. Computable rules = classical software.


7. Why the AI doesn't make things up

Hallucinations are the biggest risk with generative AI. In the himi-ai stack this risk is contained in several places:

In short: the AI may propose, the human and the rules decide.

Try it yourself

The most important sanity check is classical software: pure regex validation of the identifiers. Here is a self-contained example that checks an AI proposal field by field against the formal patterns – no LLM, no network, just the standard library. A proposal that doesn't pass this gate never reaches the database.

import re
from dataclasses import dataclass

PATTERNS = {
    "himi":  re.compile(r"^\d{2}\.\d{2}\.\d{2}\.\d{4}$"),  # e.g. 15.25.01.0001
    "pzn":   re.compile(r"^\d{8}$"),                        # 8-digit PZN
    "ik":    re.compile(r"^\d{9}$"),                        # 9-digit IK
    "legs":  re.compile(r"^[A-Z0-9]{2,6}$"),                # billing code
}

@dataclass(frozen=True)
class Ok:
    field: str
    value: str
@dataclass(frozen=True)
class Err:
    field: str
    value: str
    reason: str

def validate(field: str, value: str):
    """Pure function: (field, value) → Ok | Err."""
    pattern = PATTERNS.get(field)
    if pattern is None:
        return Err(field, value, f"unknown field: {field}")
    if not pattern.fullmatch(value):
        return Err(field, value, f"does not match pattern {pattern.pattern}")
    return Ok(field, value)

def validate_record(record: dict):
    """Validates all fields of an AI proposal."""
    return [validate(k, v) for k, v in record.items()]

# Example: an AI proposal from station 2 – some values are deliberately broken.
ai_proposal = {
    "himi": "15.25.01.0001",   # ok
    "pzn":  "12345678",        # ok
    "ik":   "12345",           # too short
    "legs": "AC/TK",           # special chars → invalid
}

for result in validate_record(ai_proposal):
    print(result)

Extension idea: split validate_record into a partition(results) helper (all Oks vs. all Errs) and add a small reporting function that only shows the reviewer the problematic fields in the UI – pure data transformation, easy to test.

That's exactly what we do next: partition cleanly splits the results into "fits" and "doesn't fit", and review_report turns the bad ones into the Markdown list the reviewer sees in the UI. Both are pure functions over the validation results from the first block.

import re
from dataclasses import dataclass

PATTERNS = {
    "himi": re.compile(r"^\d{2}\.\d{2}\.\d{2}\.\d{4}$"),
    "pzn":  re.compile(r"^\d{8}$"),
    "ik":   re.compile(r"^\d{9}$"),
    "legs": re.compile(r"^[A-Z0-9]{2,6}$"),
}

@dataclass(frozen=True)
class Ok:
    field: str
    value: str
@dataclass(frozen=True)
class Err:
    field: str
    value: str
    reason: str

def validate(field, value):
    pat = PATTERNS.get(field)
    if pat is None:
        return Err(field, value, f"unknown field: {field}")
    if not pat.fullmatch(value):
        return Err(field, value, f"does not match {pat.pattern}")
    return Ok(field, value)

def partition(results):
    """Pure split: (oks, errs)."""
    oks  = [r for r in results if isinstance(r, Ok)]
    errs = [r for r in results if isinstance(r, Err)]
    return oks, errs

def review_report(oks, errs) -> str:
    """Markdown report containing only the problematic fields."""
    lines = [f"✅ {len(oks)} fields ok, ❌ {len(errs)} fields to review"]
    for e in errs:
        lines.append(f"- **{e.field}** = {e.value!r}: {e.reason}")
    return "\n".join(lines)

ai_proposal = {
    "himi":  "15.25.01.0001",
    "pzn":   "12345678",
    "ik":    "12345",         # too short
    "legs":  "AC/TK",         # special chars
    "color": "blue",          # unknown field
}

results   = [validate(k, v) for k, v in ai_proposal.items()]
oks, errs = partition(results)
print(review_report(oks, errs))

The reviewer in the UI only sees the real problem fields – the AI proposes, classical software checks, and a human decides about the remaining set.


8. What does the user see?

The browser flow is deliberately slim:

  1. Upload contract – PDF via drag-and-drop on the upload page.
  2. Processing – a live log shows the steps: "tables extracted", "LLM structured", "LEGS propagated", "GKV catalog matched".
  3. Editable form – contract header, annexes, LEGS, HiMi ranges, prices, approval requirement. Everything is visible and editable.
  4. "Open as Excel file" – produces a CSV file (semicolon-separated, UTF-8 with BOM) that can be imported directly into the GFI system or Excel.

In the in-person meeting the live demo of the running tool and the dialogue carry the hour – this PDF is the compact companion reading.


9. Status & roadmap

Stable today:

Roadmap (discussion points with GFI):


10. Contact & next steps

Goal of the meeting: jointly clarify how himi-ai can dock onto GFI's contract onboarding process – and which annex types we prioritise next.

📄 As PDF