Vorlagensprache (Schema 1)
Eine Vorlage beschreibt ein Dokumentformat: woran es erkannt wird (erkennung), wo jedes Feld steht (felder) und welche Regeln zwischen Feldern gelten (regeln). Agile Extract liest damit PDFs und Bilder aus, ohne generative KI: gleiche Datei + gleiche Vorlagenversion = gleiches Ergebnis.
Jede Änderung ist eine neue Version. Läufe merken sich Vorlage und Version; ältere Läufe lassen sich mit einer neueren Version wiederholen (mit Vergleich je Feld).
Grundlagen
- Koordinaten sind auf die Seite normiert:
x,yvon 0 bis 1, Ursprung oben links.b= Breite,h= Höhe. So gilt eine Vorlage unabhängig von Auflösung, Scan oder PDF. - Seiten zählen ab 1; negative Zahlen von hinten (
-1= letzte Seite). - Text kommt aus der Textebene des PDFs (Sicherheit 100 %) oder aus der OCR (Tesseract, Deutsch + Englisch, mit Lageerkennung und Begradigung; Sicherheit je Wort).
- Zeilen entstehen aus der Lage der Wörter. Innerhalb einer Zeile trennt ein Leerzeichen die Wörter, eine große waagerechte Lücke (Spaltenabstand) zwei Leerzeichen. Reguläre Ausdrücke sollten
\s+statt einem Leerzeichen verwenden. - Reguläre Ausdrücke sind JavaScript-Ausdrücke mit
u-Flag, Groß/Klein egal (außergross_klein: true). Verschachtelte Wiederholungen wie(a+)+werden abgelehnt, feste wie(?:\.\d{3})*nicht.\wpasst nur auf A–Z, 0–9 und_, also nicht auf „ä“: für Wörter\S+oder[\p{L}]+nehmen.
Aufbau
{
"schema": 1,
"erkennung": { "muss": [], "darf_nicht": [], "soll": [], "absender": [], "seiten": { "min": 1, "max": 6 }, "schwelle": 0.6 },
"felder": [],
"regeln": [],
"min_konfidenz": 0.6
}
Treffer
Ein Treffer sucht Text zeilenweise. Genau eines von text oder regex.
| Schlüssel | Bedeutung |
|---|---|
| --- | --- |
text | wörtlicher Text, Leerraum flexibel, Groß/Klein egal |
regex | regulärer Ausdruck |
gross_klein | true: Groß/Klein beachten |
unscharf | erlaubte Tippfehler (0–3, Levenshtein über die Wörter), für Scans |
seite | nur auf dieser Seite (1, 2 …, -1) |
bereich | nur in diesem Bereich { "x", "y", "b", "h" } |
gewicht | Gewicht für die Punktzahl (Vorgabe 1) |
Erkennung
| Schlüssel | Bedeutung |
|---|---|
| --- | --- |
muss | alle müssen vorkommen, sonst 0 Punkte |
darf_nicht | keiner darf vorkommen, sonst 0 Punkte (trennt z. B. Police von Nachtrag) |
absender | Aussteller: mindestens einer muss passen (falls angegeben) |
soll | erhöht die Punkte, ist aber nicht Pflicht |
seiten | Seitenzahl min/max |
schwelle | Mindestpunkte 0..1 für die automatische Auswahl (Vorgabe 0,6) |
Punkte = erfüllte Gewichte (Muss + Absender + Soll) / alle Gewichte. Bei vorlage=auto gewinnt die Vorlage mit den meisten Punkten über ihrer Schwelle; bei Gleichstand die genauere (mehr Regeln), dann die zuletzt geänderte. Passt keine, ist der Status no_template. Vorlagen mit auto: false werden nur fest gewählt.
Felder
{ "name": "versicherungsnummer", "typ": "text", "pflicht": true, "quelle": { … }, "muster": "…", "pruefung": { … } }
| Schlüssel | Bedeutung |
|---|---|
| --- | --- |
name | Kleinbuchstaben, Ziffern, _; beginnt mit Buchstaben |
typ | text, datum, geld, zahl, iban, auswahl, ja_nein, tabelle, mrz |
pflicht | fehlt das Feld, ist der Lauf partial |
quelle | woher der Wert kommt (siehe unten) |
muster | Ausdruck auf den gefundenen Rohtext; Gruppe 1 (falls vorhanden) ist der Wert |
pruefung | Prüfung des normalisierten Werts (siehe unten) |
werte | nur auswahl: [{ "wert": "KOMFORT", "texte": ["Komfort", "Tarif K"] }] |
ja, nein, leer_ist_nein | nur ja_nein: eigene Wörter; leer = nein |
min_konfidenz | darunter gilt das Feld als unsicher (Vorgabe aus der Vorlage bzw. 0,6) |
weitere_quellen | Ersatzquellen (anker, bereich, regex, fest), der Reihe nach versucht, bis eine einen gültigen Wert liefert |
Quellen
art | Schlüssel | Wert |
|---|---|---|
| --- | --- | --- |
anker | anker (Treffer), richtung (rechts, unten, links, oben), abstand, breite, hoehe, luecke, zeilen, vorkommen, bereich | Text neben einem Ankertext |
bereich | seite, bereich | alles in einem festen Bereich |
regex | regex, gruppe, seite, bereich, vorkommen | Treffer im Volltext oder in einem Bereich |
tabelle | spalten, ende, zeile_beginnt, seite, kopf_min, max_luecke, max_zeilen | Zeilen einer Tabelle |
mrz | format (auto, TD1, TD2, TD3) | maschinenlesbare Zone von Ausweis/Pass |
ableitung | feld, pfad | Teil eines anderen Felds (z. B. mrz → geburtsdatum) |
Ersatzquellen: Steht eine Angabe je nach Fassung des Formulars an verschiedenen Stellen, gibt weitere_quellen weitere Fundorte an. Gewinnt die erste Quelle mit gültigem Wert (Normalisierung und Prüfung bestanden); das Protokoll vermerkt „aus Ersatzquelle n“. Liefert keine einen gültigen Wert, zählt das erste Ergebnis, das überhaupt etwas fand. Jede Quelle (anker, bereich, regex) darf ein eigenes muster haben, das dann statt des Feldmusters gilt.
Anker: rechts nimmt die Wörter rechts vom Anker in derselben Zeile bis zur ersten waagerechten Lücke größer luecke (Vorgabe 0,05) und höchstens abstand (Vorgabe 0,6) weit. unten nimmt zeilen (Vorgabe 1) Zeilen unter dem Anker, links bündig mit dem Anker, breite breit (Vorgabe: Ankerbreite, mindestens 0,25) und höchstens abstand (Vorgabe 0,06) tiefer. bereich mit dx, dy, b, h legt einen freien Bereich relativ zur linken oberen Ecke des Ankers fest (ersetzt Richtung und Abstand). vorkommen: 1 = erster Anker, -1 = letzter.
Tabelle: Jede Spalte hat name, kopf (Text im Tabellenkopf), typ und optional von/bis (feste Spaltengrenzen). Sonst liegt die Grenze zwischen zwei Spalten in der Mitte zwischen den Kopftexten. Der Kopf darf über zwei Zeilen gehen. Die Tabelle endet vor der ersten Zeile, auf die ein Treffer aus ende passt, bei einer senkrechten Lücke größer max_luecke (Vorgabe 0,05) oder am Seitenende. Ohne seite läuft sie über alle Seiten, auf denen der Kopf wieder steht. Mit zeile_beginnt (spalte, regex) beginnt eine neue Zeile nur, wenn diese Spalte passt; andere Zeilen werden an die vorige angehängt (mehrzeilige Einträge). Spalten können pflicht, muster und pruefung haben. pruefung.min/max am Tabellenfeld begrenzen die Zeilenzahl.
MRZ: sucht 2 × 44 (TD3, Reisepass), 3 × 30 (TD1, Personalausweis) oder 2 × 36 (TD2) Zeichen aus A–Z, 0–9 und <. Bei Scans liest die Analyse MRZ-Zeilen ein zweites Mal nur mit diesen Zeichen. Prüfziffern nach ICAO 9303 (Gewichte 7-3-1); an rein numerischen Stellen werden typische OCR-Verwechslungen korrigiert (O→0, I→1, B→8 …). Der Wert ist ein Objekt: format, dokumentart, ausstellerstaat, nachname, vornamen, dokumentnummer, staatsangehoerigkeit, geburtsdatum, geschlecht, ablaufdatum, optional, pruefziffern. Eine falsche Prüfziffer macht das Feld ungueltig.
Typen und Normalisierung
| --- | --- | --- |
|---|---|---|
text | alles | Text, Leerraum zusammengefasst, Zeilen mit \n |
datum | 01.02.2026, 1.2.26, 01. Februar 2026, 2026-02-01, 01022026 | "2026-02-01"; zweistellige Jahre bis heute + 20 → 20xx, sonst 19xx |
geld | 1.234,56 €, EUR 1.234,56, 12,00-, CHF 1'250.50 | { "betrag": 1234.56, "waehrung": "EUR" } (ohne Angabe EUR) |
zahl | 1.234,56, 1 234,5, 1,234.56, -3,5 | Zahl; 1.234 ohne Komma = 1234 |
iban | DE89 3704 0044 0532 0130 00 | "DE89370400440532013000", Länge je Land, Prüfsumme Mod 97 |
ja_nein | ja/nein, x, ☒, ☐, zutreffend, nicht zutreffend | true/false |
mrz | Objekt (siehe oben) |
Prüfung
| Schlüssel | Bedeutung |
|---|---|
| --- | --- |
min, max | Zahl, Betrag oder Datum ("2020-01-01") |
min_laenge, max_laenge | Textlänge |
pruefziffer | iban, luhn, mod11 |
Regeln
art | Schlüssel | Bedeutung |
|---|---|---|
| --- | --- | --- |
summe | tabelle + spalte oder felder, gesamt, toleranz (Vorgabe 0,01) | Summe = Gesamtfeld |
reihenfolge | felder, streng | aufsteigend (z. B. Beginn ≤ Ablauf) |
pflicht_wenn | feld, wenn: { feld, wert } | Pflicht, wenn das andere Feld (diesen Wert) hat |
Eine verletzte Regel macht den Lauf partial, schreibt den Grund ins Protokoll und senkt die Sicherheit der beteiligten Felder.
Sicherheit und Status
Je Feld: Sicherheit = mittlere OCR-Sicherheit der Wörter (Textebene 1,0). Normalisierung fehlgeschlagen: × 0,3. Prüfung fehlgeschlagen: × 0,5. Prüfziffer bestanden (IBAN, MRZ, Luhn, Mod 11): mindestens 0,98. Verletzte Regel: × 0,7.
Feldstatus: ok, fehlt, ungueltig (Normalisierung oder Prüfung), unsicher (unter min_konfidenz).
Laufstatus:
ok– alle Felder ok, alle Regeln erfülltpartial– mindestens ein Pflichtfeld fehlt, ein Feld ist ungültig oder unsicher, oder eine Regel ist verletztfailed– kein Text im Dokument, kein Feld gefunden oder Analyse fehlgeschlagenno_template– beivorlage=autopasst keine Vorlage
Die Gründe stehen in gruende (code, text, feld).
Beispiel 1: Police mit Ankerfeldern
{
"schema": 1,
"erkennung": {
"muss": [{ "regex": "Versicherungsschein|Police" }],
"darf_nicht": [{ "text": "Nachtrag" }, { "text": "Beitragsrechnung" }],
"absender": [{ "text": "Musterversicherung AG" }],
"soll": [{ "text": "Versicherungsnehmer" }, { "regex": "Versicherungs-?nummer" }],
"seiten": { "max": 12 }
},
"felder": [
{ "name": "versicherungsnummer", "typ": "text", "pflicht": true,
"quelle": { "art": "anker", "anker": { "regex": "Versicherungs-?(nummer|schein-Nr\\.?)" }, "richtung": "rechts" },
"muster": "([A-Z0-9][A-Z0-9./-]{5,})",
"pruefung": { "min_laenge": 6 } },
{ "name": "versicherungsbeginn", "typ": "datum", "pflicht": true,
"quelle": { "art": "regex", "regex": "Beginn[^\\d]{0,20}(\\d{1,2}\\.\\d{1,2}\\.\\d{2,4})" } },
{ "name": "ablauf", "typ": "datum",
"quelle": { "art": "regex", "regex": "Ablauf[^\\d]{0,20}(\\d{1,2}\\.\\d{1,2}\\.\\d{2,4})" } },
{ "name": "jahresbeitrag", "typ": "geld", "pflicht": true,
"quelle": { "art": "anker", "anker": { "text": "Jahresbeitrag", "unscharf": 1 }, "richtung": "rechts" } },
{ "name": "versicherungsnehmer", "typ": "text",
"quelle": { "art": "anker", "anker": { "text": "Versicherungsnehmer" }, "richtung": "unten", "zeilen": 3 } },
{ "name": "iban", "typ": "iban",
"quelle": { "art": "anker", "anker": { "text": "IBAN" }, "richtung": "rechts", "luecke": 0.03 } },
{ "name": "zahlweise", "typ": "auswahl",
"quelle": { "art": "anker", "anker": { "text": "Zahlweise" } },
"werte": [ { "wert": "JAEHRLICH", "texte": ["jährlich"] }, { "wert": "MONATLICH", "texte": ["monatlich"] },
{ "wert": "VIERTELJAEHRLICH", "texte": ["vierteljährlich"] }, { "wert": "HALBJAEHRLICH", "texte": ["halbjährlich"] } ] }
],
"regeln": [ { "art": "reihenfolge", "felder": ["versicherungsbeginn", "ablauf"] } ]
}
Beispiel 2: Tabelle der versicherten Personen
Kopfzeile Name | Geburtsdatum | Tarif | Beitrag, darunter je Person eine Zeile, manche Namen über zwei Zeilen, darunter Gesamtbeitrag.
{
"felder": [
{ "name": "versicherte_personen", "typ": "tabelle", "pflicht": true,
"quelle": {
"art": "tabelle",
"spalten": [
{ "name": "name", "kopf": "Name", "pflicht": true },
{ "name": "geburtsdatum", "kopf": "Geburtsdatum", "typ": "datum" },
{ "name": "tarif", "kopf": "Tarif" },
{ "name": "beitrag", "kopf": "Beitrag", "typ": "geld" }
],
"ende": [ { "regex": "Gesamt(beitrag)?|Summe" } ],
"zeile_beginnt": { "spalte": "geburtsdatum", "regex": "\\d{1,2}\\.\\d{1,2}\\.\\d{2,4}" }
},
"pruefung": { "min": 1 } },
{ "name": "gesamtbeitrag", "typ": "geld",
"quelle": { "art": "anker", "anker": { "regex": "Gesamt(beitrag)?|Summe" }, "richtung": "rechts" } }
],
"regeln": [ { "art": "summe", "tabelle": "versicherte_personen", "spalte": "beitrag", "gesamt": "gesamtbeitrag", "toleranz": 0.01 } ]
}
Ergebnis (daten):
{
"versicherte_personen": [
{ "name": "Erika Mustermann", "geburtsdatum": "1964-08-12", "tarif": "Komfort", "beitrag": { "betrag": 734.56, "waehrung": "EUR" } },
{ "name": "Max Mustermann (Mitversicherter)", "geburtsdatum": "1990-04-03", "tarif": "Basis", "beitrag": { "betrag": 500, "waehrung": "EUR" } }
],
"gesamtbeitrag": { "betrag": 1234.56, "waehrung": "EUR" }
}
Beispiel 3: Personalausweis (Rückseite) und Reisepass per MRZ
{
"erkennung": { "muss": [ { "regex": "^(ID|I<|P<)" } ], "schwelle": 0.5 },
"felder": [
{ "name": "mrz", "typ": "mrz", "pflicht": true, "quelle": { "art": "mrz", "format": "auto" } },
{ "name": "nachname", "typ": "text", "quelle": { "art": "ableitung", "feld": "mrz", "pfad": "nachname" } },
{ "name": "vornamen", "typ": "text", "quelle": { "art": "ableitung", "feld": "mrz", "pfad": "vornamen" } },
{ "name": "geburtsdatum", "typ": "datum", "quelle": { "art": "ableitung", "feld": "mrz", "pfad": "geburtsdatum" } },
{ "name": "ausweisnummer", "typ": "text", "quelle": { "art": "ableitung", "feld": "mrz", "pfad": "dokumentnummer" } },
{ "name": "gueltig_bis", "typ": "datum", "quelle": { "art": "ableitung", "feld": "mrz", "pfad": "ablaufdatum" },
"pruefung": { "min": "2020-01-01" } }
]
}
Für den deutschen Personalausweis (TD1) ist ausstellerstaat und staatsangehoerigkeit D. Fehlt die Rückseite, ist mrz fehlt und der Lauf partial.
Tipps
- Anker möglichst eindeutig wählen und
darf_nichtnutzen, um ähnliche Formate (Police, Nachtrag, Beitragsrechnung) zu trennen. - Für Scans
unscharf: 1an Ankern undmin_konfidenzan kritischen Feldern setzen. - Im Editor Beispiele hochladen, Wörter anklicken (Anker) oder Bereiche ziehen; „Alle Beispiele testen“ prüft den Entwurf gegen alle Beispiele und die Erkennung gegen Läufe ohne Vorlage. Erwartete Werte je Beispiel (API
PATCH /api/v1/vorlagen/{id}/beispiele/{bid}) zeigen Abweichungen im Test. - Nach dem Speichern einer neuen Version:
POST /api/v1/laeufe/wiederholenmit Filter{ "vorlage_id": …, "status": "partial" }liest betroffene Läufe neu aus.