Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Need help or bug] Fields skipped in a state loop? #48

Open
noyannus opened this issue Jul 26, 2023 · 7 comments
Open

[Need help or bug] Fields skipped in a state loop? #48

noyannus opened this issue Jul 26, 2023 · 7 comments

Comments

@noyannus
Copy link

noyannus commented Jul 26, 2023

I'm using textricator form to extract text fields that in a short stretch of the input PDFs occur in varying orders (field1-field2-field3 or field1-field3-field2 or field3-field1-field 2; ...).

A state with multiple conditions is iterated through to go through all states of the variedly-ordered fields, eight such fields plus an exit and a loop condition.
When none of the conditions but the next field after these fields match, iteration stops and the machine moves to that state.

At least, that's what it's supposed to do. It does not fully work. In different PDFs it skips fields, but different fields in different PDFs. Why?

  • All condition regexs were checked in LibreOffice search to find the fields in the corresponding textricator text CSV files.
  • Literal texts were copied over from the CSV files.
  • All condition coordinates were double checked to match in all PDF variations. Before, I already had filtered the CSV for each field and collected all extant coordinates (usually ulx ) to formulate a match.
  • YamlLint tells me that the YAML is well formed.

Am I overlooking something basic? Could there be a bug that causes skips in a lenghty 'condition:... nextState:...` sequence?

states:
    state-before-loop:
        include: false
        transitions:
            -
                condition: true                     # ( 1 = 1 )
                nextState: search-loop
    search-loop:
        include: false
        transitions:
            -
                condition: field-1
                nextState: field-1
            -
                condition: field-2
                nextState: field-2
            -
                condition: field-3
                nextState: field-3

            ...

            -
                condition: next-state-after-loop    # now we have left the varied-order fields 
                nextState: next-state-after-loop    # and exit the loop
            -
                condition: search-loop              # and if not
                nextState: search-loop			    # search again
    field-1:
        transitions:
            -
                condition: true
                nextState: search-loop
    field-2:
        transitions:
            -
                condition: true
                nextState: search-loop
    field-3
            -
                condition: true
                nextState: search-loop
    ...

    next-state-after-loop
            -
                condition: proceed
                nextState: proceed
    ...
@stephen-byrne
Copy link
Contributor

Can you provide an example PDF and yaml that shows this behavior?

@noyannus
Copy link
Author

Same as #47 -- sensitive data. If a way to change the field content without altering the PDF otherwise exists, please let me know.

@stephen-byrne
Copy link
Contributor

Same as #47 -- sensitive data. If a way to change the field content without altering the PDF otherwise exists, please let me know.

You could use textricator text, redact the values in the CSV or JSON, and provide that file.

@noyannus
Copy link
Author

noyannus commented Nov 3, 2023

I'll try to get it done this or next weekend.

@noyannus
Copy link
Author

noyannus commented Nov 5, 2023

So here's the yaml. It's the last version before I gave up in January with textricator-10.0**.67**.

Some replacements have a debugging label prepended, e.g., "MO-$1" for the month output.

---
# Konfiguration für Textricator
# Zum Extrahieren der Buchungen aus Kontoauszügen und Kreditkarten-Abrechnungen der DKB

# ================================================================================
# Textricator den Extraktor vorgeben
# "pdf.itext7" und "pdf.pdfbox" zerhacken bei manchen Kontoauszügen/Kreditkarten-Abrechnungen
# den Buchungstext (z.B. "Angabe des Unternehmens / Verwendungszweck",
# "Wir haben für Sie gebucht", mglw. noch andere Felder)
extractor: "pdf.itext5"

# ================================================================================
# Definition des durchsuchten Seitenbereichs
# Maßeinheit: 1 Punkt = 1/72 Zoll
# Koordinaten: (0/0) ist links oben

