Preis-Vorschlag: Auswahl aus mehreren erkannten Beträgen
Findet die Beleg-Analyse mehrere Beträge, liefert der Upload jetzt eine Kandidatenliste (bester Tipp zuerst). Web und iOS bieten dann ein Dropdown zur Auswahl des richtigen Kaufpreises, statt nur den automatischen Tipp. 1 neuer Test; Backend-Suite gruen. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -33,6 +33,7 @@ from ..schemas import (
|
||||
from ..services.items import generate_uid, item_to_out
|
||||
from ..services.warranty import (
|
||||
extract_pdf_text,
|
||||
guess_price_candidates,
|
||||
guess_price_cents,
|
||||
guess_warranty_until,
|
||||
)
|
||||
@@ -246,10 +247,12 @@ async def upload_item_document(
|
||||
|
||||
# Garantieende und Preis nur aus PDFs schätzen (Bilder haben keine Textebene).
|
||||
warranty = price = None
|
||||
candidates: list[int] = []
|
||||
if content_type == "application/pdf":
|
||||
text = extract_pdf_text(data)
|
||||
warranty = guess_warranty_until(text, acquired_on=item.acquired_on)
|
||||
price = guess_price_cents(text)
|
||||
candidates = guess_price_candidates(text)
|
||||
|
||||
return ItemDocumentUploadOut(
|
||||
id=doc.id,
|
||||
@@ -258,6 +261,7 @@ async def upload_item_document(
|
||||
uploaded_at=doc.uploaded_at,
|
||||
suggested_warranty_until=warranty,
|
||||
suggested_price_cents=price,
|
||||
suggested_price_candidates=candidates,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -544,6 +544,8 @@ class ItemDocumentUploadOut(ItemDocumentOut):
|
||||
"""Antwort nach dem Upload – mit aus dem PDF geschätztem Garantieende und Preis."""
|
||||
suggested_warranty_until: date | None = None
|
||||
suggested_price_cents: int | None = None
|
||||
# Alle plausiblen Preise (bester zuerst) – zur Auswahl, falls es mehrere gibt.
|
||||
suggested_price_candidates: list[int] = []
|
||||
|
||||
|
||||
class ItemOut(BaseModel):
|
||||
|
||||
@@ -177,3 +177,27 @@ def guess_price_cents(text: str) -> int | None:
|
||||
if dist <= 40 and dist < best_dist:
|
||||
best, best_dist = c, dist
|
||||
return best if best is not None else max(c for _, c in betraege)
|
||||
|
||||
|
||||
def guess_price_candidates(text: str) -> list[int]:
|
||||
"""Alle plausiblen Beträge aus dem Beleg (in Rappen/Cent), ohne Dubletten.
|
||||
|
||||
Der beste Tipp (:func:`guess_price_cents`) steht vorne; danach die übrigen
|
||||
Beträge absteigend. So kann die Oberfläche eine Auswahl anbieten, falls der
|
||||
automatische Tipp danebenliegt.
|
||||
"""
|
||||
if not text:
|
||||
return []
|
||||
einzigartig = set()
|
||||
for m in _AMOUNT_RE.finditer(text):
|
||||
c = _amount_to_cents(m.group())
|
||||
if c:
|
||||
einzigartig.add(c)
|
||||
if not einzigartig:
|
||||
return []
|
||||
kandidaten = sorted(einzigartig, reverse=True)
|
||||
best = guess_price_cents(text)
|
||||
if best is not None and best in kandidaten:
|
||||
kandidaten.remove(best)
|
||||
kandidaten.insert(0, best)
|
||||
return kandidaten[:8]
|
||||
|
||||
Reference in New Issue
Block a user