Agile ExtractDokumentation

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

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üsselBedeutung
------
textwörtlicher Text, Leerraum flexibel, Groß/Klein egal
regexregulärer Ausdruck
gross_kleintrue: Groß/Klein beachten
unscharferlaubte Tippfehler (0–3, Levenshtein über die Wörter), für Scans
seitenur auf dieser Seite (1, 2 …, -1)
bereichnur in diesem Bereich { "x", "y", "b", "h" }
gewichtGewicht für die Punktzahl (Vorgabe 1)

Erkennung

SchlüsselBedeutung
------
mussalle müssen vorkommen, sonst 0 Punkte
darf_nichtkeiner darf vorkommen, sonst 0 Punkte (trennt z. B. Police von Nachtrag)
absenderAussteller: mindestens einer muss passen (falls angegeben)
sollerhöht die Punkte, ist aber nicht Pflicht
seitenSeitenzahl min/max
schwelleMindestpunkte 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üsselBedeutung
------
nameKleinbuchstaben, Ziffern, _; beginnt mit Buchstaben
typtext, datum, geld, zahl, iban, auswahl, ja_nein, tabelle, mrz
pflichtfehlt das Feld, ist der Lauf partial
quellewoher der Wert kommt (siehe unten)
musterAusdruck auf den gefundenen Rohtext; Gruppe 1 (falls vorhanden) ist der Wert
pruefungPrüfung des normalisierten Werts (siehe unten)
wertenur auswahl: [{ "wert": "KOMFORT", "texte": ["Komfort", "Tarif K"] }]
ja, nein, leer_ist_neinnur ja_nein: eigene Wörter; leer = nein
min_konfidenzdarunter gilt das Feld als unsicher (Vorgabe aus der Vorlage bzw. 0,6)
weitere_quellenErsatzquellen (anker, bereich, regex, fest), der Reihe nach versucht, bis eine einen gültigen Wert liefert

Quellen

artSchlüsselWert
---------
ankeranker (Treffer), richtung (rechts, unten, links, oben), abstand, breite, hoehe, luecke, zeilen, vorkommen, bereichText neben einem Ankertext
bereichseite, bereichalles in einem festen Bereich
regexregex, gruppe, seite, bereich, vorkommenTreffer im Volltext oder in einem Bereich
tabellespalten, ende, zeile_beginnt, seite, kopf_min, max_luecke, max_zeilenZeilen einer Tabelle
mrzformat (auto, TD1, TD2, TD3)maschinenlesbare Zone von Ausweis/Pass
ableitungfeld, pfadTeil 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

---------
textallesText, Leerraum zusammengefasst, Zeilen mit \n
datum01.02.2026, 1.2.26, 01. Februar 2026, 2026-02-01, 01022026"2026-02-01"; zweistellige Jahre bis heute + 20 → 20xx, sonst 19xx
geld1.234,56 €, EUR 1.234,56, 12,00-, CHF 1'250.50{ "betrag": 1234.56, "waehrung": "EUR" } (ohne Angabe EUR)
zahl1.234,56, 1 234,5, 1,234.56, -3,5Zahl; 1.234 ohne Komma = 1234
ibanDE89 3704 0044 0532 0130 00"DE89370400440532013000", Länge je Land, Prüfsumme Mod 97
ja_neinja/nein, x, ☒, ☐, zutreffend, nicht zutreffendtrue/false
mrzObjekt (siehe oben)

Prüfung

SchlüsselBedeutung
------
min, maxZahl, Betrag oder Datum ("2020-01-01")
min_laenge, max_laengeTextlänge
pruefzifferiban, luhn, mod11

Regeln

artSchlüsselBedeutung
---------
summetabelle + spalte oder felder, gesamt, toleranz (Vorgabe 0,01)Summe = Gesamtfeld
reihenfolgefelder, strengaufsteigend (z. B. Beginn ≤ Ablauf)
pflicht_wennfeld, 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:

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

Vorlagensprache · Agile Extract