# ignoriere Header bis vor ...
header:
    default: 145        #  171.44
    # Beginn Buchungsliste ab zweiter Seite (uly)
    # könnte man für Seiten 1 und 2+ differenzieren (Seite)

# ignoriere Footer nach ...
footer:
    default: 777.5      # Beginn erste Footerzeile (uly)

# y-Toleranz: Abstand zwischen zwei Textsegmenten die zur selben Zeile gehören
maxRowDistance: 2

# ================================================================================
# Definition der Ausgabe-Datenstruktur
# Je Kontoauszug/Kreditkarten-Abrechnung ein neuer rootRecord
# Je Buchung ein Child-Record
rootRecordType: kontaus
recordTypes:
# Kontoauszug/Abrechnung
    kontaus:
        label: "Kontoauszug" # Notwendig, auch wenn nicht referenziert
        valueTypes:     # Datenfelder
            # Girokonto
            - dispo     # nur bei Girokonto
            - girart    # DKB-Cash, Girokonto
            - girausz   # Kontoauszugsnummer, Zeitraum (enthält von und bis)
            - girkont   # Kontonummer
            - iban      # IBAN
            # Girokonto und Karte
            - von       # Auszugsbeginn
            - bis       # Auszugsende
            # Karte
            - abrmon    # nur bei Karte
            - abrdat    # Abrechnungsdatum
            - kartyp    # Kartenbezeichnung ("Visa Card")
            - karnum    # Kartennummer
            - servid    # zeitweilig existierte eine Service-ID
        #    - inhlbl    # Anker für Feld Inhaber
            - inhab     # Karteninhaber - nicht bei Giro
        #     - abrgzt    # Abrechnungszeitraum (enthält von und bis)
        children:
            - buchg     # Buchung
# Buchung
    buchg:
        label: "Buchung"
        valueTypes:
            - budat      # Datum Buchung/Beleg
            - wrtdat     # Datum Wertstellung/Eingang
            # Girokonto
            - kateg      # Transaktionskategorie # nicht bei Karte
            - belast     # Belastung - nicht bei Karte
            - gutsch     # Gutschrift - nicht bei Karte
            # Karte
            - währg      # Fremdwährung - nicht bei Giro
            - wägbtg     # Fremdwärungsbetrag - nicht bei Giro
            - kurs       # Wechselkurs - nicht bei Giro
            - euro       # Eurobetrag - bei Karte, Absolutwert
            - vorzn      # Vorzeichen - bei Karte
            # Girokonto und Karte
            - butxt     # Buchungstext
# ================================================================================
# Spaltenüberschriften anlegen
valueTypes:
# Girokonto
    dispo:
        label: "Dispo"
        replacements:
            -
                pattern: '^IHR DISPOKREDIT EUR ([0-9.,]+)$'
                replacement: "$1"
    girart:
        label: "Kontoart"
        replacements:
            -
                pattern: '(.+)'
                replacement: "$1"
    girausz:
        label: "Kontoauszug"
        replacements:
            -
                pattern: '^Kontoauszug Nummer (\d+ \/ \d+) vom [0-9.]{10} bis [0-9.]{10}$'
                replacement: "$1"
    girkont:
        label: "Kontonummer"
        replacements:
            -
                pattern: '^Kontonummer (\d{10}) \/ IBAN [A-Z]{2}\d\d (\d{4} ){4}\d\d$'
                replacement: "$1"
    iban:
        label: "IBAN"
        replacements:
            -
                pattern: '^Kontonummer \d{10} \/ IBAN ([A-Z]{2}\d\d (\d{4} ){4}\d\d$)'
                replacement: "$1"
