#!/usr/bin/env python3 """Customer draft-email builder for the quote-to-proposal workflow. Creates a Gmail DRAFT (never sends) to the customer with the proposal PDF attached and the interactive proposal attached (phase 1) or linked via a live per-customer URL (phase 2, once the Cloudflare connector is live). Customer-facing rules enforced here: - zero em-dashes / en-dashes anywhere in generated copy - title "Account Relationship Manager" (carried by the signature) - customer never sees costs, margins, or lease factors - Sean's own addresses never on To/Cc (draft goes only to the customer) """ import os, re, subprocess, html as htmlmod BASE = os.path.dirname(os.path.abspath(__file__)) ACCOUNT = "d48c9e2643f643aeb3c4dacf347245b8" # sean@seanmattsson.com SIGNATURE_PATH = "/home/hatch/workspace/goals/sales-weapon-ae-copilot/hidden_files/signature.html" DASHES = re.compile(r"[\u2013\u2014]") def _clean(s): """Strip em/en dashes from generated copy (safety net).""" return DASHES.sub(",", str(s or "")) def _first_name(contact_name): return (contact_name or "").strip().split()[0] if contact_name else "there" def _money(v): return "$%s" % format(float(v), ",.2f") def load_signature(): try: return open(SIGNATURE_PATH).read() except Exception: return ("

Best regards,
Sean Mattsson
Account Relationship Manager
" "Stratix Systems
Direct (484) 214-3895

") def _reason_line(raw): reason = (raw.get("reason") or "").strip() if not reason or reason.lower() in ("not sure yet",): return "Based on what you shared, I have prepared your proposal." return "Based on what you shared about %s, I have prepared your proposal." % _clean(reason[0].lower() + reason[1:]) def _timing_line(raw): timing = (raw.get("timing") or "").lower() if "as soon as possible" in timing or "within 30 days" in timing: return "I know timing is tight, so I turned this around same day." if "1" in timing and "3 months" in timing: return "I know you are deciding in the next few months, so this gives us a head start." return "" def _interest_line(raw): interests = (raw.get("interests") or "").strip() if not interests: return "" bits = [b.strip() for b in re.split(r"[,;]|\n", interests) if b.strip()] bits = [_clean(b) for b in bits[:2]] if not bits: return "" return ("When we talk, I also want to show you what we can do around %s. " "Most offices add at least one of these once they see it." % " and ".join(bits)) def build_customer_draft(priced, live_url=None): """Return (subject, html_body) for the customer proposal draft.""" cust = priced.get("customer", {}) raw = priced.get("intake", {}).get("raw", {}) if isinstance(priced.get("intake"), dict) else {} company = cust.get("company") or "your office" first = _first_name(cust.get("contact_name")) total = _money(priced["total_monthly"]) term = priced.get("term_months", 63) valid = priced.get("valid_until", "") paras = [] paras.append("

Hi %s,

" % htmlmod.escape(first)) paras.append("

Thanks for telling us about %s. %s

" % ( htmlmod.escape(company), _reason_line(raw))) timing = _timing_line(raw) if timing: paras.append("

%s

" % timing) paras.append("

The recommended configuration comes to %s/month on a %d-month plan, " "including the equipment, service, and supplies. Pricing is locked through %s.

" % ( total, term, htmlmod.escape(valid))) if live_url: paras.append("

Your interactive proposal is ready here:
" '%s

' "

Open it and you can compare systems, adjust terms and options, " "and watch the monthly total update live. When you land on a configuration " "you like, hit \"Send Me This Configuration\" and it comes straight to me.

" % ( htmlmod.escape(live_url), htmlmod.escape(live_url))) else: paras.append("

I have also attached an interactive version of the proposal. " "Save it and open it in any browser: you can compare systems, adjust terms " "and options, and watch the monthly total update live. When you land on a " "configuration you like, hit \"Send Me This Configuration\" and it comes " "straight to me.

") paras.append("

The formal proposal is attached as a PDF as well.

") interest = _interest_line(raw) if interest: paras.append("

%s

" % interest) paras.append("

I will give you a call in the next day or two to walk through it. " "If you would rather pick a time, the booking link is below.

") paras.append("

Best regards,

") paras.append(load_signature()) body = "\n".join(paras) assert not DASHES.search(body), "em/en dash leaked into customer draft" subject = "Your Stratix proposal is ready, %s" % first if first != "there" else \ "Your Stratix proposal is ready" return subject, body def build_gap_draft(priced): """'Quick question' draft for when pricing has gaps (needs_review).""" cust = priced.get("customer", {}) raw = priced.get("intake", {}).get("raw", {}) if isinstance(priced.get("intake"), dict) else {} first = _first_name(cust.get("contact_name")) gaps = priced.get("gaps", []) # translate internal gaps into plain customer questions questions = [] for g in gaps: gl = g.lower() if "volume" in gl: questions.append("about how many black and white and color pages you print in a typical month") elif "model" in gl or "accessory" in gl or "cost" in gl: questions.append("which specific model and options you had in mind") else: questions.append("a couple of details on your current setup") ask = "; ".join(dict.fromkeys(questions)) or "a couple of details on your current setup" body = ( "

Hi %s,

" "

Thanks for your request. I am putting your proposal together now and I need %s " "to price it accurately.

" "

Could you reply with your best estimates? Even rough numbers are fine.

" "

I will have your proposal over within a day of hearing back.

" "

Best regards,

" "%s" % (htmlmod.escape(first), htmlmod.escape(ask), load_signature())) assert not DASHES.search(body), "em/en dash leaked into gap draft" return "Quick question on your Stratix request", body def create_draft(to_email, subject, html_body, attachments): """Create a Gmail draft via hatch_gws_cli. Returns the draft id or None.""" if not to_email or "@" not in to_email: return None cmd = ["hatch_gws_cli", "gmail", "--account", ACCOUNT, "+send", "--to", to_email, "--subject", subject, "--body", html_body, "--html", "--draft"] for a in attachments or []: if a and os.path.exists(a): cmd += ["-a", a] r = subprocess.run(cmd, capture_output=True, text=True, timeout=180) try: import json as _json data = _json.loads(r.stdout) return data.get("id") or data.get("draft", {}).get("id") except Exception: m = re.search(r'"id"\s*:\s*"([^"]+)"', r.stdout) return m.group(1) if m else None