RcmEmailAutomation / scripts /preview_brief.py
Avinash Nalla
Phase 2+4: PO/Invoice client folder routing and subject-line MTR trigger
c55abce
Raw
History Blame Contribute Delete
4 kB
"""Render a sample Daily Brief email for client sign-off.
Runs the production `_build_brief_html()` with realistic synthetic data
covering all three section types: ambiguous routings, MTRs missing heat
codes, and processing errors.
Usage: python -m scripts.preview_brief [output_path]
If output_path is omitted, writes to ./daily_brief_preview.html relative
to the repo root.
"""
from __future__ import annotations
import json
import sys
from pathlib import Path
# Make repo root importable when run via `python scripts/preview_brief.py`
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from app.lib.utils.brief_html import build_brief_html
# Synthetic items modeled on real `daily_brief` rows. Confidence scores are
# JSON-encoded strings because that's how they're stored in SQLite.
SAMPLE_ITEMS: list[dict] = [
# Two ambiguous routings — multiple doc types crossed the threshold.
{
"id": 1,
"sender_email": "ap@westlake.com",
"subject": "RCM - Westlake PO 4502953154 and quote",
"attachment_filename": "Westlake_PO_4502953154.pdf",
"reason": "ambiguous",
"confidence_scores": json.dumps({
"po": 0.81, "invoice": 0.12, "mtr": 0.03, "quote": 0.74,
}),
},
{
"id": 2,
"sender_email": "billing@halliburton.com",
"subject": "RCM - Halliburton invoice 759085",
"attachment_filename": "Halliburton_invoice_759085.pdf",
"reason": "ambiguous",
"confidence_scores": json.dumps({
"po": 0.71, "invoice": 0.78, "mtr": 0.01, "quote": 0.05,
}),
},
# Two MTRs where heat code couldn't be extracted.
{
"id": 3,
"sender_email": "qa@woi-houston.com",
"subject": "RCM - MTR for WOI order W000009810",
"attachment_filename": "MTR W000009810.pdf",
"reason": "mtr_no_heat_code",
"confidence_scores": json.dumps({
"po": 0.04, "invoice": 0.02, "mtr": 0.82, "quote": 0.00,
}),
},
{
"id": 4,
"sender_email": "certs@flotite.com",
"subject": "RCM - Material Test Report S3000CSPGGL015MP",
"attachment_filename": "0.5_Full Port_Part No S3000CSPGGL015MP.pdf",
"reason": "mtr_no_heat_code",
"confidence_scores": json.dumps({
"po": 0.03, "invoice": 0.01, "mtr": 0.79, "quote": 0.00,
}),
},
# Two unmatched sender domains.
{
"id": 5,
"sender_email": "purchasing@newvendor-inc.com",
"subject": "RCM - PO from a vendor we haven't seen before",
"attachment_filename": "NewVendor_PO_2026-04-22.pdf",
"reason": "client_unmatched",
"confidence_scores": json.dumps({
"po": 0.86, "invoice": 0.08, "mtr": 0.02, "quote": 0.04,
}),
},
{
"id": 6,
"sender_email": "billing@small-shop.com",
"subject": "RCM - Invoice March 2026",
"attachment_filename": "Invoice_March_2026.pdf",
"reason": "client_unmatched",
"confidence_scores": json.dumps({
"po": 0.11, "invoice": 0.84, "mtr": 0.01, "quote": 0.03,
}),
},
# One processing error.
{
"id": 7,
"sender_email": "noreply@example-vendor.com",
"subject": "RCM - corrupted attachment",
"attachment_filename": "scan_2026-05-10_corrupted.pdf",
"reason": "error",
"error_message": (
"PDFSyntaxError: No /Root object! - Is this really a PDF? "
"(stack trace in /logs/processor.log.2026-05-10)"
),
"confidence_scores": None,
},
]
def main() -> None:
out = Path(sys.argv[1]) if len(sys.argv) > 1 else (
Path(__file__).resolve().parent.parent / "daily_brief_preview.html"
)
html = build_brief_html(SAMPLE_ITEMS)
out.write_text(html, encoding="utf-8")
print(f"Wrote {len(html)} bytes to {out}")
print("Open the file in a browser to preview.")
if __name__ == "__main__":
main()