# Karte
    abrmon:
        label: "Monat"
        replacements:   # Konvertieren in Kurzform (Jan) oder numerischen Monat (01)?
            -
                pattern: '(.+)'
                replacement: "MO-$1"
    abrdat:
        label: "Abr.-Datum"
        replacements:
            -
                pattern: '(\d\d\. (Januar|Februar|März|April|Mai|Juni|Juli|August|September|Oktober|November|Dezember) \d{4})'
                replacement: "DAT-$1"
    kartyp:
        label: "Kartentyp"
        replacements:
            -
                pattern: '(.*)'
                # '((DKB)?[ -]?VISA[ -]?(Card|CARD))(-Nummer)?:?$'
                replacement: "TYP-$1"
    karnum:
        label: "Kartennummer"
        replacements:
            -
                pattern: '(.+)'
                replacement: "NUM-$1"
    servid:
        label: "Service-ID"
        replacements:
            -
                pattern: '(.+)'
                replacement: "SID-$1"
    # inhlbl:
    #     label: "INH"
    #     replacements:
    #         -
    #             pattern: '(.+)'
    #             replacement: "ILB-$1"
    inhab:
        label: "Karteninhaber"
        replacements:
            -
                pattern: '(.+)'
                replacement: "INH-$1"
    abrgzt:
        label: "abrgzt"
        replacements:
            -
                pattern: '(.+)'
                replacement: "AZM-$1"
    # Girokonto und Karte
    von:
        label: "Von"
        replacements:
            -
                pattern: '^((Kontoauszug Nummer \d+ \/ \d{4})|(Ihre Abrechnung)) vom ((\d\d.){2}\d{4}) bis ((\d\d.){2}\d{4})$'
                replacement: "VON-$4"
    bis:
        label: "Bis"
        replacements:
            -
                pattern: '^((Kontoauszug Nummer \d+ \/ \d{4})|(Ihre Abrechnung)) vom ((\d\d.){2}\d{4}) bis ((\d\d.){2}\d{4})$'
                replacement: "BIS-$6"
    # Buchung
    budat:
        label: "Buchung/Beleg"
        replacements:
            -
                pattern: '(.+)'
                replacement: "BUD-$1"
    wrtdat:
        label: "Wertstellung"
        replacements:
            -
                pattern: '(.+)'
                replacement: "$1"
    # Girokonto
    kateg:
        label: "Kategorie"
        replacements:
            -
                pattern: '(.+)'
                replacement: "$1"
    belast:
        label: "Belastung"
        replacements:
            -
                pattern: '(.+)'
                replacement: "$1"
    gutsch:
        label: "Gutschrift"
        replacements:
            -
                pattern: '(.+)'
                replacement: "$1"
    # Karte
    währg:
        label: "Fremdwährung"
        separator: "; "            # Separator notwendig? Zeilen zusammenfassen?
        replacements:
            -
                pattern: '(.+)'
                replacement: "$1"
    wägbtg:
        label: "Fremdw.-Betrag"
        separator: "; "
        replacements:
            -
                pattern: '(.+)'
                replacement: "$1"
    kurs:
        label: "Kurs"
        separator: "; "
        replacements:
            -
                pattern: '(.+)'
                replacement: "$1"
    euro:
        label: "Euro"
        separator: "; "
        replacements:
            -
                pattern: '(.+)'
                replacement: "$1"
    vorzn:
        label: "Vorzeichen"
        separator: "; "
        replacements:
            -
                pattern: '(.+)'
                replacement: "$1"
    # Girokonto und Karte
    butxt:
        label: "Buchungstext"
        separator: "; "
        replacements:
            -
                pattern: '(.+)'
                replacement: "$1"

