- LLM: Anthropic Sonnet 5 (effort=low) via OAuth Bearer (Claude Code keychain token, expiry-aware); NVIDIA/LM Studio endpoints kept commented for switch-back. - Entitlements: scripts/import_entitlements.py joins ACL export to accounts by normalized parent name (324/342 matched; ID column Excel-corrupted), maps products->capabilities via data/product_capability_map.json. Raw ACL csv and entitlements.json are gitignored (confidential); accounts.json carries derived currentProducts + ownedCapabilityIds. Owned capabilities tag briefs EXPANSION. - Renewals: local renewal-proximity score boost (window 365d, max +0.10) + 'renewals' report; briefs/digests show RENEWAL WINDOW context. - Batch runs: loop --limit/--rep with persistent cursor (strategic + stale-first). - Digests: per-rep HTML export; timestamped digest_<date>_<time>.md kept forever. - Account narrative memory fed into relevance/synthesis; synthesis maintains it. - Signal quality gate drops PR fluff pre-LLM; SEC EDGAR filings connector. - Citation verification: talking points must cite [S#] signals or are dropped. - Brief filenames use account-name slug; whitespace report + installed-base view. - USAGE.md: full command/flag/env reference. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
124 lines
4.7 KiB
Python
124 lines
4.7 KiB
Python
#!/usr/bin/env python3
|
|
"""Build data/accounts.json from the ISG territory CSV export.
|
|
|
|
Mapping (decided with the rep):
|
|
accountId <- OEC Account Number (canonical; ties to entitlements)
|
|
accountName <- Customer Company Name
|
|
domain <- Website host (fallback: Authorized Email Domain)
|
|
industry <- Licensee Industry (fallback: Parent Account Industry)
|
|
accountTier <- Classification: Strategic->strategic, Corporate->enterprise
|
|
assignedRep <- Owner; email synthesized first.last@broadcom.com
|
|
targetContacts <- Primary Contact / Contact Email / Contact Phone (if present)
|
|
reference <- parent + account IDs and CRM metadata for entitlement matching
|
|
|
|
Usage: python3 scripts/import_accounts.py /path/to/territory.csv
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import csv
|
|
import json
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
OUT = ROOT / "data" / "accounts.json"
|
|
|
|
TIER_MAP = {"strategic": "strategic", "corporate": "enterprise"}
|
|
EMAIL_DOMAIN = "broadcom.com"
|
|
|
|
|
|
def clean_domain(website: str, email_domain: str) -> str:
|
|
w = (website or "").strip()
|
|
if w:
|
|
w = re.sub(r"^https?://", "", w, flags=re.I)
|
|
w = re.sub(r"^www\.", "", w, flags=re.I)
|
|
w = w.split("/")[0].strip().lower()
|
|
if "." in w:
|
|
return w
|
|
return (email_domain or "").strip().lower()
|
|
|
|
|
|
def rep_identity(owner: str) -> dict:
|
|
name = (owner or "").strip()
|
|
if not name:
|
|
return {"id": "rep_unassigned", "name": "(unassigned)", "email": ""}
|
|
tokens = [re.sub(r"[^a-z0-9]", "", t.lower()) for t in name.split()]
|
|
tokens = [t for t in tokens if t]
|
|
slug = "_".join(tokens)
|
|
# first.last for the email local-part (collapse middle tokens out)
|
|
local = f"{tokens[0]}.{tokens[-1]}" if len(tokens) >= 2 else tokens[0]
|
|
return {"id": f"rep_{slug}", "name": name, "email": f"{local}@{EMAIL_DOMAIN}"}
|
|
|
|
|
|
def target_contacts(row: dict) -> list[dict]:
|
|
name = (row.get("Primary Contact") or "").strip()
|
|
email = (row.get("Contact Email") or "").strip()
|
|
phone = (row.get("Contact Phone") or "").strip()
|
|
if not (name or email):
|
|
return []
|
|
return [{
|
|
"name": name or "(primary contact)",
|
|
"email": email,
|
|
"phone": phone,
|
|
"inCrm": True,
|
|
}]
|
|
|
|
|
|
def main(csv_path: str) -> None:
|
|
rows = list(csv.DictReader(open(csv_path, newline="", encoding="utf-8-sig")))
|
|
accounts = []
|
|
for r in rows:
|
|
g = lambda k: (r.get(k) or "").strip()
|
|
classification = g("Classification")
|
|
accounts.append({
|
|
"accountId": g("OEC Account Number"),
|
|
"accountName": g("Customer Company Name"),
|
|
"domain": clean_domain(g("Website"), g("Authorized Email Domain")),
|
|
"industry": g("Licensee Industry") or g("Parent Account Industry"),
|
|
"accountTier": TIER_MAP.get(classification.lower(), "enterprise"),
|
|
"crmClassification": classification,
|
|
"subClass": g("Sub Class"),
|
|
"assignedRep": rep_identity(g("Owner")),
|
|
"currentProducts": [],
|
|
"targetContacts": target_contacts(r),
|
|
"reference": {
|
|
"oecAccountNumber": g("OEC Account Number"),
|
|
"erpAccountNumber": g("Erp Account Number"),
|
|
"parentId": g("Parent ID"),
|
|
"parentName": g("Rpt Parent Name") or g("Parent Account"),
|
|
"globalParentId": g("Global Parent ID"),
|
|
"globalParentName": g("Global Parent Name"),
|
|
"classification": classification,
|
|
"subClass": g("Sub Class"),
|
|
"salesRegion": g("Sales Region"),
|
|
"salesArea": g("Sales Area"),
|
|
"salesTerritory": g("Sales Territory"),
|
|
"renewalOwner": g("Renewal Owner"),
|
|
"country": g("Country"),
|
|
"state": g("State"),
|
|
"website": g("Website"),
|
|
"emailDomain": g("Authorized Email Domain"),
|
|
},
|
|
"lastRunAt": None,
|
|
})
|
|
|
|
doc = {
|
|
"_note": (
|
|
"Imported from ISG Customer Sales Assignments territory CSV "
|
|
"(scripts/import_accounts.py). accountId = OEC Account Number. "
|
|
"Tier: Classification Strategic->strategic, Corporate->enterprise "
|
|
"(verbatim in crmClassification/subClass). Rep email synthesized "
|
|
"first.last@broadcom.com (UNVERIFIED). reference.parentId is the "
|
|
"Parent Account ID for future product-entitlement matching. "
|
|
"currentProducts empty until entitlement data is loaded."
|
|
),
|
|
"accounts": accounts,
|
|
}
|
|
OUT.write_text(json.dumps(doc, indent=2))
|
|
print(f"wrote {len(accounts)} accounts -> {OUT}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main(sys.argv[1])
|