CheddahBot/cheddahbot/delivery.py

131 lines
4.5 KiB
Python

"""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