# ================================================================================
# Definition der Zustände und Übergänge der State Machine
initialState: "INIT"
states:
    # Marker für Karte oder Girokonto
    INIT:
        startRecord: true               # neuen Datensatz anlegen
        include: false                  # keine Ausgabe
        transitions:
            -
                condition: dispo        # "IHR DISPOKREDIT...." => Girokonto
                nextState: dispo
            -
                condition: abrmon       # "Abrechnung: => Karte
                nextState: abrmon
            -
                condition: wahr         # beides nicht: => weitersuchen
                nextState: INIT
    # Girokonto
    dispo:
        transitions:
            -
                condition: wahr
                nextState: girsuch
    girsuch:
        include: false
        transitions:
            -
                condition: girart       # Kontenart ("DKB-Cash", etc.), kann fehlen
                nextState: girart
            -
                condition: girausz      # Kontoauszug (Nr, Zeitraum)
                nextState: girausz
            -
                condition: wahr         # nicht vorhanden => weitersuchen
                nextState: girsuch
    girart:
        transitions:
            -
                condition: girausz
                nextState: girausz
    girausz:
        valueTypes:
            - girausz
            - von
            - bis
        transitions:
            -
                condition: girkont
                nextState: girkont
    girkont:
        valueTypes:
            - girkont
            - iban
        transitions:
            -                           # => Buchung suchen
                condition: wahr
                nextState: budat
# Karte
    abrmon:
        transitions:
            -
                condition: wahr
                nextState: karsuch
    karsuch:
        include: false
        transitions:
            -
                condition: abrdat
                nextState: abrdat
            -
                condition: kartyp
                nextState: kartyp
            -
                condition: karnum
                nextState: karnum
            -
                condition: servid
                nextState: servid
            -
                condition: inhlbl
                nextState: inhlbl
            -
                condition: abrgzt
                nextState: abrgzt
            -
                condition: budat        # => Buchungen
                nextState: budat
            -
                condition: wahr         # weitersuchen
                nextState: karsuch
    abrdat:
        transitions:
            -
                condition: wahr
                nextState: karsuch
    kartyp:
        transitions:
            -
                condition: wahr
                nextState: karsuch
    karnum:
        transitions:
            -
                condition: wahr
                nextState: karsuch
    servid:
        transitions:
            -
                condition: wahr
                nextState: karsuch
    inhlbl:                             # Indirekt via Label zu Karteninhaber, da Inhaber selbt zu variabel ist
        include: false
        transitions:
            -
                condition: inhab
                nextState: inhab
    inhab:
        transitions:
            -
                condition: wahr
                nextState: karsuch
    abrgzt:
        valueTypes:
            - von
            - bis
        transitions:
            -
                condition: wahr
                nextState: karsuch
# Buchung
    budat:                              # Karte: Saldo ist keine Buchung; wird ignoriert wg. fehlendem Belegdatum, so dass kein Filter gesetzt werden muss
        startRecord: true               # neuen Datensatz anlegen
        transitions:
            -
                condition: wrtdat
                nextState: wrtdat
            -
                condition: wahr    # Selbst
                nextState: budat
    wrtdat:
        transitions:
            -
                condition: kateg        # => Transaktionskategorie (nur Girokonto)
                nextState: kateg
            -
                condition: butxt        # keine Kategorie => Buchungstext (Girokonto und Karte)
                nextState: butxt
    # Buchung Girokonto
    kateg:
        transitions:
            -
                condition: belast
                nextState: belast
            -
                condition: gutsch
                nextState: gutsch
    belast:
        transitions:
            -
                condition: budat      # kein weiterer Buchungstext => neue Buchung
                nextState: budat
            -
                condition: butxt        # weiterer Buchungstext => neue Zeile
                nextState: butxt
            -
                condition: seitend      # beides nicht => Seitenende
                nextState: seitend
    gutsch:
        transitions:
            -
                condition: budat      # keine weiterer Buchungstext => neue Buchung
                nextState: budat
            -
                condition: butxt        # weiterer Buchungstext => neue Zeile
                nextState: butxt
            -
                condition: seitend      # beides nicht => Seite
                nextState: seitend
