procurement-ingest/test_local.py
Adam Moussa 89bf47c1bb
Add offline WO parser test suite
Cover the deterministic parser with golden-file tests over 55 real
scrubbed .eml fixtures (both comment sub-shapes, username Submitted-By,
address present/absent, br+CRLF assign addresses), fail-closed
validation-gate rules, adversarial and prompt-injection cases that must
route to ai_fallback or parse without corrupting other fields, the issue
#23 comment_id idempotency invariants, and the Bedrock-fallback dispatch
plus EMF-metric emission with a mocked invoke_model.

Extend pytest.ini testpaths to discover the co-located suite, and update
tests/conftest.load_handler to put a handler's own directory on sys.path
so the WO handler's new `from template_parser import ...` resolves under
the existing shared handler tests. Point test_local.py at the new
template-first + Bedrock flow.

Refs: #23
2026-07-15 20:11:04 -04:00

54 lines
1.7 KiB
Python

#!/usr/bin/env python3
"""
Local test script - parses sample WO emails through the deterministic template
parser, falling back to Claude on Bedrock for anything the templates don't cover.
Usage:
# AWS credentials with bedrock:InvokeModel are only needed if a sample falls
# back to the AI extractor; pure-template samples parse fully offline.
python test_local.py
"""
import json
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent / "lambdas" / "wo" / "email_processor"))
from handler import parse_raw_email, extract_with_bedrock
from template_parser import try_deterministic_parse
def main():
samples_dir = Path(__file__).parent / "samples"
eml_files = list(samples_dir.glob("*.eml"))
if not eml_files:
print("No .eml files found in samples/")
return
for eml_path in eml_files:
print(f"\n{'=' * 80}")
print(f"FILE: {eml_path.name}")
print(f"{'=' * 80}")
raw = eml_path.read_bytes()
email_data = parse_raw_email(raw)
print(f"Subject: {email_data['subject']}")
print(f"From: {email_data['sender']}")
print(f"Date: {email_data['date']}")
print(f"Body preview: {email_data['body'][:200]}...")
print()
parsed, method, template_id, reason = try_deterministic_parse(email_data)
if parsed is None:
print(f"Template miss ({template_id}/{reason}); sending to Bedrock...")
parsed = extract_with_bedrock(email_data)
method = "ai_fallback"
print(f"Parse method: {method} (template={template_id}, reason={reason})")
print(json.dumps(parsed, indent=2))
if __name__ == "__main__":
main()