Spaces:
Sleeping
Sleeping
| import time | |
| from dataclasses import dataclass, field | |
| from pathlib import Path | |
| import easyocr | |
| from google import genai | |
| from src.kyc_detector import detect_kyc | |
| from src.llm_classifier import classify_with_llm | |
| from src.monitoring import trace_classify, trace_kyc_ocr, trace_llm_classify, trace_rules_engine | |
| from src.rules_engine import apply_rules | |
| _SUB_TYPE_DEFAULTS = {"bill": "Bills", "kyc": "KYC"} | |
| def _default_sub_type(doc_type: str) -> str | None: | |
| return _SUB_TYPE_DEFAULTS.get(doc_type) | |
| class ClassificationResult: | |
| filename: str | |
| doc_type: str # "bill" | "kyc" | "image" | |
| sub_type: str | None # Gemini category for "image" docs, else None | |
| method: str # "rules" | "ocr" | "llm" | |
| latency_ms: int | |
| input_tokens: int = 0 | |
| output_tokens: int = 0 | |
| def classify( | |
| filename: str, | |
| image_bytes: bytes, | |
| ocr_reader: easyocr.Reader | None = None, | |
| llm_client: genai.Client | None = None, | |
| ) -> ClassificationResult: | |
| """Run a single image through the full classification pipeline.""" | |
| start = time.monotonic() | |
| # Stage 1 — rules engine (filename-based, no ML) | |
| rules_result = apply_rules(filename) | |
| trace_rules_engine( | |
| filename=filename, | |
| doc_type=rules_result.doc_type, | |
| send_to_llm=rules_result.send_to_llm, | |
| ) | |
| if not rules_result.send_to_llm: | |
| result = ClassificationResult( | |
| filename=filename, | |
| doc_type=rules_result.doc_type, | |
| sub_type=_default_sub_type(rules_result.doc_type), | |
| method="rules", | |
| latency_ms=int((time.monotonic() - start) * 1000), | |
| ) | |
| trace_classify(**result.__dict__) | |
| return result | |
| # Stage 2 — KYC OCR detector | |
| kyc_result = detect_kyc(filename, image_bytes, reader=ocr_reader) | |
| trace_kyc_ocr( | |
| filename=filename, | |
| doc_type=kyc_result.doc_type, | |
| send_to_llm=kyc_result.send_to_llm, | |
| ocr_text=kyc_result.ocr_text, | |
| ) | |
| if not kyc_result.send_to_llm: | |
| result = ClassificationResult( | |
| filename=filename, | |
| doc_type=kyc_result.doc_type, | |
| sub_type=_default_sub_type(kyc_result.doc_type), | |
| method="ocr", | |
| latency_ms=int((time.monotonic() - start) * 1000), | |
| ) | |
| trace_classify(**result.__dict__) | |
| return result | |
| # Stage 3 — Gemini LLM classifier | |
| llm_result = classify_with_llm(filename, image_bytes, client=llm_client) | |
| trace_llm_classify( | |
| filename=filename, | |
| sub_type=llm_result.sub_type, | |
| input_tokens=llm_result.input_tokens, | |
| output_tokens=llm_result.output_tokens, | |
| raw_response=llm_result.raw_response, | |
| ) | |
| result = ClassificationResult( | |
| filename=filename, | |
| doc_type=llm_result.doc_type, | |
| sub_type=llm_result.sub_type, | |
| method="llm", | |
| latency_ms=int((time.monotonic() - start) * 1000), | |
| input_tokens=llm_result.input_tokens, | |
| output_tokens=llm_result.output_tokens, | |
| ) | |
| trace_classify(**result.__dict__) | |
| return result | |
| def classify_dataset( | |
| dataset_dir: str | Path = "dataset", | |
| ocr_reader: easyocr.Reader | None = None, | |
| llm_client: genai.Client | None = None, | |
| ) -> list[ClassificationResult]: | |
| """Classify all PNG/JPEG images in a directory.""" | |
| dataset_path = Path(dataset_dir) | |
| results = [] | |
| for image_path in sorted(dataset_path.glob("*.png")): | |
| image_bytes = image_path.read_bytes() | |
| result = classify( | |
| filename=image_path.name, | |
| image_bytes=image_bytes, | |
| ocr_reader=ocr_reader, | |
| llm_client=llm_client, | |
| ) | |
| results.append(result) | |
| return results | |