# Buchung Karte
    währg:
        transitions:
            -
                condition: wägbtg
                nextState: wägbtg
    wägbtg:
        transitions:
            -
                condition: kurs
                nextState: kurs
    kurs:
        transitions:
            -
                condition: euro
                nextState: euro
    euro:
        transitions:
            -
                condition: vorzn
                nextState: vorzn
    vorzn:
        transitions:
            -
                condition: budat        # => nächste Buchung
                nextState: budat
            -
                condition: butxt        # weiterer Buchungstext => neue Zeile
                nextState: butxt
            -
                condition: wahr             # keine Buchung => Seitenende
                nextState: seitend
# Girokonto und Karte
    butxt:
        transitions:
            -
                condition: währg        # Zahlung in Fremdwährung
                nextState: währg
            -
                condition: euro         # Zahlung in Euro
                nextState: euro
            -
                condition: budat        # Datum => neue Buchung
                nextState: budat
            -
                condition: butxt        # weiteres Buchungstext-Feld => neue Zeile
                nextState: butxt
            -
                condition: wahr         # => Seitenende
                nextState: seitend
    seitend:
        include: false
        transitions:
            -
                condition: budat        # Buchung auf nächster Seite
                nextState: budat
            -
                condition: wahr         # beides nicht: Weitersuchen bis EOF
                nextState: seitend      # INIT ??? oder neue Seite = neuer Durchlauf? TODO
# ================================================================================
# Definition der Bedingungen
# lrx geht nicht, da textricator hier oft den Dezimaltrenner vergisst (z.B. "575968" pt = 203,19 m)
# Koordinaten als "x < wert < (x+1)", um Probleme mit unterschiedlichen Dezimaltrennern zu vermeiden  <= wirklich notwendig?
# Abhängig von Feldreihenfolge im PDF werden manche Felder nicht gefunden
conditions:
    wahr:       ( 0 = 0 )               # immer wahr (einfach 'true' geht nicht)
# Girokonto
    dispo:      ( lrx > 400 )
                and ( ulx > 200 )
                and ( text =~ /^IHR DISPOKREDIT EUR [0-9,.]*$/ )    # Marker für Girokonto
    girart:     ( ( ulx = 50 ) or ( ulx = 55 ) )
                and ( text =~ /^(DKB-Cash|Girokonto|DKB-Cash u18|Girokonto u18|DKB-Pfändungsschutzkonto|Basiskonto|Vermieterpaket)$/ )
    #  - - - - - - - - - - - - - - - ^^^ ...hoffentlich haben die alle dasselbe Auszugsformat... ^^^
    girausz:    ( ( ulx = 30 ) or ( ulx = 50 ) )
                and ( text =~ /^Kontoauszug Nummer \d+ \/ \d+ vom (\d\d\.){2}\d{4} bis (\d\d\.){2}\d{4}$/ )
    girkont:    ( ( ulx = 30 ) or ( ulx = 50 ) )
                and ( text =~ /^Kontonummer \d{10} \/ IBAN [A-Z]{2}\d\d (\d{4} ){4}\d\d$/ )
    iban:       ( ( ulx = 30 ) or ( ulx = 50 ) )
                and ( text =~ /^Kontonummer \d{10} \/ IBAN [A-Z]{2}\d\d (\d{4} ){4}\d\d$/ )
#   Karte
    abrmon:     ( ulx > 300 )
                and ( text =~ /^(Januar|Februar|März|April|Mai|Juni|Juli|August|September|Oktober|November|Dezember) \d{4}$/ )  # Marker für Karte
    abrdat:     ( ulx > 300 ) and ( text =~ /^\d\d\. (Januar|Februar|März|April|Mai|Juni|Juli|August|September|Oktober|November|Dezember) \d{4}$/ )
    kartyp:     ( 350 < ulx < 360 )
                and ( 340 < uly < 350 )
                #  and ( text = "DKB-VISA-Card:" )
                # ( text =~  /^.+(DKB[ -]?)?VISA[ -]?C(ARD|ard)(-Nummer)?:?$/ )
    karnum:     ( ulx > 300 )
                and
                ( text =~ /^\d{4} \d\d[0-9X]{2} [0-9X]{4} \d{4}$/ )
    servid:     ( ulx > 450 )
                and ( text =~ /^\d+$/ )
    inhlbl:     ( 350 < ulx < 360 )
                and ( text = "Karteninhaber:" )
    inhab:      ( ulx_rel > 20 ) and ( uly_rel = 0 )
    abrgzt:     ( text =~ /^Ihre Abrechnung vom (\d\d\.){2}\d{2,4} bis (\d\d\.){2}\d{2,4}$/ )
