drowzeys's picture
v1.0 alpha: keys-Auto Receipts Studio (iPhone / may add Autonomous Lamp Skill)
2edb151 verified
Raw
History Blame Contribute Delete
2.31 kB
from __future__ import annotations
from app.config import CATEGORIES, DOC_KINDS, Settings
from app.schemas import ReceiptExtract, parse_extract_json
from backends.base import LLMBackend
SYSTEM = (
"You extract structured data from a photo of a receipt, invoice, or paper document. "
"Reply with a single JSON object only — no markdown, no commentary, no trailing text."
)
_SCHEMA_HINT = f"""
Required keys:
doc_kind: one of {list(DOC_KINDS)}
category: one of {list(CATEGORIES)}
vendor: string or null
date: YYYY-MM-DD or null
tax: number or null
total: number or null
currency: string or null (ISO 4217 if known)
line_items: array of objects with description (string), qty (number|null),
unit_price (number|null), amount (number|null), sku (string|null)
Rules:
- Money as numbers, not strings. Unknown fields must be null.
- Do not invent SKUs, vendors, or totals. If unreadable, use null.
- Prefer the printed total over summing line items when they disagree.
- category is the spend bucket (groceries, dining, …), not the store name.
""".strip()
def build_user_prompt(*, ocr_text: str | None, hint: str | None = None) -> str:
parts = [_SCHEMA_HINT]
if ocr_text:
parts.append("OCR assist (may be noisy):\n" + ocr_text.strip()[:8000])
if hint:
parts.append(hint)
parts.append("Extract the JSON now.")
return "\n\n".join(parts)
def extract_receipt(
llm: LLMBackend,
*,
settings: Settings,
image_jpeg: bytes | None,
ocr_text: str | None,
) -> ReceiptExtract:
del settings
if image_jpeg and not llm.accepts_images:
image_jpeg = None
if image_jpeg is None and not (ocr_text and ocr_text.strip()):
raise ValueError("need an image (vision LLM) or OCR/text to extract")
user = build_user_prompt(ocr_text=ocr_text)
raw = llm.complete_json(system=SYSTEM, user=user, image_jpeg=image_jpeg)
try:
return parse_extract_json(raw)
except (ValueError, Exception) as first:
retry = build_user_prompt(
ocr_text=ocr_text,
hint=f"Previous output failed validation: {first}. Return corrected JSON only.",
)
raw2 = llm.complete_json(system=SYSTEM, user=retry, image_jpeg=image_jpeg)
return parse_extract_json(raw2)