Markenerkennung: Grenzen gemessen, Summenzeile im Kopf ist keine Marke
Auf die Frage, ob Belege einheitlich gestaltet sind: nein. Die Erkennung haengt an drei Annahmen - Marke in den ersten acht Zeilen, Marke in der festen Liste, Marke ueberhaupt vorhanden. Alle drei sind jetzt gemessen und in Tests festgehalten. Dabei ein echter Defekt gefunden: "Total" ist Kopfmarke UND Summenwort. Ein Beleg mit Betrag in den ersten acht Zeilen lieferte faelschlich die Marke "Total" (an einem echten Beleg reproduziert). _betragszeile() filtert Summenzeilen jetzt aus dem Kopf - verlangt werden beide Kennzeichen zugleich, Summenwort und Geldangabe, damit "SHELL TANKSTELLE, 6450 SOELDEN" nicht mitfaellt. Gegenprobe: ein echter "TOTAL TANKSTELLE"-Kopf ergibt weiter Total. Der Ausfall beschaedigt nichts: mit unbekannter Marke im Kopf bleiben Liter, Betrag, Rabatt und Anschrift unveraendert richtig, nur die Marke fehlt. Parser-Suite 17 Tests (von 11), alle zwoelf abgelegten Belege liefern weiter Shell, audi_ha_test auf 2026.9.4.22 ohne Traceback.
This commit is contained in:
@@ -165,5 +165,68 @@ class BelegOhneRabatt(unittest.TestCase):
|
||||
self.assertAlmostEqual(d["liters"], 42.15, delta=0.001)
|
||||
|
||||
|
||||
|
||||
class MarkenerkennungGrenzen(unittest.TestCase):
|
||||
"""Was die Markenerkennung leistet - und was ausdrücklich nicht.
|
||||
|
||||
Belege sind nicht genormt. Diese Suite hält fest, wo die Erkennung greift,
|
||||
wo sie aufgibt, und dass ein Aufgeben den Beleg nicht beschädigt.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.zeilen = p._text_aus_pdf(os.path.join(_BELEGE, "e-receipt.pdf")).splitlines()
|
||||
|
||||
def test_marke_steht_ganz_oben(self):
|
||||
# Gemessen am 04.09.2026: in allen elf Belegen in Zeile 0. Das Fenster
|
||||
# von acht Zeilen ist der Puffer, nicht die Erwartung - der
|
||||
# österreichische Beleg (nur in der Instanz) hat sie in Zeile 1.
|
||||
for datei in sorted(f for f in os.listdir(_BELEGE) if f.endswith(".pdf")):
|
||||
with self.subTest(datei=datei):
|
||||
zeilen = p._text_aus_pdf(os.path.join(_BELEGE, datei)).splitlines()
|
||||
self.assertEqual(p._marke(zeilen[:1]), "Shell")
|
||||
|
||||
def test_ausserhalb_des_fensters_wird_nicht_gefunden(self):
|
||||
# Ehrlich dokumentiert statt stillschweigend angenommen: steht die
|
||||
# Marke erst ab Zeile 8, findet sie niemand.
|
||||
verschoben = [""] * 8 + self.zeilen
|
||||
self.assertIsNone(p._marke(verschoben))
|
||||
|
||||
def test_unbekannte_marke_beschaedigt_den_beleg_nicht(self):
|
||||
# Freie Tankstellen und Marken, die nicht in _MARKEN stehen: der Beleg
|
||||
# wird vollständig eingelesen, nur eben ohne Marke. Ein falscher Name
|
||||
# wäre schlimmer als keiner.
|
||||
for ersatz in ("OIL! Tankstelle", "Hoyer Tankstelle", "Freie Tankstelle Mueller"):
|
||||
with self.subTest(kopf=ersatz):
|
||||
d = p._parsen("\n".join([ersatz] + self.zeilen[1:]))
|
||||
self.assertIsNone(d["station_brand"])
|
||||
# Ohne Marke tritt der Betreibername an ihre Stelle.
|
||||
self.assertEqual(d["station_name"], "A. Zrenner GmbH, Pascalstr. 8, Ingolstadt")
|
||||
self.assertAlmostEqual(d["liters"], 42.15, delta=0.001)
|
||||
self.assertAlmostEqual(d["fuel_total_eur"], 78.78, delta=0.001)
|
||||
|
||||
def test_summenzeile_im_kopf_ist_keine_marke(self):
|
||||
# "Total" ist beides - Kopfmarke und Summenwort. Ein Beleg mit knappem
|
||||
# Kopf, der den Betrag schon oben druckt, lieferte vorher fälschlich
|
||||
# die Marke "Total" (nachgestellt am 04.09.2026).
|
||||
for summe in ("TOTAL: 78,78 EUR", "GESAMT 78,78 EUR", "Summe 78,78 EUR"):
|
||||
with self.subTest(zeile=summe):
|
||||
self.assertEqual(p._marke([summe] + self.zeilen), "Shell")
|
||||
|
||||
def test_echte_marke_total_bleibt_erkennbar(self):
|
||||
# Die Gegenprobe zum Test darüber: verworfen wird nur, wo Summenwort
|
||||
# UND Geldangabe zusammenkommen - eine Kopfzeile ohne Betrag nicht.
|
||||
self.assertEqual(p._marke(["TOTAL TANKSTELLE", "Musterweg 1", "80331 München"]), "Total")
|
||||
# Und eine Postleitzahl in der Kopfzeile faellt nicht mit heraus.
|
||||
self.assertEqual(
|
||||
p._marke(["AUTO B. FRISCHMANN GMBH", "SHELL TANKSTELLE, 6450 SÖLDEN"]), "Shell"
|
||||
)
|
||||
|
||||
def test_wortgrenze_schuetzt_vor_teiltreffern(self):
|
||||
# "Star" darf nicht in "Starnberg" treffen, "Eni" nicht in "Enisstr.".
|
||||
self.assertIsNone(p._marke(["Tankstelle Mueller", "Seestr. 3", "82319 Starnberg"]))
|
||||
self.assertIsNone(p._marke(["Freie Tankstelle", "Enisweg 4", "12345 Musterort"]))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(verbosity=2)
|
||||
|
||||
Reference in New Issue
Block a user