"""Client delivery pipeline: Google Drive upload + Gmail draft creation.""" from __future__ import annotations import logging from dataclasses import dataclass, field from pathlib import Path from .contacts import CompanyContact, lookup_contact, parse_company_directory from .email_templates import load_template, render_template log = logging.getLogger(__name__) @dataclass class DeliveryResult: """Result of a client delivery attempt.""" doc_links: list[str] = field(default_factory=list) draft_id: str = "" errors: list[str] = field(default_factory=list) @property def success(self) -> bool: return bool(self.draft_id) and not self.errors def deliver_to_client( files: list[Path], company_name: str, task_id: str, task_type: str, ctx: dict | None = None, ) -> DeliveryResult: """Upload files to Google Drive and create a Gmail draft for the client. Steps: 1. Look up client contact (email, opening, cc) from companies.md 2. Upload each .docx to Google Drive as a Google Doc 3. Load and render the email template for this task type 4. Create a Gmail draft with the rendered content This function is designed to be non-fatal -- callers should wrap in try/except so delivery failures don't break the main pipeline. """ result = DeliveryResult() config = ctx.get("config") if ctx else None # 1. Look up client contact contacts = parse_company_directory() contact = lookup_contact(company_name, contacts) if not contact: result.errors.append("No contact found for company '%s'" % company_name) log.warning("Delivery aborted: %s", result.errors[-1]) return result if not contact.email: result.errors.append( "No email address for '%s' -- add Email field to skills/companies.md" % contact.name ) log.warning("Delivery aborted: %s", result.errors[-1]) return result # 2. Upload to Google Drive drive_enabled = config and config.google_drive.enabled if config else False if drive_enabled: try: from .google_drive import DriveClient drive = DriveClient(root_folder_id=config.google_drive.root_folder_id) folder_id = drive.ensure_client_folder(contact.name) for file_path in files: if not file_path.exists(): result.errors.append("File not found: %s" % file_path) continue file_id, web_link = drive.upload_as_google_doc(file_path, folder_id) drive.set_link_sharing(file_id, role="commenter") result.doc_links.append(web_link) except Exception as e: result.errors.append("Drive upload failed: %s" % e) log.error("Drive upload failed: %s", e) else: log.info("Google Drive disabled, skipping upload") # Fall back to listing local file paths for file_path in files: result.doc_links.append(str(file_path)) # 3. Load and render email template template = load_template(task_type) if not template: result.errors.append("No email template for task type '%s'" % task_type) log.warning("Delivery aborted: %s", result.errors[-1]) return result # Build Google Doc links as a bullet list links_text = "\n".join("- %s" % link for link in result.doc_links) # Use the contact's custom opening, or fall back to "Hi {first_name}," opening = contact.opening or ("Hi %s," % contact.executive_first_name) context = { "company_name": contact.name, "opening": opening, "executive_first_name": contact.executive_first_name, "google_doc_links": links_text, "task_id": task_id, } subject, body = render_template(template, context) # 4. Create Gmail draft gmail_enabled = config and config.gmail_api.enabled if config else False if gmail_enabled: try: from .gmail_draft import GmailDraftClient gmail = GmailDraftClient() result.draft_id = gmail.create_draft( to=contact.email, subject=subject, body=body, cc=contact.cc_list or None, ) except Exception as e: result.errors.append("Gmail draft creation failed: %s" % e) log.error("Gmail draft creation failed: %s", e) else: log.info("Gmail API disabled, skipping draft creation") result.errors.append("Gmail API disabled -- draft not created") return result