Anthropic OAuth LLM, entitlements (feature 1), renewals, + 7 enhancements
- 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>
This commit is contained in:
@@ -17,6 +17,7 @@ import sys
|
||||
from src import config, pipeline
|
||||
from src.connectors import crm, exa
|
||||
from src.delivery import digest as digest_mod
|
||||
from src.delivery import whitespace as whitespace_mod
|
||||
from src.memory import store, feedback
|
||||
|
||||
|
||||
@@ -25,13 +26,57 @@ def cmd_loop(args):
|
||||
config.EXA_DOMAIN_FILTER = False # per-run override
|
||||
if args.domain_filter:
|
||||
config.EXA_DOMAIN_FILTER = True
|
||||
pipeline.run_all(tier=args.tier, account_id=args.account)
|
||||
pipeline.run_all(tier=args.tier, account_id=args.account,
|
||||
limit=args.limit, rep=args.rep)
|
||||
print("\n--- digest preview ---")
|
||||
print(digest_mod.write_digest())
|
||||
|
||||
|
||||
def cmd_digest(args):
|
||||
print(digest_mod.write_digest(rep_id=args.rep))
|
||||
if args.html:
|
||||
path = digest_mod.write_digest_html(rep=args.rep)
|
||||
print(f"HTML digest written: {path}")
|
||||
else:
|
||||
print(digest_mod.write_digest(rep_id=args.rep))
|
||||
|
||||
|
||||
def cmd_whitespace(args):
|
||||
print(whitespace_mod.write_report())
|
||||
print(f"(also written to {config.OUTPUT_DIR / 'whitespace.md'})")
|
||||
|
||||
|
||||
def cmd_renewals(args):
|
||||
"""Upcoming renewals across the territory (local entitlement data)."""
|
||||
import json as _json
|
||||
from collections import defaultdict
|
||||
from datetime import datetime, timezone
|
||||
p = config.DATA_DIR / "entitlements.json"
|
||||
if not p.exists():
|
||||
print("No entitlement data. Run: python3 scripts/import_entitlements.py <ACL.csv>")
|
||||
return
|
||||
ent = _json.loads(p.read_text())
|
||||
now = datetime.now(timezone.utc)
|
||||
horizon = args.months * 31
|
||||
by_month = defaultdict(list)
|
||||
for aid, e in ent.items():
|
||||
for prod in e["products"]:
|
||||
d = prod.get("nextRenewal")
|
||||
if not d:
|
||||
continue
|
||||
days = (datetime.strptime(d, "%Y-%m-%d").replace(tzinfo=timezone.utc) - now).days
|
||||
if 0 <= days <= horizon:
|
||||
by_month[d[:7]].append((d, e["accountName"], prod["product"], prod["lines"]))
|
||||
print(f"💰 UPCOMING RENEWALS — next {args.months} month(s)\n")
|
||||
if not by_month:
|
||||
print(" none in window")
|
||||
for month in sorted(by_month):
|
||||
items = sorted(by_month[month])
|
||||
print(f"── {month} ({len(items)} renewal line-group(s)) " + "─" * 20)
|
||||
for d, name, prod, lines in items[:args.limit]:
|
||||
print(f" {d} {name[:32]:32} {prod[:58]} ({lines} lines)")
|
||||
if len(items) > args.limit:
|
||||
print(f" ... and {len(items) - args.limit} more (use --limit to widen)")
|
||||
print()
|
||||
|
||||
|
||||
def cmd_feedback(args):
|
||||
@@ -52,8 +97,10 @@ def cmd_recalibrate(args):
|
||||
|
||||
def cmd_accounts(args):
|
||||
for a in crm.load_accounts():
|
||||
prods = a.get("currentProducts") or []
|
||||
has = f"{len(prods)} products, {len(a.get('ownedCapabilityIds') or [])} caps" if prods else "-"
|
||||
print(f" {a['accountId']:18} {a['accountTier']:10} {a['accountName']:28} "
|
||||
f"rep={a['assignedRep']['name']} has={a.get('currentProducts') or '[]'}")
|
||||
f"rep={a['assignedRep']['name']} owns: {has}")
|
||||
|
||||
|
||||
def cmd_briefs(args):
|
||||
@@ -86,6 +133,10 @@ def main(argv=None):
|
||||
p_loop = sub.add_parser("loop", help="Run the harvest->deliver loop")
|
||||
p_loop.add_argument("--account", help="single account id")
|
||||
p_loop.add_argument("--tier", choices=["strategic", "enterprise", "growth"])
|
||||
p_loop.add_argument("--rep", help="only accounts assigned to this rep (name or id substring)")
|
||||
p_loop.add_argument("--limit", type=int,
|
||||
help="process at most N accounts (strategic + least-recently-run first); "
|
||||
"successive runs resume where the last left off")
|
||||
p_loop.add_argument("--no-domain-filter", action="store_true",
|
||||
help="broad-web news about the account (default behavior)")
|
||||
p_loop.add_argument("--domain-filter", action="store_true",
|
||||
@@ -93,9 +144,20 @@ def main(argv=None):
|
||||
p_loop.set_defaults(func=cmd_loop)
|
||||
|
||||
p_dig = sub.add_parser("digest", help="Print the daily intelligence digest")
|
||||
p_dig.add_argument("--rep", help="filter to a rep id")
|
||||
p_dig.add_argument("--rep", help="filter to a rep (name substring or rep id)")
|
||||
p_dig.add_argument("--html", action="store_true",
|
||||
help="write a forwardable HTML digest to output/digests/")
|
||||
p_dig.set_defaults(func=cmd_digest)
|
||||
|
||||
sub.add_parser("whitespace",
|
||||
help="Territory coverage report: opportunities, gaps, never-scanned accounts"
|
||||
).set_defaults(func=cmd_whitespace)
|
||||
|
||||
p_ren = sub.add_parser("renewals", help="Upcoming renewals by month (local entitlement data)")
|
||||
p_ren.add_argument("--months", type=int, default=6, help="horizon in months (default 6)")
|
||||
p_ren.add_argument("--limit", type=int, default=15, help="max rows shown per month (default 15)")
|
||||
p_ren.set_defaults(func=cmd_renewals)
|
||||
|
||||
p_fb = sub.add_parser("feedback", help="Record rep feedback on a brief")
|
||||
p_fb.add_argument("--brief", required=True)
|
||||
p_fb.add_argument("--action", required=True,
|
||||
|
||||
Reference in New Issue
Block a user