"""Daily Brief HTML rendering. Pure-stdlib module — has no dependencies on the rest of the app, so it can be imported by preview scripts, tests, and the scheduler alike. The shape of the items list matches a SELECT * from the `daily_brief` table. """ from __future__ import annotations import json def build_brief_html(items: list[dict]) -> str: """Render the Daily Brief email body from `daily_brief` rows. Sections rendered (in order, only if non-empty): 1. Ambiguous Routings — `reason == "ambiguous"` 2. MTRs Missing Heat Code — `reason == "mtr_no_heat_code"` 3. Processing Errors — `reason == "error"` If all sections are empty, returns a short "nothing to report" body. """ ambiguous = [i for i in items if i["reason"] == "ambiguous"] no_heat = [i for i in items if i["reason"] == "mtr_no_heat_code"] unmatched = [i for i in items if i["reason"] == "client_unmatched"] errors = [i for i in items if i["reason"] == "error"] sections: list[str] = [] # One-line summary at top so ops doesn't need to count rows. summary_bits = [] if ambiguous: summary_bits.append(f"{len(ambiguous)} ambiguous routing(s)") if no_heat: summary_bits.append(f"{len(no_heat)} MTR(s) without a heat code") if unmatched: summary_bits.append(f"{len(unmatched)} unmatched sender domain(s)") if errors: summary_bits.append(f"{len(errors)} processing error(s)") if summary_bits: sections.append( f"
Today's automation summary: {'; '.join(summary_bits)}.
" ) if ambiguous: rows = "" for item in ambiguous: scores: dict = ( json.loads(item["confidence_scores"]) if item.get("confidence_scores") else {} ) score_cols = "".join( f"These attachments scored above the confidence threshold for " "more than one document type and were not auto-routed. Review and " "file manually.
" "| Sender | Subject | Attachment | {score_headers}
|---|
These MTRs were uploaded to MTR/_no_heat_code/ "
"because no heat code could be extracted from the document. "
"Please review the file in Dropbox, identify the heat code(s), "
"and move/copy under the correct MTR/<heat_code>/ "
"folder.
| Sender | Subject | Attachment |
|---|
These POs/Invoices were uploaded to "
"POs/_unmatched/ or Invoices/_unmatched/ "
"because the sender's email domain isn't in the client mapping. "
"Move the file under the right client folder in Dropbox, then "
"add the domain → client mapping to "
"scripts/seed_client_domains.json and re-run the "
"seeder so future emails route correctly.
| Sender | Subject | Attachment |
|---|
These attachments raised an exception during processing. " "Stack traces are in the processor log files.
" "| Sender | Subject | Attachment | Error |
|---|
No items to report today — all attachments routed cleanly.
" "" ) return f"{''.join(sections)}"