Add project files
This commit is contained in:
76
pipeline/verify.py
Normal file
76
pipeline/verify.py
Normal file
@@ -0,0 +1,76 @@
|
||||
"""
|
||||
Optional third-party AI verification, layered on top of the core
|
||||
feature-matching pipeline: sends the winning template's original photo and
|
||||
the user's original upload to an external vision-LLM endpoint that runs a
|
||||
detailed QC-style comparison (flowers, vase, ribbon, composition) and
|
||||
returns a MATCH/DISCREPANCIES/CONFIDENCE verdict plus free-text description.
|
||||
|
||||
This never affects the core match result -- if the endpoint is slow, down,
|
||||
or unreachable (it's a Cloudflare tunnel, which can go stale), the caller
|
||||
is expected to treat any exception here as a soft failure.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
|
||||
import requests
|
||||
|
||||
import config
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
_VERDICT_RE = re.compile(
|
||||
# DISCREP\w* rather than a literal "DISCREPANCIES" -- the LLM behind
|
||||
# the endpoint doesn't reliably spell it the same way every time
|
||||
# ("DISCREPANCIES" vs "DISCREPENCIES" have both been observed), and a
|
||||
# missed match here silently dumps the whole raw block into the UI.
|
||||
r"MATCH:\s*\[?\s*(?P<match>YES|NO|PARTIAL)\s*\]?\s*"
|
||||
r"DISCREP\w*:\s*\[?\s*(?P<discrepancies>.*?)\s*\]?\s*"
|
||||
r"CONFIDENCE:\s*\[?\s*(?P<confidence>High|Medium|Low)\s*\]?",
|
||||
re.IGNORECASE | re.DOTALL,
|
||||
)
|
||||
|
||||
|
||||
def _parse_result_text(text: str) -> dict:
|
||||
"""The endpoint's `result` field is free text with an embedded
|
||||
MATCH/DISCREPANCIES/CONFIDENCE block, sometimes followed by a prose
|
||||
description, sometimes not. Pull out the structured bits; whatever's
|
||||
left over (if anything) is the description."""
|
||||
text = text or ""
|
||||
m = _VERDICT_RE.search(text)
|
||||
|
||||
if not m:
|
||||
return {"match": None, "confidence": None, "discrepancies": None,
|
||||
"description": text.strip()}
|
||||
|
||||
return {
|
||||
"match": m.group("match").upper(),
|
||||
"confidence": m.group("confidence").capitalize(),
|
||||
"discrepancies": m.group("discrepancies").strip() or "None",
|
||||
"description": text[m.end():].strip(),
|
||||
}
|
||||
|
||||
|
||||
def verify_images(reference_path: str, actual_path: str) -> dict:
|
||||
"""reference_path = the matched template's original photo (IMAGE 1),
|
||||
actual_path = the user's original upload (IMAGE 2)."""
|
||||
with open(reference_path, "rb") as f1, open(actual_path, "rb") as f2:
|
||||
files = {
|
||||
"image1": (os.path.basename(reference_path), f1, "image/png"),
|
||||
"image2": (os.path.basename(actual_path), f2, "image/jpeg"),
|
||||
}
|
||||
response = requests.post(config.VERIFY_ENDPOINT_URL, files=files,
|
||||
timeout=config.VERIFY_TIMEOUT_SECONDS)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
parsed = _parse_result_text(data.get("result", ""))
|
||||
return {
|
||||
"match": parsed["match"],
|
||||
"confidence": parsed["confidence"],
|
||||
"discrepancies": parsed["discrepancies"],
|
||||
"description": parsed["description"],
|
||||
"raw_result": data.get("result"),
|
||||
"pixel_precheck": data.get("pixel_precheck"),
|
||||
}
|
||||
Reference in New Issue
Block a user