Add crop refinement, finger removal, and local LLM naming

- vision/refine.py: tighten crops to the paper band (removes mat
  margins and hands beside receipts) and inpaint border-connected
  skin regions so fingers disappear from output
- llm/vision.py: identify documents with a local Ollama vision model
  (qwen2.5vl); extracts vendor/date/total/form code and flags quality
  issues (fingers, blur, glare); falls back to Tesseract when down
- pipeline: drop blank pages, dedupe consecutive captures of the same
  document, record refine/LLM fields in report and export summary
This commit is contained in:
2026-07-08 18:23:50 -04:00
parent 2c28a623fb
commit a43ab20df6
22 changed files with 1497 additions and 111 deletions
+21
View File
@@ -32,11 +32,32 @@ def build_name(
vendor: str | None,
fallback_dt: datetime,
max_vendor_len: int = 40,
form_code: str | None = None,
tax_year: str | None = None,
org_name: str | None = None,
) -> NamingResult:
"""date must already be normalized to YYYY-MM-DD, or None."""
vendor_slug = slugify(vendor, max_vendor_len) if vendor else None
ts = fallback_dt.strftime("%Y%m%d_%H%M%S")
# Tax slips: prefer 2023_T4_York_University over UNSORTED_t4_york_university_<timestamp>.
if form_code:
org_slug = slugify(org_name, max_vendor_len) if org_name else None
if not org_slug and vendor:
stripped = vendor
if stripped.upper().startswith(form_code.upper()):
stripped = stripped[len(form_code) :].strip()
org_slug = slugify(stripped, max_vendor_len) if stripped else None
year = tax_year or (date[:4] if date else None)
code = form_code.upper()
if year and org_slug:
return NamingResult(stem=f"{year}_{code}_{org_slug}", source="ocr")
if year:
return NamingResult(stem=f"{year}_{code}", source="ocr")
if org_slug:
return NamingResult(stem=f"{code}_{org_slug}", source="ocr")
return NamingResult(stem=code.lower(), source="ocr")
if date and vendor_slug:
return NamingResult(stem=f"{date}_{vendor_slug}", source="ocr")
if date: