stubs.py
Language Python7.6 KB190 lines
"""Client stubs: index, create, upgrade, and mirror pipeline blocks."""from __future__ import annotationsimport jsonimport refrom pathlib import Pathimport yamlfrom .config import NON_PARTNER, guard_or_exitfrom .history import to_floatfrom .paths import CLIENTS_DIR, TODAYdef safe_stub_name(first, last, email="") -> str:"""Filesystem-safe stub filename. Names come from Plinth order fields and cancontain slashes or shipping-label junk that would otherwise break the path."""raw = f"{(first or '').strip()} {(last or '').strip()}".strip() or (email or "").strip()safe = re.sub(r'[/\\:*?"<>|\n\r\t]+', '-', raw) # kill path/illegal charssafe = re.sub(r'\s+', ' ', safe).strip(' .-') # tidy whitespace and edgesreturn safe[:80].strip(' .-') or "unknown"def _read_frontmatter(path: Path) -> dict:try:text = path.read_text(encoding="utf-8")except Exception:return {}m = re.match(r"^---\n(.*?)\n---", text, re.DOTALL)if not m:return {}try:return yaml.safe_load(m.group(1)) or {}except yaml.YAMLError:return {}def index_existing_stubs() -> dict:by_email, by_name = {}, {}if not CLIENTS_DIR.exists():return {"by_email": by_email, "by_name": by_name}for p in CLIENTS_DIR.glob("*.md"):fm = _read_frontmatter(p)if not fm:continuerec = {"path": p, "fm": fm}email = (str(fm.get("email") or "")).strip().lower()if email:by_email[email] = recfn = (str(fm.get("first_name") or "")).strip().lower()ln = (str(fm.get("last_name") or "")).strip().lower()if fn or ln:by_name[f"{fn} {ln}".strip()] = recreturn {"by_email": by_email, "by_name": by_name}def stub_status(c: dict, idx: dict) -> tuple[str, Path | None]:email = c["email"]name = f"{c['first_name']} {c['last_name']}".strip().lower()rec = idx["by_email"].get(email) or idx["by_name"].get(name)if not rec:return "new", Nonefm = rec["fm"]changed = (to_float(fm.get("ltv")) != c["ltv"]or int(fm.get("total_orders_count") or 0) != c["total_orders_count"])return ("exists-changed" if changed else "exists"), rec["path"]def cmd_write_stubs(args):guard_or_exit()plan = json.loads(Path(args.plan).read_text(encoding="utf-8"))idx = index_existing_stubs()created, updated, flagged = [], [], []for r in plan["rows"]:if r["assigned_tier"] in NON_PARTNER:continueemail = r["email"]name = f"{(r.get('first_name') or '')} {(r.get('last_name') or '')}".strip()rec = idx["by_email"].get(email) or idx["by_name"].get(name.lower())if rec:_upgrade_stub(rec["path"], r)updated.append(rec["path"].name)else:path = _create_stub(r)created.append(path.name)if re.search(r'[/\\:]|c/o|booth|attn', name, re.IGNORECASE):flagged.append(f"{name} -> {path.name}")print(f"Stubs created: {len(created)}; upgraded: {len(updated)}")if flagged:print(f"\nFLAGGED: {len(flagged)} stub(s) created from messy Plinth name fields ""(filename sanitized; review and rename or delete if not a real client):")for f in flagged:print(" ", f)print("REMINDER: append one LEDGER entry for this change-set (attribution by session email).")def _create_stub(r: dict) -> Path:display = f"{(r.get('first_name') or '')} {(r.get('last_name') or '')}".strip() or r["email"]fname = safe_stub_name(r.get("first_name"), r.get("last_name"), r["email"])path = CLIENTS_DIR / f"{fname}.md"if path.exists(): # don't clobber an existing same-name stubpath = CLIENTS_DIR / f"{fname} ({r.get('user_id', '')}).md"fm = {"type": "client","bitrix_id": "","first_name": r["first_name"],"last_name": r["last_name"],"email": r["email"],"user_group": "","user_group_correct": r["assigned_tier"],"industry": "","company": r.get("company", ""),"phone": r.get("phone", ""),"ltv": f"${r['ltv']:,.2f}","repeat_revenue": f"${r['repeat_revenue']:,.2f}","title": "","company_website": "","total_orders_count": r["total_orders_count"],"last_order_date": r["last_order_date"],"source": "weekly_partner_pipeline","last_modified": TODAY,"tags": ["client", "partner-pipeline"],}body = (f"\n# {display}\n\n## Snapshot\n| Field | Value |\n|-------|-------|\n"f"| Title | [TBD] |\n| Company | {r.get('company','')} |\n"f"| Email | {r['email']} |\n| Phone | {r.get('phone','')} |\n\n""## Order History\n"f"- Total orders: {r['total_orders_count']}\n"f"- Lifetime value: ${r['ltv']:,.2f}\n"f"- Repeat revenue: ${r['repeat_revenue']:,.2f}\n"f"- Last order: {r['last_order_date']}\n\n""## Research\n<!-- Enriched via client-enrich. Never overwrite Human Notes. -->\n""### Company\n[TBD]\n### Person / LinkedIn\n[TBD]\n### Recent News & Signals\n[TBD]\n### Sources\n\n""## Human Notes\n<!-- Matt's space. Automation never writes here. -->\n\n""## Outreach Log\n| Date | Pipeline | Channel | Template | Outcome | Next step |\n""|------|----------|---------|----------|---------|-----------|\n")path.write_text("---\n" + yaml.safe_dump(fm, sort_keys=False, allow_unicode=True) + "---\n" + body,encoding="utf-8")return pathdef _upgrade_stub(path: Path, r: dict):"""Update only computed frontmatter + last_modified. Never touch Human Notes/Research."""text = path.read_text(encoding="utf-8")m = re.match(r"^---\n(.*?)\n---(.*)$", text, re.DOTALL)if not m:returnfm = yaml.safe_load(m.group(1)) or {}fm["ltv"] = f"${r['ltv']:,.2f}"fm["repeat_revenue"] = f"${r['repeat_revenue']:,.2f}"fm["total_orders_count"] = r["total_orders_count"]fm["last_order_date"] = r["last_order_date"]fm["user_group_correct"] = r["assigned_tier"]fm["last_modified"] = TODAYpath.write_text("---\n" + yaml.safe_dump(fm, sort_keys=False, allow_unicode=True) + "---" + m.group(2),encoding="utf-8")def mirror_stub_pipeline(stub_path: Path, key: str, stage: str, status: str, entered: str):"""Copy a census pipeline block into the stub's `outreach_pipelines` frontmatter.Returns True when the stub changed. A failure warns and never stops the run."""try:txt = stub_path.read_text(encoding="utf-8")m = re.match(r"^---\n(.*?\n)---(.*)$", txt, re.DOTALL)if not m:return Falsefm = yaml.safe_load(m.group(1)) or {}op = fm.get("outreach_pipelines") or {}e = op.get(key) or {}if e.get("stage") == stage and e.get("status") == status:return Falsee.update({"stage": stage, "status": status})if not e.get("entered"):e["entered"] = entered or Nonefor f in ("last_touch", "next_step", "next_date"):e.setdefault(f, None)op[key] = efm["outreach_pipelines"] = opfm["last_modified"] = TODAYstub_path.write_text("---\n" + yaml.safe_dump(fm, sort_keys=False,allow_unicode=True) + "---" + m.group(2), encoding="utf-8")return Trueexcept Exception as ex:print(f"WARNING: stub mirror failed for {stub_path.name}: {ex}")return False