CoolFace
Apppublic

VAKYA/Report_generation

sourceHugging Faceupdated 5mo agoView on Hugging Face
0likes
execution_agent.py191 linesDownload Raw Back to server
1"""Execution agent loop for retries, routing, and email tracking."""2 3from __future__ import annotations4 5import base646import os7import smtplib8from email.message import EmailMessage9from typing import Dict10 11import requests12 13from .database import ReportTrackingDB14from .report_builder import build_10am_account_statement, build_11am_finance_summary15 16 17def _send_agent_email(18    recipient_email: str,19    subject: str,20    body: str,21    attachment_name: str | None = None,22    attachment_bytes: bytes | None = None,23) -> tuple[bool, str]:24    """Send scheduler email using SMTP.25 26    Behavior:27    - If AGENT_EMAIL_MOCK=1, do mock send (useful for local testing).28    - Otherwise requires SMTP_EMAIL and SMTP_PASSWORD to send real emails.29    """30    if os.environ.get("AGENT_EMAIL_MOCK", "0") == "1":31        print(f"[EMAIL][MOCK] recipient={recipient_email} subject={subject}")32        return True, "mock_sent"33 34    sender_email = os.environ.get("SENDER_EMAIL") or os.environ.get("SMTP_EMAIL")35    api_key = os.environ.get("BREVO_API_KEY") or os.environ.get("SMTP_PASSWORD")36 37    if not sender_email or not api_key:38        print(f"[EMAIL][SKIP] smtp_not_configured recipient={recipient_email} (Configure SENDER_EMAIL and BREVO_API_KEY)")39        return False, "api_keys_not_configured"40 41    try:42        url = "https://api.brevo.com/v3/smtp/email"43        payload: Dict[str, object] = {44            "sender": {"name": "Reporting Service Agent", "email": sender_email},45            "to": [{"email": recipient_email}],46            "subject": subject,47            "textContent": body,48        }49        50        if attachment_name and attachment_bytes:51            payload["attachment"] = [52                {53                    "name": attachment_name,54                    "content": base64.b64encode(attachment_bytes).decode("ascii")55                }56            ]57 58        print(f"[EMAIL][BREVO] attempting recipient={recipient_email}")59        60        headers = {61            "accept": "application/json",62            "api-key": api_key,63            "content-type": "application/json"64        }65        66        response = requests.post(url, json=payload, headers=headers, timeout=15)67        response.raise_for_status()68        print(f"[EMAIL][BREVO] sent recipient={recipient_email}")69        return True, "sent_via_brevo"70    except Exception as exc:71        err_msg = str(exc)72        if hasattr(exc, 'response') and exc.response is not None:73            err_msg += f" body={exc.response.text}"74        print(f"[EMAIL][BREVO][ERROR] recipient={recipient_email} err={err_msg}")75        return False, f"brevo_error:{exc}"76 77 78def execute_report_job(db: ReportTrackingDB, report_id: str) -> Dict[str, object]:79    """Run one report job with max retries and DB live updates.80 81    Rules:82    - Permanent LAN failure -> retry until max_retries, then fail.83    - Success -> send report to resolved customer email.84    - Failure -> send failure email only to fixed failure email.85    """86    track = db.get_live_track(report_id)87    if track is None:88        raise ValueError(f"Unknown report_id: {report_id}")89 90    max_retries = int(track["max_retries"])91    lan_id = int(track["lan_id"])92    lan_code = None93    should_fail_permanently = 094    for pair in db.get_customer_lan_pairs():95        if int(pair["lan_id"]) == lan_id:96            lan_code = str(pair["lan_code"])97            should_fail_permanently = int(pair["should_fail_permanently"])98            break99 100    if lan_code is None:101        raise ValueError(f"LAN mapping not found for report_id: {report_id}")102 103    for attempt in range(1, max_retries + 1):104        db.update_live_track_status(105            report_id=report_id,106            status="in_progress" if attempt == 1 else "retrying",107            retries_used=attempt,108            error_code=None,109            error_message=None,110            report_generated=False,111            finished=False,112        )113 114        if should_fail_permanently == 1:115            db.update_live_track_status(116                report_id=report_id,117                status="retrying" if attempt < max_retries else "failed",118                retries_used=attempt,119                error_code="lan_permanent_failure",120                error_message=f"{lan_code} report generation failed",121                report_generated=False,122                finished=attempt == max_retries,123            )124            continue125 126        # Success path127        db.update_live_track_status(128            report_id=report_id,129            status="success",130            retries_used=attempt,131            error_code=None,132            error_message=None,133            report_generated=True,134            finished=True,135        )136        recipient = db.resolve_recipient_email(report_id, is_failure=False)137        context = db.get_report_context(report_id)138        if context is None:139            raise ValueError(f"Report context not found: {report_id}")140        if str(track["scheduler_slot"]) == "10am":141            pdf_bytes = build_10am_account_statement(context)142        else:143            pdf_bytes = build_11am_finance_summary(context)144        sent, reason = _send_agent_email(145            recipient_email=recipient,146            subject=f"[SUCCESS] Report generated for {lan_code}",147            body=(148                f"Hello,\n\nYour report has been generated and sent by agent.\n"149                f"Report ID: {report_id}\nLAN: {lan_code}\nType: {track['report_type']}\n"150            ),151            attachment_name=f"{report_id}.pdf",152            attachment_bytes=pdf_bytes,153        )154        # SMTP can accept mail even if remote mailbox later bounces.155        email_status = "success_accepted_by_smtp" if sent else f"success_failed:{reason}"156        db.update_email_status(report_id, sent=sent, status=email_status)157        return {158            "report_id": report_id,159            "status": "success",160            "attempts": attempt,161            "email_recipient": recipient,162            "email_sent": sent,163            "email_status": email_status,164        }165 166    # Final failure path: email goes only to fixed failure notification address.167    recipient = db.resolve_recipient_email(report_id, is_failure=True)168    sent, reason = _send_agent_email(169        recipient_email=recipient,170        subject=f"[FAILED] Report generation failed for {lan_code}",171        body=(172            "Report generation failed after max retries.\n"173            f"Report ID: {report_id}\n"174            f"LAN: {lan_code}\n"175            f"Retries: {max_retries}\n"176            "Please check and resolve the issue.\n"177        ),178    )179    email_status = "failure_accepted_by_smtp" if sent else f"failure_failed:{reason}"180    db.update_email_status(report_id, sent=sent, status=email_status)181    final_state = db.get_live_track(report_id)182    return {183        "report_id": report_id,184        "status": "failed",185        "attempts": max_retries,186        "email_recipient": recipient,187        "email_sent": sent,188        "email_status": email_status,189        "last_error_code": final_state["last_error_code"] if final_state else None,190    }191