#!/usr/bin/env python3 """Test each receipt sequentially and report results.""" import json import os import sys import time import requests from datetime import datetime, timedelta from jose import jwt API_BASE = "http://localhost:8000" PDF_FOLDER = "/mnt/e/proiecte/ab-worktrees/doctr-ocr-metrics/docs/data-entry" EXPECTED_FILE = "/mnt/e/proiecte/ab-worktrees/doctr-ocr-metrics/tests/ocr-validation/expected_receipts.json" def get_jwt_token(): """Create a test JWT token for API authentication.""" secret_key = os.getenv('JWT_SECRET_KEY', 'generate_with_secrets_token_urlsafe_32') now = datetime.utcnow() expire = now + timedelta(hours=1) payload = { "username": "MARIUS", "user_id": 1, "companies": ["604"], "permissions": ["read", "write"], "exp": expire, "iat": now, "type": "access" } return jwt.encode(payload, secret_key, algorithm="HS256") def test_receipt(pdf_path: str, expected: dict, headers: dict) -> dict: """Test a single receipt and return results.""" filename = os.path.basename(pdf_path) result = { "filename": filename, "success": False, "time_ms": 0, "error": None, "extracted": {}, "matches": {}, "issues": [] } start_time = time.time() try: with open(pdf_path, "rb") as f: files = {"file": (filename, f, "application/pdf")} response = requests.post( f"{API_BASE}/api/data-entry/ocr/extract?engine=doctr_plus", files=files, headers=headers, timeout=120 ) if response.status_code != 200: result["error"] = f"HTTP {response.status_code}" result["time_ms"] = int((time.time() - start_time) * 1000) return result job_data = response.json() job_id = job_data.get("job_id") # Poll for completion for _ in range(60): # Max 60 polls (2 minutes) poll_response = requests.get( f"{API_BASE}/api/data-entry/ocr/jobs/{job_id}/wait?timeout=30", headers=headers, timeout=35 ) if poll_response.status_code == 200: job_result = poll_response.json() status = job_result.get("status") if status == "completed": break elif status == "error": result["error"] = job_result.get("error", "Unknown error") result["time_ms"] = int((time.time() - start_time) * 1000) return result time.sleep(1) result["time_ms"] = int((time.time() - start_time) * 1000) if job_result.get("status") != "completed": result["error"] = f"Timeout - status: {job_result.get('status')}" return result # Extract fields (correct field names from API) extraction = job_result.get("result", {}) result["extracted"] = { "total": extraction.get("amount"), # API uses "amount" not "total" "date": extraction.get("receipt_date"), # API uses "receipt_date" not "date" "cui": extraction.get("cui"), "tva_total": extraction.get("tva_total"), "confidence": extraction.get("overall_confidence") } # Compare with expected (use correct field names from expected_receipts.json) exp_total = expected.get("total") exp_date = expected.get("data_bon") exp_cui = expected.get("cui_furnizor") # Normalize for comparison def normalize_total(val): if val is None: return None return float(str(val).replace(',', '.')) def normalize_cui(val): if val is None: return None return str(val).upper().replace('RO', '').replace(' ', '').strip() ext_total = normalize_total(result["extracted"]["total"]) ext_cui = normalize_cui(result["extracted"]["cui"]) exp_cui_norm = normalize_cui(exp_cui) exp_total_norm = normalize_total(exp_total) result["matches"]["total"] = abs(ext_total - exp_total_norm) < 0.01 if ext_total and exp_total_norm else None result["matches"]["date"] = result["extracted"]["date"] == exp_date if exp_date else None result["matches"]["cui"] = ext_cui == exp_cui_norm if exp_cui else None # Check for issues if exp_total and not result["matches"]["total"]: result["issues"].append(f"TOTAL: got {result['extracted']['total']}, expected {exp_total}") if exp_date and not result["matches"]["date"]: result["issues"].append(f"DATE: got {result['extracted']['date']}, expected {exp_date}") if exp_cui and not result["matches"]["cui"]: result["issues"].append(f"CUI: got {result['extracted']['cui']}, expected {exp_cui}") result["success"] = len(result["issues"]) == 0 except Exception as e: result["error"] = str(e) result["time_ms"] = int((time.time() - start_time) * 1000) return result def main(): # Load expected data with open(EXPECTED_FILE) as f: expected_data = json.load(f) # Handle both formats: list or dict with "receipts" key if isinstance(expected_data, dict) and "receipts" in expected_data: all_receipts = expected_data["receipts"] else: all_receipts = expected_data # Get JWT token token = get_jwt_token() if not token: print("ERROR: Could not get JWT token") sys.exit(1) headers = {"Authorization": f"Bearer {token}"} # Filter single-page receipts receipts = [r for r in all_receipts if r.get("pages", 1) == 1] print(f"\n{'='*60}") print(f"Testing {len(receipts)} single-page receipts with doctr_plus") print(f"{'='*60}\n") results = [] times = [] for i, receipt in enumerate(receipts, 1): filename = receipt["filename"] pdf_path = os.path.join(PDF_FOLDER, filename) if not os.path.exists(pdf_path): print(f"[{i:02d}/{len(receipts)}] SKIP: {filename} (not found)") continue print(f"[{i:02d}/{len(receipts)}] Testing: {filename[:50]}...", end=" ", flush=True) result = test_receipt(pdf_path, receipt, headers) results.append(result) if result["error"]: print(f"ERROR ({result['time_ms']}ms): {result['error']}") elif result["success"]: print(f"OK ({result['time_ms']}ms) conf={result['extracted'].get('confidence', 0):.2f}") times.append(result["time_ms"]) else: print(f"FAIL ({result['time_ms']}ms): {'; '.join(result['issues'])}") times.append(result["time_ms"]) # Summary print(f"\n{'='*60}") print("SUMMARY") print(f"{'='*60}") successful = [r for r in results if r["success"]] failed = [r for r in results if not r["success"] and not r["error"]] errors = [r for r in results if r["error"]] print(f"Total: {len(results)}") print(f"Success: {len(successful)} ({len(successful)*100/len(results):.1f}%)") print(f"Failed: {len(failed)}") print(f"Errors: {len(errors)}") if times: avg_time = sum(times) / len(times) print(f"\nTiming: avg={avg_time:.0f}ms, min={min(times)}ms, max={max(times)}ms") # Flag slow ones slow_threshold = avg_time * 2 slow = [r for r in results if r["time_ms"] > slow_threshold and not r["error"]] if slow: print(f"\nSlow receipts (>{slow_threshold:.0f}ms):") for r in slow: print(f" - {r['filename']}: {r['time_ms']}ms") if failed: print(f"\nFailed receipts:") for r in failed: print(f" - {r['filename']}: {'; '.join(r['issues'])}") if errors: print(f"\nError receipts:") for r in errors: print(f" - {r['filename']}: {r['error']}") if __name__ == "__main__": main()