/ 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()