/ sales-playbook / value_pricing_briefing.py
value_pricing_briefing.py
1 #!/usr/bin/env python3 2 """ 3 Value-Based Pricing: Pre-Call Briefing Generator 4 5 Takes a prospect's domain + competitor domains and outputs everything a 6 salesperson needs to anchor the conversation on value, not cost. 7 8 Usage: 9 python3 value_pricing_briefing.py --domain acme.com --competitors "comp1.com,comp2.com" 10 python3 value_pricing_briefing.py --domain acme.com --competitors "comp1.com" --industry saas --deal-target 80000 11 python3 value_pricing_briefing.py --domain acme.com --competitors "comp1.com" --format json 12 """ 13 14 import argparse 15 import json 16 import os 17 import random 18 import sys 19 from datetime import datetime 20 21 # --------------------------------------------------------------------------- 22 # API Stubs — Replace with real Ahrefs/SEMrush API calls when keys are available 23 # --------------------------------------------------------------------------- 24 # To integrate real APIs: 25 # 1. Set AHREFS_API_KEY or SEMRUSH_API_KEY environment variables 26 # 2. Replace the stub functions below with actual API calls 27 # 3. Ahrefs API docs: https://ahrefs.com/api 28 # 4. SEMrush API docs: https://developer.semrush.com/api/ 29 30 31 def _fetch_domain_metrics(domain: str) -> dict: 32 """ 33 Stub: Fetch domain authority, backlinks, and traffic estimates. 34 35 In production, call: 36 GET https://api.ahrefs.com/v3/site-explorer/domain-rating 37 GET https://api.semrush.com/?type=domain_ranks&key=KEY&domain=DOMAIN 38 """ 39 api_key = os.environ.get("AHREFS_API_KEY") or os.environ.get("SEMRUSH_API_KEY") 40 if api_key: 41 # TODO: Implement real API call here 42 # import requests 43 # resp = requests.get(f"https://api.ahrefs.com/v3/...", headers={"Authorization": f"Bearer {api_key}"}) 44 # return resp.json() 45 pass 46 47 # Stub data — deterministic seed from domain for consistency 48 seed = sum(ord(c) for c in domain) 49 rng = random.Random(seed) 50 return { 51 "domain": domain, 52 "domain_authority": rng.randint(15, 85), 53 "monthly_organic_traffic": rng.randint(5000, 500000), 54 "total_keywords": rng.randint(500, 50000), 55 "total_backlinks": rng.randint(1000, 200000), 56 "top_keywords": [ 57 {"keyword": f"{domain.split('.')[0]} solutions", "position": rng.randint(1, 20), "volume": rng.randint(500, 10000), "cpc": round(rng.uniform(2.0, 25.0), 2)}, 58 {"keyword": f"best {domain.split('.')[0]} alternative", "position": rng.randint(5, 50), "volume": rng.randint(200, 5000), "cpc": round(rng.uniform(3.0, 30.0), 2)}, 59 {"keyword": f"{domain.split('.')[0]} pricing", "position": rng.randint(1, 30), "volume": rng.randint(300, 8000), "cpc": round(rng.uniform(5.0, 40.0), 2)}, 60 {"keyword": f"{domain.split('.')[0]} vs competitor", "position": rng.randint(3, 40), "volume": rng.randint(100, 3000), "cpc": round(rng.uniform(4.0, 35.0), 2)}, 61 {"keyword": f"{domain.split('.')[0]} reviews", "position": rng.randint(2, 25), "volume": rng.randint(400, 6000), "cpc": round(rng.uniform(1.5, 15.0), 2)}, 62 ], 63 } 64 65 66 def _estimate_traffic_value(volume: int, current_pos: int, target_pos: int, cpc: float) -> dict: 67 """Estimate additional traffic and value from ranking improvement.""" 68 # CTR curves by position (approximate) 69 ctr_by_pos = {1: 0.32, 2: 0.24, 3: 0.18, 4: 0.13, 5: 0.10, 6: 0.07, 7: 0.05, 8: 0.04, 9: 0.03, 10: 0.025} 70 current_ctr = ctr_by_pos.get(current_pos, max(0.005, 0.025 - (current_pos - 10) * 0.001)) 71 target_ctr = ctr_by_pos.get(target_pos, max(0.005, 0.025 - (target_pos - 10) * 0.001)) 72 current_traffic = int(volume * current_ctr) 73 target_traffic = int(volume * target_ctr) 74 additional_traffic = max(0, target_traffic - current_traffic) 75 monthly_value = round(additional_traffic * cpc, 2) 76 return { 77 "current_position": current_pos, 78 "target_position": target_pos, 79 "search_volume": volume, 80 "current_monthly_traffic": current_traffic, 81 "projected_monthly_traffic": target_traffic, 82 "additional_monthly_traffic": additional_traffic, 83 "cpc": cpc, 84 "monthly_paid_equivalent": monthly_value, 85 "annual_paid_equivalent": round(monthly_value * 12, 2), 86 } 87 88 89 # --------------------------------------------------------------------------- 90 # Briefing Generation 91 # --------------------------------------------------------------------------- 92 93 CONVERSATION_HOOKS = [ 94 "What's your current strategy for [top_keyword]? I noticed some interesting gaps there.", 95 "When you look at your competitive landscape for organic search, what concerns you most?", 96 "If you could rank #1 for one keyword tomorrow, which would move the revenue needle most?", 97 "How much are you currently spending on paid search for terms you could be ranking organically?", 98 "What would it mean for your pipeline if you captured even 20% of the traffic your top competitor gets?", 99 "I pulled some data before our call. Mind if I share what I found about your organic presence vs. [competitor]?", 100 "Your competitor just passed you on [keyword]. Have you noticed the traffic shift?", 101 "What does your content production look like right now? I want to understand where the bottleneck is.", 102 ] 103 104 OBJECTION_RESPONSES = { 105 "price_too_high": { 106 "objection": "That's more than we were expecting to spend.", 107 "response": "I hear you. Let me reframe: your competitor is capturing $X/mo in organic traffic value that you're not. The question isn't whether $Y/mo is expensive; it's whether leaving $X/mo on the table is more expensive. Let me show you the math.", 108 "key_principle": "Redirect from cost to cost-of-inaction", 109 }, 110 "need_to_think": { 111 "objection": "We need to think about it / discuss internally.", 112 "response": "Absolutely. To help that internal conversation, I'll send over the competitive analysis we reviewed. One thing worth noting: [competitor] is actively investing in the keywords we discussed. Every month of deliberation is a month they're building a wider moat.", 113 "key_principle": "Create urgency with competitor data, not pressure", 114 }, 115 "already_doing_seo": { 116 "objection": "We already have someone doing SEO.", 117 "response": "Great. What are they focused on? The reason I ask is that the data shows [specific gap]. If that's being addressed, fantastic. If not, it might be worth a conversation about where the current strategy is leaving value on the table.", 118 "key_principle": "Don't attack their current vendor; question the results", 119 }, 120 "can_do_internally": { 121 "objection": "We think we can handle this in-house.", 122 "response": "In-house is great when you have the bandwidth. The gap we identified represents [X keywords / $Y in traffic value]. To close it, you'd need roughly [estimate] hours/month of specialized work. Hiring for that takes 3-6 months. We can bridge the gap while you build the team, or complement what you have.", 123 "key_principle": "Bridge offer + acknowledge their capability", 124 }, 125 "show_me_results": { 126 "objection": "Can you show me results from similar companies?", 127 "response": "Absolutely. We have a client in a similar space who went from ranking for ~Z keywords to over [Z*3] in 8 months. Their organic traffic value went from $A/mo to $B/mo. Happy to connect you with them if that would help.", 128 "key_principle": "Reference customer drop with specific (anonymized) numbers", 129 }, 130 "budget_locked": { 131 "objection": "Our budget is already allocated for this quarter.", 132 "response": "Understood. Two thoughts: first, our baseline tier starts at [$X], which might fit within discretionary budget. Second, if we start with a focused engagement on your top 3 keyword gaps, I can show ROI within 60 days that makes the case for a larger Q2 investment. Sometimes the best way to unlock budget is to prove the model.", 133 "key_principle": "Offer a smaller entry point tied to provable ROI", 134 }, 135 } 136 137 138 def generate_briefing(domain: str, competitors: list, industry: str = None, deal_target: int = 50000) -> dict: 139 """Generate a complete pre-call briefing.""" 140 # Fetch metrics for all domains 141 prospect_metrics = _fetch_domain_metrics(domain) 142 competitor_metrics = [_fetch_domain_metrics(c) for c in competitors] 143 144 # Build anchor data points 145 anchors = [] 146 for comp in competitor_metrics: 147 keyword_gap = comp["total_keywords"] - prospect_metrics["total_keywords"] 148 traffic_gap = comp["monthly_organic_traffic"] - prospect_metrics["monthly_organic_traffic"] 149 if keyword_gap > 0: 150 anchors.append({ 151 "type": "keyword_gap", 152 "message": f"You rank for {prospect_metrics['total_keywords']:,} keywords. {comp['domain']} ranks for {comp['total_keywords']:,}. Gap: {keyword_gap:,} keywords.", 153 "severity": "high" if keyword_gap > 5000 else "medium", 154 }) 155 if traffic_gap > 0: 156 anchors.append({ 157 "type": "traffic_gap", 158 "message": f"{comp['domain']} gets ~{comp['monthly_organic_traffic']:,} organic visits/mo vs. your ~{prospect_metrics['monthly_organic_traffic']:,}. That's {traffic_gap:,} visits you're leaving on the table.", 159 "severity": "high" if traffic_gap > 50000 else "medium", 160 }) 161 162 # Build competitive triggers 163 triggers = [] 164 for comp in competitor_metrics: 165 for kw in comp["top_keywords"]: 166 # Find matching keyword in prospect 167 for pkw in prospect_metrics["top_keywords"]: 168 if pkw["keyword"].split()[-1] == kw["keyword"].split()[-1]: 169 if kw["position"] < pkw["position"]: 170 triggers.append({ 171 "keyword": kw["keyword"], 172 "competitor": comp["domain"], 173 "competitor_position": kw["position"], 174 "prospect_position": pkw["position"], 175 "message": f"{comp['domain']} is #{kw['position']} for '{kw['keyword']}'. You're #{pkw['position']}.", 176 }) 177 178 # Build value calculations 179 value_calcs = [] 180 for kw in prospect_metrics["top_keywords"]: 181 if kw["position"] > 5: 182 target_pos = min(3, kw["position"]) 183 calc = _estimate_traffic_value(kw["volume"], kw["position"], target_pos, kw["cpc"]) 184 calc["keyword"] = kw["keyword"] 185 if calc["monthly_paid_equivalent"] > 100: 186 value_calcs.append(calc) 187 188 # Sort value calcs by monthly value 189 value_calcs.sort(key=lambda x: x["monthly_paid_equivalent"], reverse=True) 190 191 # Total value opportunity 192 total_monthly_value = sum(v["monthly_paid_equivalent"] for v in value_calcs) 193 total_annual_value = sum(v["annual_paid_equivalent"] for v in value_calcs) 194 195 # Select conversation hooks (pick 4 most relevant) 196 hooks = random.Random(sum(ord(c) for c in domain)).sample(CONVERSATION_HOOKS, min(4, len(CONVERSATION_HOOKS))) 197 # Personalize hooks 198 if competitor_metrics: 199 hooks = [h.replace("[competitor]", competitors[0]) for h in hooks] 200 if value_calcs: 201 hooks = [h.replace("[top_keyword]", value_calcs[0]["keyword"]) for h in hooks] 202 203 # Build objection pre-empts based on deal size 204 relevant_objections = {} 205 if deal_target >= 50000: 206 relevant_objections["price_too_high"] = OBJECTION_RESPONSES["price_too_high"] 207 relevant_objections["need_to_think"] = OBJECTION_RESPONSES["need_to_think"] 208 if deal_target >= 30000: 209 relevant_objections["can_do_internally"] = OBJECTION_RESPONSES["can_do_internally"] 210 relevant_objections["show_me_results"] = OBJECTION_RESPONSES["show_me_results"] 211 relevant_objections["already_doing_seo"] = OBJECTION_RESPONSES["already_doing_seo"] 212 relevant_objections["budget_locked"] = OBJECTION_RESPONSES["budget_locked"] 213 214 # Fill in dynamic values in objection responses 215 for key, obj in relevant_objections.items(): 216 obj = dict(obj) # copy 217 if total_monthly_value > 0: 218 obj["response"] = obj["response"].replace("$X/mo", f"${total_monthly_value:,.0f}/mo") 219 obj["response"] = obj["response"].replace("$Y/mo", f"${deal_target:,}/mo") 220 relevant_objections[key] = obj 221 222 briefing = { 223 "generated_at": datetime.now().isoformat(), 224 "prospect": { 225 "domain": domain, 226 "industry": industry, 227 "deal_target": deal_target, 228 "metrics": prospect_metrics, 229 }, 230 "competitors": [{"domain": c, "metrics": m} for c, m in zip(competitors, competitor_metrics)], 231 "anchor_data_points": anchors, 232 "competitive_triggers": triggers, 233 "value_calculations": value_calcs, 234 "total_opportunity": { 235 "monthly_paid_equivalent": round(total_monthly_value, 2), 236 "annual_paid_equivalent": round(total_annual_value, 2), 237 "roi_multiple": round(total_annual_value / (deal_target * 12), 1) if deal_target > 0 else 0, 238 }, 239 "conversation_hooks": hooks, 240 "objection_preempts": relevant_objections, 241 } 242 243 return briefing 244 245 246 def format_markdown(briefing: dict) -> str: 247 """Format briefing as readable markdown.""" 248 lines = [] 249 p = briefing["prospect"] 250 lines.append(f"# Pre-Call Briefing: {p['domain']}") 251 lines.append(f"*Generated: {briefing['generated_at']}*") 252 lines.append(f"*Target deal: ${p['deal_target']:,}/mo | Industry: {p.get('industry') or 'Not specified'}*") 253 lines.append("") 254 255 # Prospect snapshot 256 m = p["metrics"] 257 lines.append("## Prospect Snapshot") 258 lines.append(f"- **Domain Authority:** {m['domain_authority']}") 259 lines.append(f"- **Monthly Organic Traffic:** {m['monthly_organic_traffic']:,}") 260 lines.append(f"- **Total Keywords:** {m['total_keywords']:,}") 261 lines.append(f"- **Total Backlinks:** {m['total_backlinks']:,}") 262 lines.append("") 263 264 # Competitor comparison 265 if briefing["competitors"]: 266 lines.append("## Competitor Comparison") 267 lines.append("| Metric | " + p["domain"] + " | " + " | ".join(c["domain"] for c in briefing["competitors"]) + " |") 268 lines.append("|--------|" + "--------|" * (1 + len(briefing["competitors"]))) 269 lines.append(f"| DA | {m['domain_authority']} | " + " | ".join(str(c["metrics"]["domain_authority"]) for c in briefing["competitors"]) + " |") 270 lines.append(f"| Traffic | {m['monthly_organic_traffic']:,} | " + " | ".join(f"{c['metrics']['monthly_organic_traffic']:,}" for c in briefing["competitors"]) + " |") 271 lines.append(f"| Keywords | {m['total_keywords']:,} | " + " | ".join(f"{c['metrics']['total_keywords']:,}" for c in briefing["competitors"]) + " |") 272 lines.append("") 273 274 # Anchor data points 275 if briefing["anchor_data_points"]: 276 lines.append("## 🎯 Anchor Data Points") 277 lines.append("*Lead with these. Let the gaps sell the urgency.*") 278 lines.append("") 279 for a in briefing["anchor_data_points"]: 280 icon = "🔴" if a["severity"] == "high" else "🟡" 281 lines.append(f"- {icon} {a['message']}") 282 lines.append("") 283 284 # Competitive triggers 285 if briefing["competitive_triggers"]: 286 lines.append("## ⚡ Competitive Triggers") 287 lines.append("*Use these to activate competitive instinct.*") 288 lines.append("") 289 for t in briefing["competitive_triggers"]: 290 lines.append(f"- **{t['keyword']}**: {t['message']}") 291 lines.append("") 292 293 # Value calculations 294 if briefing["value_calculations"]: 295 lines.append("## 💰 Value Calculations") 296 lines.append("*Make the ROI visual and obvious.*") 297 lines.append("") 298 for v in briefing["value_calculations"]: 299 lines.append(f"### '{v['keyword']}'") 300 lines.append(f"- Current: position #{v['current_position']} → {v['current_monthly_traffic']:,} visits/mo") 301 lines.append(f"- Target: position #{v['target_position']} → {v['projected_monthly_traffic']:,} visits/mo") 302 lines.append(f"- **Additional traffic: +{v['additional_monthly_traffic']:,} visits/mo**") 303 lines.append(f"- **Paid equivalent: ${v['monthly_paid_equivalent']:,.0f}/mo (${v['annual_paid_equivalent']:,.0f}/yr)**") 304 lines.append("") 305 306 opp = briefing["total_opportunity"] 307 lines.append(f"### Total Opportunity") 308 lines.append(f"- Monthly traffic value: **${opp['monthly_paid_equivalent']:,.0f}/mo**") 309 lines.append(f"- Annual traffic value: **${opp['annual_paid_equivalent']:,.0f}/yr**") 310 lines.append(f"- ROI multiple at ${p['deal_target']:,}/mo investment: **{opp['roi_multiple']}x**") 311 lines.append("") 312 313 # Conversation hooks 314 lines.append("## 🎣 Conversation Hooks") 315 lines.append("*Opening questions to surface pain and anchor on value.*") 316 lines.append("") 317 for i, h in enumerate(briefing["conversation_hooks"], 1): 318 lines.append(f"{i}. \"{h}\"") 319 lines.append("") 320 321 # Objection pre-empts 322 lines.append("## 🛡️ Objection Pre-Empts") 323 lines.append("") 324 for key, obj in briefing["objection_preempts"].items(): 325 lines.append(f"### \"{obj['objection']}\"") 326 lines.append(f"**Response:** {obj['response']}") 327 lines.append(f"*Principle: {obj['key_principle']}*") 328 lines.append("") 329 330 return "\n".join(lines) 331 332 333 def main(): 334 parser = argparse.ArgumentParser( 335 description="Value-Based Pricing: Pre-Call Briefing Generator", 336 formatter_class=argparse.RawDescriptionHelpFormatter, 337 epilog=""" 338 Examples: 339 python3 value_pricing_briefing.py --domain acme.com --competitors "comp1.com,comp2.com" 340 python3 value_pricing_briefing.py --domain acme.com --competitors "comp1.com" --industry saas --deal-target 80000 341 python3 value_pricing_briefing.py --domain acme.com --competitors "comp1.com" --format json 342 """, 343 ) 344 parser.add_argument("--domain", required=True, help="Prospect's domain (e.g., acme.com)") 345 parser.add_argument("--competitors", required=True, help="Comma-separated competitor domains (e.g., 'comp1.com,comp2.com')") 346 parser.add_argument("--industry", default=None, help="Prospect's industry (e.g., saas, ecommerce, fintech)") 347 parser.add_argument("--deal-target", type=int, default=50000, help="Target monthly deal size in dollars (default: 50000)") 348 parser.add_argument("--format", choices=["markdown", "json", "both"], default="markdown", help="Output format (default: markdown)") 349 350 args = parser.parse_args() 351 competitors = [c.strip() for c in args.competitors.split(",") if c.strip()] 352 353 briefing = generate_briefing( 354 domain=args.domain, 355 competitors=competitors, 356 industry=args.industry, 357 deal_target=args.deal_target, 358 ) 359 360 if args.format == "json": 361 print(json.dumps(briefing, indent=2)) 362 elif args.format == "both": 363 print(format_markdown(briefing)) 364 print("\n---\n## Raw JSON\n") 365 print(json.dumps(briefing, indent=2)) 366 else: 367 print(format_markdown(briefing)) 368 369 370 if __name__ == "__main__": 371 main()