Files
claude-skills-reference/product-team/competitive-teardown/scripts/competitive_matrix_builder.py
Alireza Rezvani 87070d46e9 feat: add scripts and references to 4 prompt-only skills + Tessl optimization (#292)
Phase 2: 3 new scripts + 2 reference files for prompt-only skills. Tessl 45-55% → 94-100%.
2026-03-07 12:35:58 +01:00

300 lines
10 KiB
Python

#!/usr/bin/env python3
"""Competitive Matrix Builder — Analyze and score competitors across feature dimensions.
Generates weighted competitive matrices, gap analysis, and positioning insights
from structured competitor data.
Usage:
python competitive_matrix_builder.py competitors.json --format json
python competitive_matrix_builder.py competitors.json --format text
python competitive_matrix_builder.py competitors.json --format text --weights pricing=2,ux=1.5
"""
import argparse
import json
import sys
from typing import Dict, List, Any, Optional
from datetime import datetime
from statistics import mean, stdev
def load_competitors(path: str) -> Dict[str, Any]:
"""Load competitor data from JSON file."""
with open(path, "r") as f:
return json.load(f)
def normalize_score(value: float, min_val: float = 1.0, max_val: float = 10.0) -> float:
"""Normalize a score to 0-100 scale."""
return max(0.0, min(100.0, ((value - min_val) / (max_val - min_val)) * 100))
def calculate_weighted_scores(
competitors: List[Dict[str, Any]],
dimensions: List[str],
weights: Optional[Dict[str, float]] = None
) -> List[Dict[str, Any]]:
"""Calculate weighted scores for each competitor across dimensions."""
if weights is None:
weights = {d: 1.0 for d in dimensions}
results = []
for comp in competitors:
scores = comp.get("scores", {})
weighted_total = 0.0
weight_sum = 0.0
dimension_results = {}
for dim in dimensions:
raw = scores.get(dim, 0)
w = weights.get(dim, 1.0)
normalized = normalize_score(raw)
weighted = normalized * w
weighted_total += weighted
weight_sum += w
dimension_results[dim] = {
"raw": raw,
"normalized": round(normalized, 1),
"weight": w,
"weighted": round(weighted, 1)
}
overall = round(weighted_total / weight_sum, 1) if weight_sum > 0 else 0
results.append({
"name": comp["name"],
"overall_score": overall,
"dimensions": dimension_results,
"tier": classify_tier(overall),
"pricing": comp.get("pricing", {}),
"strengths": comp.get("strengths", []),
"weaknesses": comp.get("weaknesses", [])
})
results.sort(key=lambda x: x["overall_score"], reverse=True)
return results
def classify_tier(score: float) -> str:
"""Classify competitor into tier based on overall score."""
if score >= 80:
return "Leader"
elif score >= 60:
return "Strong Competitor"
elif score >= 40:
return "Viable Alternative"
elif score >= 20:
return "Niche Player"
else:
return "Weak"
def gap_analysis(
your_scores: Dict[str, float],
competitor_scores: List[Dict[str, Any]],
dimensions: List[str]
) -> Dict[str, Any]:
"""Identify gaps between your product and competitors."""
gaps = {}
for dim in dimensions:
your_val = your_scores.get(dim, 0)
comp_vals = [c["dimensions"][dim]["raw"] for c in competitor_scores if dim in c.get("dimensions", {})]
if not comp_vals:
continue
avg_comp = mean(comp_vals)
best_comp = max(comp_vals)
gap_to_avg = round(your_val - avg_comp, 1)
gap_to_best = round(your_val - best_comp, 1)
gaps[dim] = {
"your_score": your_val,
"competitor_avg": round(avg_comp, 1),
"competitor_best": best_comp,
"gap_to_avg": gap_to_avg,
"gap_to_best": gap_to_best,
"status": "ahead" if gap_to_avg > 0.5 else ("behind" if gap_to_avg < -0.5 else "parity"),
"priority": "high" if gap_to_best < -2 else ("medium" if gap_to_best < -1 else "low")
}
return {
"gaps": gaps,
"biggest_opportunities": sorted(
[{"dimension": k, **v} for k, v in gaps.items() if v["status"] == "behind"],
key=lambda x: x["gap_to_best"]
)[:5],
"competitive_advantages": sorted(
[{"dimension": k, **v} for k, v in gaps.items() if v["status"] == "ahead"],
key=lambda x: -x["gap_to_avg"]
)[:5]
}
def positioning_analysis(scored: List[Dict[str, Any]]) -> Dict[str, Any]:
"""Generate positioning insights from scored competitors."""
scores = [c["overall_score"] for c in scored]
return {
"market_leaders": [c["name"] for c in scored if c["tier"] == "Leader"],
"your_rank": next((i + 1 for i, c in enumerate(scored) if c.get("is_you")), None),
"total_competitors": len(scored),
"score_distribution": {
"mean": round(mean(scores), 1) if scores else 0,
"stdev": round(stdev(scores), 1) if len(scores) > 1 else 0,
"min": round(min(scores), 1) if scores else 0,
"max": round(max(scores), 1) if scores else 0
},
"tier_distribution": {
tier: len([c for c in scored if c["tier"] == tier])
for tier in ["Leader", "Strong Competitor", "Viable Alternative", "Niche Player", "Weak"]
}
}
def format_text(result: Dict[str, Any]) -> str:
"""Format results as human-readable text."""
lines = []
lines.append("=" * 70)
lines.append("COMPETITIVE MATRIX ANALYSIS")
lines.append(f"Generated: {result['generated_at']}")
lines.append("=" * 70)
# Ranking table
lines.append("\n## COMPETITIVE RANKING\n")
lines.append(f"{'Rank':<6}{'Competitor':<25}{'Score':<10}{'Tier':<20}")
lines.append("-" * 61)
for i, c in enumerate(result["scored_competitors"], 1):
marker = " ← YOU" if c.get("is_you") else ""
lines.append(f"{i:<6}{c['name']:<25}{c['overall_score']:<10}{c['tier']:<20}{marker}")
# Dimension breakdown
lines.append("\n## DIMENSION BREAKDOWN\n")
dims = result["dimensions"]
header = f"{'Dimension':<20}" + "".join(f"{c['name'][:12]:<14}" for c in result["scored_competitors"])
lines.append(header)
lines.append("-" * len(header))
for dim in dims:
row = f"{dim:<20}"
for c in result["scored_competitors"]:
val = c["dimensions"].get(dim, {}).get("raw", "N/A")
row += f"{val:<14}"
lines.append(row)
# Gap analysis
if result.get("gap_analysis"):
ga = result["gap_analysis"]
if ga["biggest_opportunities"]:
lines.append("\n## BIGGEST OPPORTUNITIES (where you're behind)\n")
for opp in ga["biggest_opportunities"]:
lines.append(f"{opp['dimension']}: You={opp['your_score']}, "
f"Best={opp['competitor_best']}, Gap={opp['gap_to_best']} "
f"[{opp['priority'].upper()} priority]")
if ga["competitive_advantages"]:
lines.append("\n## COMPETITIVE ADVANTAGES (where you lead)\n")
for adv in ga["competitive_advantages"]:
lines.append(f"{adv['dimension']}: You={adv['your_score']}, "
f"Avg={adv['competitor_avg']}, Lead=+{adv['gap_to_avg']}")
# Positioning
pos = result.get("positioning", {})
if pos:
lines.append("\n## MARKET POSITIONING\n")
lines.append(f" Market Leaders: {', '.join(pos.get('market_leaders', ['None']))}")
if pos.get("your_rank"):
lines.append(f" Your Rank: #{pos['your_rank']} of {pos['total_competitors']}")
dist = pos.get("score_distribution", {})
lines.append(f" Score Range: {dist.get('min', 0)} - {dist.get('max', 0)} "
f"(avg: {dist.get('mean', 0)}, stdev: {dist.get('stdev', 0)})")
lines.append("\n" + "=" * 70)
return "\n".join(lines)
def build_matrix(data: Dict[str, Any], weight_overrides: Optional[Dict[str, float]] = None) -> Dict[str, Any]:
"""Main entry: build competitive matrix from input data."""
competitors = data.get("competitors", [])
dimensions = data.get("dimensions", [])
your_product = data.get("your_product", {})
if not competitors:
return {"error": "No competitors provided"}
if not dimensions:
# Auto-detect from first competitor's scores
dimensions = list(competitors[0].get("scores", {}).keys())
weights = data.get("weights", {})
if weight_overrides:
weights.update(weight_overrides)
# Include your product in scoring if provided
all_entries = list(competitors)
if your_product:
your_product["is_you"] = True
all_entries.insert(0, your_product)
scored = calculate_weighted_scores(all_entries, dimensions, weights)
# Mark your product
for s in scored:
if any(c.get("is_you") and c["name"] == s["name"] for c in all_entries):
s["is_you"] = True
result = {
"generated_at": datetime.now().isoformat(),
"dimensions": dimensions,
"weights": weights if weights else {d: 1.0 for d in dimensions},
"scored_competitors": scored,
"positioning": positioning_analysis(scored)
}
if your_product:
result["gap_analysis"] = gap_analysis(
your_product.get("scores", {}), scored, dimensions
)
return result
def parse_weights(weight_str: str) -> Dict[str, float]:
"""Parse weight string like 'pricing=2,ux=1.5' into dict."""
weights = {}
for pair in weight_str.split(","):
if "=" in pair:
k, v = pair.split("=", 1)
weights[k.strip()] = float(v.strip())
return weights
def main():
parser = argparse.ArgumentParser(
description="Build competitive matrix with scoring and gap analysis"
)
parser.add_argument("input", help="Path to competitors JSON file")
parser.add_argument("--format", choices=["json", "text"], default="text",
help="Output format (default: text)")
parser.add_argument("--weights", type=str, default=None,
help="Weight overrides: 'dim1=2.0,dim2=1.5'")
parser.add_argument("--output", type=str, default=None,
help="Output file path (default: stdout)")
args = parser.parse_args()
data = load_competitors(args.input)
weight_overrides = parse_weights(args.weights) if args.weights else None
result = build_matrix(data, weight_overrides)
if args.format == "json":
output = json.dumps(result, indent=2)
else:
output = format_text(result)
if args.output:
with open(args.output, "w") as f:
f.write(output)
print(f"Output written to {args.output}")
else:
print(output)
if __name__ == "__main__":
main()