# Buchung
    budat:      ( 40 < ulx < 60 )
                and ( text =~ /^(\d\d\.){2}\d{2,4}$/ )
    wrtdat:     ( 70 < ulx < 90 )
                and ( text =~ /^(\d\d\.){2}\d{2,4}$/ )
    # B. Girokonto
    kateg:      ( -2 <= uly_rel <= 2 )
                and ( 40 < ulx_rel < 50)
    belast:     ( -2 <= uly_rel <= 2 )
                and ( 420 < ulx < 480 )
                and ( text =~ /^[0-9.]+,\d\d$/ )
    gutsch:     ( -2 <= uly_rel <= 2 )
                and ( 515 < ulx < 580 )
                and ( text =~ /^[0-9.]+,\d\d$/ )
    # B. Karte
    währg:      ( -2 <= uly_rel <= 2 )
                and ( text =~ /^[A-Z]+$/ )
    wägbtg:     ( -2 <= uly_rel <= 2 )
                and ( text =~ /^[0-9.]+,\d+$/ )
    kurs:       ( -2 <= uly_rel <= 2 )
                and ( text =~ /^[0-9.]+,\d+$/ )
    euro:       ( -2 <= uly_rel <= 2 )
                and ( text =~ /^[0-9.]+,\d\d?$/ )
    vorzn:      ( -2 <= uly_rel <= 2 )
                and ( text =~ /^[+-]$/ )
    # B. Girokonto und Karte
    von:        ( 35 < ulx < 55 )
                and ( text =~ /^(Kontoauszug Nummer \d+ \/ \d+|Ihre Abrechnung) vom (\d\d\.){2}\d{4} bis (\d\d\.){2}\d{4}$/ )
                # 39.6 .. 52
    bis:        ( 35 < ulx < 55 )
                and ( text =~ /^(Kontoauszug Nummer \d+ \/ \d+|Ihre Abrechnung) vom (\d\d\.){2}\d{4} bis (\d\d\.){2}\d{4}$/ )
    butxt:      ( 120 < ulx < 200 )
                and (
                    ( -2 <= uly_rel <= 2 )
                    or ( 9 < uly_rel < 13 )
                    )
#             and ( text =~ /^(?!(ALTER KONTOSTAND|Neuer Saldo).*)$/ )
                    #     ) # +1 Zeile nach belast/gutsch (giro)
#            # gleiche zeile nach wrtdat (Karte)
#     seitend:  text =~ /^Seite \d+.*$/ # TODO

#   karsuch: ( text = "" )


@wstumbo-mfj
Copy link
Contributor

Noyannus - I assume this is still a problem? I'm trying to figure out how to replicate it. I wonder if this may also be a localization issue like #47 - but have no evidence one way or the other.

Can you point me to where in your yaml file the problem occurs and the matching area in all-p1-redact.pdf? I'm making the assumption that is the general structure of the document you're parsing.

Maybe with those two pieces of information, I can start to build a model of what's going on and zero in on something specific.

@noyannus
Copy link
Author

noyannus commented Dec 12, 2023

Sorry for the delayed answer (life has a way to get in the way). And thank you for your kind offer.

I will try a simplified version first making use of the y-values I have shunned so far (#47). That made the conditions overly complicated; and I want reduce the likelihood that I have introduced a bug there. If this does not work, I'll be back here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants