#!/usr/bin/env python3 """AutoFax - Send a fax every hour via Sinch and log results.""" import json import subprocess import sys import time from datetime import datetime, timezone from pathlib import Path import requests import config API = f"https://fax.api.sinch.com/v3/projects/{config.SINCH_PROJECT_ID}" def auth(): return (config.SINCH_KEY_ID, config.SINCH_KEY_SECRET) def find_claim_pdf() -> Path: pdfs = sorted(config.CLAIMS_DIR.glob("*.pdf")) if not pdfs: print("ERROR: No PDF found in claims/ directory.", file=sys.stderr) sys.exit(1) return pdfs[0] def send_fax(pdf_path: Path) -> dict: """Send a fax via Sinch with direct file upload. Returns result dict.""" timestamp = datetime.now(timezone.utc).isoformat() try: with open(pdf_path, "rb") as f: resp = requests.post( f"{API}/faxes", auth=auth(), files={"file": (pdf_path.name, f, "application/pdf")}, data={k: v for k, v in { "to": config.FAX_TO_NUMBER, "from": config.SINCH_FROM_NUMBER or None, }.items() if v}, timeout=60, ) resp.raise_for_status() fax_data = resp.json() return { "timestamp": timestamp, "status": fax_data.get("status", "QUEUED"), "fax_id": fax_data.get("id"), "to": config.FAX_TO_NUMBER, "from": config.SINCH_FROM_NUMBER, "file": pdf_path.name, "error": None, } except requests.HTTPError as e: error_body = "" try: error_body = e.response.json() except Exception: error_body = e.response.text[:500] return { "timestamp": timestamp, "status": "send_failed", "fax_id": None, "to": config.FAX_TO_NUMBER, "from": config.SINCH_FROM_NUMBER, "file": pdf_path.name, "error": f"{e} | {error_body}", } except Exception as e: return { "timestamp": timestamp, "status": "send_failed", "fax_id": None, "to": config.FAX_TO_NUMBER, "from": config.SINCH_FROM_NUMBER, "file": pdf_path.name, "error": str(e), } def check_fax_status(fax_id: str) -> dict | None: if not fax_id: return None try: resp = requests.get(f"{API}/faxes/{fax_id}", auth=auth(), timeout=15) resp.raise_for_status() return resp.json() except Exception: return None def load_log() -> list[dict]: if config.LOG_FILE.exists(): return json.loads(config.LOG_FILE.read_text()) return [] def save_log(entries: list[dict]): config.REPORTS_DIR.mkdir(parents=True, exist_ok=True) config.LOG_FILE.write_text(json.dumps(entries, indent=2)) def notify(subject: str, message: str, priority: str = "default"): if not config.NTFY_URL: return try: hdrs = {"Title": subject, "Priority": priority} if config.NTFY_TOKEN: hdrs["Authorization"] = config.NTFY_TOKEN requests.post(config.NTFY_URL, data=message.encode(), headers=hdrs, timeout=10) except Exception: pass def generate_report(entries: list[dict]): config.REPORTS_DIR.mkdir(parents=True, exist_ok=True) total = len(entries) success_statuses = {"QUEUED", "IN_PROGRESS", "COMPLETED"} failed = sum(1 for e in entries if e["status"] not in success_statuses) succeeded = sum(1 for e in entries if e["status"] == "COMPLETED") pending = total - failed - succeeded rows = "" for i, e in enumerate(entries, 1): status = e["status"] if status == "COMPLETED": status_class = "success" status_display = "DELIVERED" elif status in ("QUEUED", "IN_PROGRESS"): status_class = "pending" status_display = status else: status_class = "failed" status_display = "FAILED" error_col = e.get("error") or e.get("failure_reason") or "-" try: dt = datetime.fromisoformat(e["timestamp"]) ts_display = dt.strftime("%b %d, %Y %I:%M %p UTC") except Exception: ts_display = e["timestamp"] rows += f"""
This report documents automated fax transmission attempts to the insurance company for claim processing.
Total attempts: {total} | Delivered: {succeeded} | Failed: {failed} | Pending: {pending}
Fax destination: {entries[0]['to'] if entries else 'N/A'}
Report generated: {datetime.now(timezone.utc).strftime('%B %d, %Y at %I:%M %p UTC')}
| # | Date & Time | Fax Number | Status | Confirmation ID | Error Details |
|---|