Backend: Kassenzettel-Abgleich (POST /products/match) + Schwellwert-Einstellung

Neuer Endpunkt ordnet OCR-Zeilen den aehnlichsten LEBENSMITTELN zu (Score 0-100,
difflib + Token-/Praefix-Abgleich fuer abgekuerzte Kassennamen). Gegenstaende
inkl. Verbrauchsgegenstaende bleiben aussen vor. Je Zeile die besten Treffer ueber
dem Schwellwert (Request oder Einstellung receipt_match_threshold, Default 45).
3 Tests, gesamt 182 gruen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-07-30 18:24:19 +02:00
parent d05727779b
commit 91adee5333
4 changed files with 173 additions and 0 deletions

View File

@@ -0,0 +1,60 @@
"""Kassenzettel-Abgleich: OCR-Zeilen den ähnlichsten Lebensmitteln zuordnen."""
import pytest
from app.models import BaseUnit, Product, Role, User
from app.routers.products import match_receipt
from app.schemas import MatchRequest
@pytest.fixture()
def user(db):
person = User(username="tester", password_hash="x", role=Role.admin)
db.add(person)
db.commit()
db.refresh(person)
return person
def test_abgekuerzte_kassenzeile_trifft_lebensmittel(db, user):
p = Product(
name="Vegane Mühlen-Schnitzel auf Basis von Soja",
base_unit=BaseUnit.gram, package_size=180,
)
db.add(p)
db.commit()
db.refresh(p)
res = match_receipt(
MatchRequest(lines=["MÜHLEN SCHNITZ", "voelliger unsinn xyz"]),
db=db, _=user,
)
# Abgekürzte Kassenzeile findet den vollen Namen mit hohem Score.
assert res[0].text == "MÜHLEN SCHNITZ"
assert res[0].candidates
assert res[0].candidates[0].product_id == p.id
assert res[0].candidates[0].score >= 70
# Unsinnszeile bleibt ohne Treffer über dem Schwellwert.
assert res[1].candidates == []
def test_nur_lebensmittel_keine_gegenstaende(db, user):
# Einzelstück-Flag -> tracking "object" -> darf nicht vorgeschlagen werden.
obj = Product(name="Powerbank Anker", base_unit=BaseUnit.piece, individual=True)
db.add(obj)
db.commit()
res = match_receipt(MatchRequest(lines=["POWERBANK ANKER"], threshold=10), db=db, _=user)
assert res[0].candidates == []
def test_schwellwert_filtert(db, user):
p = Product(name="Basmati Reis", base_unit=BaseUnit.gram, package_size=1)
db.add(p)
db.commit()
# Nur teilweise passende Zeile: bei sehr hohem Schwellwert kein Treffer.
hart = match_receipt(MatchRequest(lines=["reis lose"], threshold=99), db=db, _=user)
weich = match_receipt(MatchRequest(lines=["reis lose"], threshold=20), db=db, _=user)
assert hart[0].candidates == []
assert weich[0].candidates and weich[0].candidates[0].product_id == p.id