Backend: Beleg-Analyse um Kaufdatum + Shop; Analyse-Endpoint
- guess_acquired_on (Kaufdatum) und guess_shop (bekannter Shop erkannt -> id; sonst Kandidatenname zum Anlegen) ergaenzt. - Upload liefert zusaetzlich suggested_acquired_on / shop_id / shop_name. - Neuer POST /items/analyze-document: analysiert einen Beleg OHNE zu speichern (zum Vorbefuellen beim Anlegen). Gemeinsamer Helfer _doc_suggestions. - 6 neue Tests; Suite 150 gruen. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -126,6 +126,57 @@ def guess_warranty_until(text: str, acquired_on: date | None = None) -> date | N
|
||||
return _find_date_near_keyword(text)
|
||||
|
||||
|
||||
def guess_acquired_on(text: str) -> date | None:
|
||||
"""Kaufdatum aus dem Beleg (Datum nahe Rechnungs-/Kaufdatum-Stichwort)."""
|
||||
return _find_purchase_date(text) if text else None
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Shop / Bezugsquelle aus dem Beleg
|
||||
# --------------------------------------------------------------------------
|
||||
_LEGAL_RE = re.compile(
|
||||
r"\b(GmbH|AG|SA|Sàrl|S\.?à r\.?l\.?|Ltd|Inc|SE|KG|OHG|e\.?K\.?|AS|BV|S\.p\.A\.)\b"
|
||||
)
|
||||
|
||||
|
||||
def _vendor_candidate(text: str) -> str | None:
|
||||
"""Bester Rate-Name des Händlers – meist im Kopf des Belegs. Best effort."""
|
||||
lines = [z.strip() for z in text.splitlines() if z.strip()]
|
||||
for z in lines[:20]:
|
||||
if _LEGAL_RE.search(z):
|
||||
return z[:60]
|
||||
for z in lines[:8]:
|
||||
buchstaben = sum(c.isalpha() for c in z)
|
||||
if 3 <= len(z) <= 40 and buchstaben >= 3 and not re.search(
|
||||
r"rechnung|invoice|quittung|beleg|kassenbon|datum|receipt|order|bestell",
|
||||
z, re.IGNORECASE,
|
||||
):
|
||||
return z[:60]
|
||||
return None
|
||||
|
||||
|
||||
def guess_shop(text: str, shops: list[tuple[int, str]]) -> tuple[int | None, str | None]:
|
||||
"""(shop_id, name).
|
||||
|
||||
Kommt der Name eines bereits hinterlegten Shops im Beleg vor, wird dieser
|
||||
vorgeschlagen (längster Treffer gewinnt). Sonst ein Kandidatenname zum
|
||||
Anlegen – oder (None, None), wenn nichts Brauchbares gefunden wird.
|
||||
"""
|
||||
if not text:
|
||||
return (None, None)
|
||||
low = text.lower()
|
||||
treffer: tuple[int, str] | None = None
|
||||
best_len = 0
|
||||
for sid, name in shops:
|
||||
n = name.strip().lower()
|
||||
if len(n) >= 3 and n in low and len(n) > best_len:
|
||||
treffer = (sid, name)
|
||||
best_len = len(n)
|
||||
if treffer is not None:
|
||||
return treffer
|
||||
return (None, _vendor_candidate(text))
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Kaufpreis aus dem Beleg schätzen
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user