
Brand Strategist
- 713 installs
- 451 repo stars
- Updated July 21, 2026
- borghei/claude-skills
brand-strategist is a Claude Code skill that recommends brand architecture models from portfolio data for developers and product leads structuring a product portfolio or sub-brands among Branded House, House of Brands, E
About
brand-strategist is a skill from borghei/claude-skills backed by a Python brand_architecture_analyzer.py script that evaluates portfolio JSON and recommends among four architecture models: Branded House, House of Brands, Endorsed, and Hybrid. It weighs portfolio overlap, audience similarity, and strategic objectives, with CLI usage via portfolio.json and a --demo flag. Developers and product leads reach for brand-strategist when deciding whether new products share a master brand, stand alone, or use endorsed sub-brands before naming systems, marketing sites, or multi-product repos are finalized.
- Python brand_architecture_analyzer evaluates portfolio JSON and scores architecture fit
- Four documented models: Branded House, House of Brands, Endorsed, Hybrid with pros, cons, and examples
- Factors: portfolio overlap, audience similarity, and strategic objectives
- CLI supports --json output and --demo for quick agent runs
- Includes usage: python brand_architecture_analyzer.py portfolio.json
Brand Strategist by the numbers
- 713 all-time installs (skills.sh)
- Ranked #500 of 1,880 Design & UI/UX skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/borghei/claude-skills --skill brand-strategistAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 713 |
|---|---|
| repo stars | ★ 451 |
| Security audit | 3 / 3 scanners passed |
| Last updated | July 21, 2026 |
| Repository | borghei/claude-skills ↗ |
Which brand architecture fits a product portfolio?
Install when you are structuring a product portfolio or sub-brands and need a data-driven recommendation among Branded House, House of Brands, Endorsed, or Hybrid models.
Who is it for?
Product leads structuring a multi-product portfolio who need a data-driven brand architecture recommendation before naming and go-to-market decisions.
Skip if: Developers who only need logo design, color palettes, or frontend UI work without portfolio-level brand strategy analysis.
When should I use this skill?
The user is structuring a product portfolio, evaluating sub-brands, or asking which brand architecture model fits their products.
What you get
Brand architecture recommendation report comparing Branded House, House of Brands, Endorsed, and Hybrid models with portfolio analysis rationale.
- Brand architecture recommendation
- Portfolio analysis report
By the numbers
- Evaluates 4 brand architecture models
- Includes Python CLI with portfolio.json input and --demo flag
Files
Brand Strategist
The agent operates as a senior brand strategist, delivering actionable brand positioning, identity systems, messaging frameworks, and governance structures for market differentiation.
Workflow
1. Assess brand context - Identify the brand's category, competitive landscape, and target audience. Validate that a clear business objective exists (launch, rebrand, extension, or audit). 2. Develop positioning - Apply the positioning framework to define target, category frame, key benefit, and proof points. Checkpoint: the positioning statement must pass the "only-we" test (no competitor could make the same claim). 3. Build identity system - Define visual identity (logo, color, typography), verbal identity (voice, tone, messaging), and experiential identity. Checkpoint: every element must trace back to the positioning. 4. Construct messaging architecture - Create master narrative, pillar messages, and audience-specific variants. Checkpoint: each pillar must have at least two proof points. 5. Select brand architecture model - Choose Branded House, House of Brands, Endorsed, or Hybrid. Validate alignment with corporate strategy. 6. Establish governance - Define brand guidelines structure, approval process, and measurement cadence. Checkpoint: brand health dashboard covers awareness, perception, and consideration. 7. Measure and iterate - Set up brand tracking (NPS, unaided awareness, share of voice). Review quarterly against baselines.
Brand Positioning Framework
Positioning Statement Template
For [target audience]
Who [need or opportunity]
[Brand] is the [category]
That [key benefit]
Unlike [competitors]
We [unique differentiator]Positioning Map
High Price
|
PREMIUM ----+---- LUXURY
* Quality | * Status
* Performance | * Exclusivity
|
Low Innovation -----+----- High Innovation
|
VALUE ----+---- DISRUPTOR
* Accessibility | * New approach
* Affordability | * Category change
|
Low PriceCompetitive Positioning Matrix
| Attribute | Us | Comp A | Comp B | Comp C |
|---|---|---|---|---|
| Price | $$$ | $$ | $$$$ | $ |
| Quality | High | Medium | High | Low |
| Innovation | High | Low | Medium | High |
| Service | High | High | Low | Medium |
Brand Identity System
BRAND IDENTITY SYSTEM
+-- Visual Identity
| +-- Logo (primary, secondary, icon)
| +-- Color palette
| +-- Typography
| +-- Imagery style
| +-- Graphic elements
+-- Verbal Identity
| +-- Brand voice
| +-- Tone guidelines
| +-- Messaging framework
| +-- Vocabulary
+-- Experiential Identity
+-- Customer experience
+-- Physical environments
+-- Digital experiencesVoice Framework
| Context | Tone Adjustment |
|---|---|
| Marketing | More enthusiastic |
| Support | More empathetic |
| Legal | More formal |
| Social | More casual |
Brand Architecture Models
| Model | Structure | Example |
|---|---|---|
| Branded House | Master Brand > Products | Google (Maps, Drive, Cloud) |
| House of Brands | Parent > Independent Brands | P&G (Tide, Pampers, Gillette) |
| Endorsed | Sub-brand by Master Brand | Marriott (Courtyard by Marriott) |
| Hybrid | Mix of above | Amazon (Prime, AWS, Whole Foods) |
Example: Brand Positioning for a SaaS Startup
# Brand Strategy: FlowMetrics
## Positioning Statement
For data-driven product managers
Who need real-time user behavior insights without engineering support
FlowMetrics is the self-serve analytics platform
That delivers actionable funnels in under 5 minutes
Unlike Amplitude and Mixpanel
We require zero SQL and zero instrumentation code
## Brand Values
1. Clarity: Complex data, simple answers
2. Speed: Insights in minutes, not days
3. Autonomy: No engineering dependency
## Brand Voice
- Confident but not arrogant
- Technical but accessible
- Direct and concise
## Proof Points
- 90-second median time-to-first-insight
- 4.8/5 satisfaction from non-technical PMs
- 50% reduction in analytics engineering ticketsBrand Health Measurement
Awareness: unaided awareness, aided awareness, top-of-mind awareness Perception: brand attribute association, NPS, brand sentiment Consideration: purchase intent, preference vs. competitors, recommendation likelihood
Brand Health Dashboard - Q1 2026
Awareness: 68% (+5%) NPS: 45 (+8) Consideration: 72% (+3%)
Brand Attributes (% association)
Innovative: 78% Trustworthy: 82% Quality: 75%
Share of Voice: 32% (+2%) Sentiment: 85% positiveScripts
# Brand audit analyzer
python scripts/brand_audit.py --surveys survey_data.csv
# Competitive positioning mapper
python scripts/positioning_map.py --competitors comp_data.csv
# Brand voice analyzer
python scripts/voice_analyzer.py --content content.txt
# Brand guidelines generator
python scripts/guidelines_gen.py --config brand_config.yamlReference Materials
references/positioning.md- Positioning frameworksreferences/identity.md- Identity system guidereferences/architecture.md- Brand architecture modelsreferences/governance.md- Governance best practices
---
Troubleshooting
| Symptom | Likely Cause | Resolution |
|---|---|---|
| Positioning statement passes internally but customers do not repeat it | Positioning built on company perspective, not customer language | Rerun April Dunford methodology with 10+ customer interviews; use verbatim customer phrases |
| Brand architecture confusion after acquisition | No decision framework for integrating acquired brands | Evaluate using brand_architecture_analyzer.py; score audience overlap and category fit to choose model |
| NPS declining despite product improvements | Brand perception lagging behind product reality | Run brand health dashboard; invest in rebranding or brand awareness campaign targeting perception gap |
| Multiple sub-brands competing for same audience | House of Brands model applied when Branded House was appropriate | Consolidate overlapping brands; use brand architecture analyzer to validate optimal model |
| Competitive positioning feels generic | Differentiators are category requirements, not unique advantages | Apply "only-we" test: if a competitor could make the same claim, it is not a differentiator |
---
Success Criteria
- Positioning statement passes the "only-we" test — no competitor could make the same claim
- 7+ out of 10 customers describe brand value unprompted in interviews
- Brand health index scores 65+/100 across awareness, perception, consideration, and loyalty
- Brand architecture model validated by lowest churn and fastest close among A-fit segments
- Share of voice increases 5+ percentage points within two quarters of brand strategy execution
- Competitive positioning map shows clear white space between brand and nearest competitor
---
Scope & Limitations
In Scope: Brand positioning frameworks (April Dunford), brand identity system design, brand architecture model selection (Branded House, House of Brands, Endorsed, Hybrid), competitive positioning analysis, brand health measurement, brand governance structures.
Out of Scope: Visual design execution (see brand-guidelines skill), marketing copy creation (see copywriting skill), campaign execution (see marketing-ops skill), product strategy decisions, legal trademark registration.
Limitations: Brand strategy effectiveness depends on consistent execution across the organization. Positioning validation requires real customer interviews — internal-only positioning is unreliable. Brand architecture recommendations are based on audience overlap and category analysis; they do not account for all political or financial factors in brand portfolio decisions.
---
Scripts
| Script | Purpose | Usage |
|---|---|---|
scripts/brand_health_dashboard.py | Calculate brand health metrics across awareness, perception, consideration, loyalty | python scripts/brand_health_dashboard.py survey_data.json --json |
scripts/positioning_map_generator.py | Generate competitive positioning maps with white space analysis | python scripts/positioning_map_generator.py competitors.json --demo |
scripts/brand_architecture_analyzer.py | Evaluate and recommend brand architecture models for a portfolio | python scripts/brand_architecture_analyzer.py portfolio.json --json |
#!/usr/bin/env python3
"""Brand Architecture Analyzer - Evaluate and recommend brand architecture models.
Analyzes brand portfolio data to recommend optimal architecture (Branded House,
House of Brands, Endorsed, Hybrid) based on portfolio overlap, audience
similarity, and strategic objectives.
Usage:
python brand_architecture_analyzer.py portfolio.json
python brand_architecture_analyzer.py portfolio.json --json
python brand_architecture_analyzer.py --demo
"""
import argparse
import json
import sys
ARCHITECTURE_MODELS = {
"branded_house": {
"name": "Branded House (Monolithic)",
"description": "Single master brand across all products. Sub-brands are extensions.",
"examples": "Google, Apple, Virgin, FedEx",
"pros": [
"Maximum brand equity transfer to new products",
"Lower marketing costs (one brand to build)",
"Clear brand recognition across portfolio",
"Easier cross-selling between products",
],
"cons": [
"Brand damage affects entire portfolio",
"Limited ability to target diverse segments",
"New products constrained by master brand positioning",
"Difficult to divest individual products",
],
"best_when": [
"Products share target audience",
"Strong master brand equity exists",
"Products reinforce the same brand promise",
"Company wants to maximize brand efficiency",
],
},
"house_of_brands": {
"name": "House of Brands (Pluralistic)",
"description": "Independent brands with no visible parent connection.",
"examples": "P&G (Tide, Pampers, Gillette), Unilever (Dove, Axe)",
"pros": [
"Each brand targets specific segments precisely",
"Brand failures are contained",
"Allows competing in same category with multiple brands",
"Easy to acquire and divest brands",
],
"cons": [
"Highest marketing cost (build each brand separately)",
"No equity transfer between brands",
"Requires deep marketing expertise per brand",
"Risk of internal brand cannibalization",
],
"best_when": [
"Products serve very different audiences",
"Brands need distinct identities",
"Acquisitions with strong existing brand equity",
"Risk isolation is critical",
],
},
"endorsed": {
"name": "Endorsed Brands",
"description": "Sub-brands with visible parent brand endorsement.",
"examples": "Marriott (Courtyard by Marriott), Nestle (KitKat by Nestle)",
"pros": [
"Sub-brands get credibility from parent",
"Each brand has own identity within parent framework",
"Balanced equity transfer",
"Parent brand strengthened by successful sub-brands",
],
"cons": [
"Complex brand management requirements",
"Parent brand partially exposed to sub-brand issues",
"Endorsement relationship must be clear and consistent",
"Higher cost than branded house, lower than house of brands",
],
"best_when": [
"Products need own identity but benefit from parent credibility",
"Entering new categories adjacent to core",
"Acquisitions that should maintain identity but gain parent trust",
],
},
"hybrid": {
"name": "Hybrid Architecture",
"description": "Mix of branded house, endorsed, and independent brands.",
"examples": "Amazon (Prime, AWS, Whole Foods), Microsoft (Office, LinkedIn, GitHub)",
"pros": [
"Flexibility to optimize per product/market",
"Can evolve architecture as portfolio changes",
"Best of multiple approaches",
],
"cons": [
"Most complex to manage",
"Risk of brand confusion if not well-governed",
"Requires clear decision framework for new additions",
],
"best_when": [
"Portfolio spans diverse markets",
"Mix of organic and acquired brands",
"Different products at different lifecycle stages",
],
},
}
def analyze_portfolio(data):
"""Analyze brand portfolio and recommend architecture."""
brands = data.get("brands", data.get("products", []))
parent_brand = data.get("parent_brand", data.get("master_brand", "Parent"))
# Calculate overlap metrics
all_audiences = set()
audience_overlap = 0
total_pairs = 0
category_diversity = set()
price_range = []
for brand in brands:
audiences = set(brand.get("audiences", []))
all_audiences.update(audiences)
category_diversity.add(brand.get("category", "unknown"))
if "price_tier" in brand:
price_range.append(brand["price_tier"])
# Calculate pairwise audience overlap
for i, a in enumerate(brands):
for j, b in enumerate(brands):
if i < j:
a_audiences = set(a.get("audiences", []))
b_audiences = set(b.get("audiences", []))
if a_audiences and b_audiences:
overlap = len(a_audiences & b_audiences) / len(a_audiences | b_audiences)
audience_overlap += overlap
total_pairs += 1
avg_overlap = (audience_overlap / total_pairs) if total_pairs > 0 else 0
# Score each architecture model
scores = {}
# Branded House score
bh_score = 50
bh_score += avg_overlap * 30 # High overlap favors branded house
bh_score -= len(category_diversity) * 5 # Many categories penalizes
bh_score += 10 if len(brands) <= 5 else -5 # Smaller portfolio favors
scores["branded_house"] = max(0, min(100, bh_score))
# House of Brands score
hob_score = 50
hob_score -= avg_overlap * 30 # Low overlap favors house of brands
hob_score += len(category_diversity) * 5
hob_score += 10 if len(brands) > 5 else -5
scores["house_of_brands"] = max(0, min(100, hob_score))
# Endorsed score
end_score = 60 # Generally safe middle ground
end_score += 5 if 0.3 < avg_overlap < 0.7 else -10
end_score += 5 if 2 <= len(category_diversity) <= 4 else -5
scores["endorsed"] = max(0, min(100, end_score))
# Hybrid score
hyb_score = 40
hyb_score += len(brands) * 2 # Larger portfolios favor hybrid
hyb_score += len(category_diversity) * 3
hyb_score += 10 if len(price_range) > 0 and len(set(price_range)) > 2 else 0
scores["hybrid"] = max(0, min(100, hyb_score))
# Determine recommendation
recommended = max(scores, key=scores.get)
# Brand-level recommendations
brand_recommendations = []
for brand in brands:
brand_audiences = set(brand.get("audiences", []))
# Check overlap with other brands
overlaps = []
for other in brands:
if other.get("name") != brand.get("name"):
other_audiences = set(other.get("audiences", []))
if brand_audiences and other_audiences:
o = len(brand_audiences & other_audiences) / len(brand_audiences | other_audiences)
overlaps.append(o)
avg_brand_overlap = sum(overlaps) / len(overlaps) if overlaps else 0
if avg_brand_overlap > 0.6:
rec = "branded_house"
elif avg_brand_overlap < 0.2:
rec = "independent"
else:
rec = "endorsed"
brand_recommendations.append({
"brand": brand.get("name", "unknown"),
"category": brand.get("category", "unknown"),
"audience_overlap": round(avg_brand_overlap, 2),
"recommendation": rec,
})
return {
"portfolio_metrics": {
"total_brands": len(brands),
"categories": list(category_diversity),
"category_count": len(category_diversity),
"average_audience_overlap": round(avg_overlap, 2),
"total_unique_audiences": len(all_audiences),
},
"architecture_scores": {k: round(v, 1) for k, v in sorted(scores.items(), key=lambda x: x[1], reverse=True)},
"recommended_model": recommended,
"model_details": ARCHITECTURE_MODELS[recommended],
"brand_level_recommendations": brand_recommendations,
"all_models": {k: ARCHITECTURE_MODELS[k] for k in ARCHITECTURE_MODELS},
}
def get_demo_data():
return {
"parent_brand": "TechCorp",
"brands": [
{"name": "TechCorp Analytics", "category": "analytics", "audiences": ["data_teams", "product_managers", "executives"], "price_tier": "mid"},
{"name": "TechCorp Cloud", "category": "infrastructure", "audiences": ["developers", "devops", "ctos"], "price_tier": "high"},
{"name": "DataViz Pro", "category": "visualization", "audiences": ["data_teams", "analysts", "executives"], "price_tier": "mid"},
{"name": "QuickDeploy", "category": "devtools", "audiences": ["developers", "devops"], "price_tier": "low"},
],
}
def format_report(analysis):
"""Format human-readable report."""
lines = []
lines.append("=" * 65)
lines.append("BRAND ARCHITECTURE ANALYSIS")
lines.append("=" * 65)
pm = analysis["portfolio_metrics"]
lines.append(f"Brands: {pm['total_brands']}")
lines.append(f"Categories: {', '.join(pm['categories'])}")
lines.append(f"Audience overlap: {pm['average_audience_overlap']:.0%}")
lines.append("")
lines.append("--- ARCHITECTURE MODEL SCORES ---")
for model, score in analysis["architecture_scores"].items():
label = ARCHITECTURE_MODELS[model]["name"]
bar = "#" * int(score / 5) + "." * (20 - int(score / 5))
rec = " <-- RECOMMENDED" if model == analysis["recommended_model"] else ""
lines.append(f" {label:<35} [{bar}] {score:.0f}{rec}")
lines.append("")
rec_model = analysis["model_details"]
lines.append(f"--- RECOMMENDED: {rec_model['name']} ---")
lines.append(f"Description: {rec_model['description']}")
lines.append(f"Examples: {rec_model['examples']}")
lines.append("")
lines.append("Pros:")
for pro in rec_model["pros"]:
lines.append(f" + {pro}")
lines.append("Cons:")
for con in rec_model["cons"]:
lines.append(f" - {con}")
lines.append("")
lines.append("--- BRAND-LEVEL RECOMMENDATIONS ---")
for br in analysis["brand_level_recommendations"]:
lines.append(f" {br['brand']}: {br['recommendation']} (overlap: {br['audience_overlap']:.0%})")
return "\n".join(lines)
def main():
parser = argparse.ArgumentParser(description="Analyze and recommend brand architecture models")
parser.add_argument("input", nargs="?", help="JSON file with brand portfolio data")
parser.add_argument("--json", action="store_true", dest="json_output", help="Output JSON")
parser.add_argument("--demo", action="store_true", help="Run with demo data")
args = parser.parse_args()
if args.demo:
data = get_demo_data()
elif args.input:
try:
with open(args.input, "r", encoding="utf-8") as f:
data = json.load(f)
except FileNotFoundError:
print(f"Error: File not found: {args.input}", file=sys.stderr)
sys.exit(1)
except json.JSONDecodeError as e:
print(f"Error: Invalid JSON: {e}", file=sys.stderr)
sys.exit(1)
else:
parser.print_help()
sys.exit(1)
analysis = analyze_portfolio(data)
if args.json_output:
print(json.dumps(analysis, indent=2, default=str))
else:
print(format_report(analysis))
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""Brand Health Dashboard - Calculate and visualize brand health metrics.
Processes survey and performance data to generate a brand health scorecard
covering awareness, perception, consideration, and loyalty.
Usage:
python brand_health_dashboard.py survey_data.json
python brand_health_dashboard.py survey_data.json --json
python brand_health_dashboard.py --demo
"""
import argparse
import json
import sys
HEALTH_DIMENSIONS = {
"awareness": {
"metrics": ["unaided_awareness", "aided_awareness", "top_of_mind"],
"weight": 0.25,
},
"perception": {
"metrics": ["nps", "brand_sentiment", "attribute_association"],
"weight": 0.25,
},
"consideration": {
"metrics": ["purchase_intent", "preference_vs_competitors", "recommendation_likelihood"],
"weight": 0.25,
},
"loyalty": {
"metrics": ["repeat_purchase_rate", "share_of_wallet", "advocacy_rate"],
"weight": 0.25,
},
}
BENCHMARKS = {
"unaided_awareness": {"poor": 10, "average": 25, "good": 50, "excellent": 70},
"aided_awareness": {"poor": 30, "average": 50, "good": 70, "excellent": 85},
"top_of_mind": {"poor": 5, "average": 15, "good": 30, "excellent": 50},
"nps": {"poor": -10, "average": 20, "good": 45, "excellent": 70},
"brand_sentiment": {"poor": 40, "average": 60, "good": 75, "excellent": 85},
"attribute_association": {"poor": 30, "average": 50, "good": 65, "excellent": 80},
"purchase_intent": {"poor": 15, "average": 30, "good": 50, "excellent": 70},
"preference_vs_competitors": {"poor": 20, "average": 35, "good": 50, "excellent": 65},
"recommendation_likelihood": {"poor": 20, "average": 40, "good": 60, "excellent": 75},
"repeat_purchase_rate": {"poor": 20, "average": 40, "good": 60, "excellent": 80},
"share_of_wallet": {"poor": 15, "average": 30, "good": 50, "excellent": 70},
"advocacy_rate": {"poor": 5, "average": 15, "good": 30, "excellent": 50},
}
def rate_metric(metric_name, value):
"""Rate a metric against benchmarks."""
bench = BENCHMARKS.get(metric_name, {"poor": 20, "average": 40, "good": 60, "excellent": 80})
if value >= bench["excellent"]:
return "excellent"
elif value >= bench["good"]:
return "good"
elif value >= bench["average"]:
return "average"
return "poor"
def normalize_score(metric_name, value):
"""Normalize metric to 0-100 scale."""
bench = BENCHMARKS.get(metric_name, {"poor": 0, "excellent": 100})
# NPS ranges from -100 to 100
if metric_name == "nps":
return max(0, min(100, (value + 100) / 2))
return max(0, min(100, value))
def calculate_health(data):
"""Calculate brand health scores from input data."""
current = data.get("current", {})
previous = data.get("previous", {})
dimension_scores = {}
for dim_name, dim_config in HEALTH_DIMENSIONS.items():
metrics_results = []
for metric in dim_config["metrics"]:
value = current.get(metric)
if value is not None:
prev_value = previous.get(metric)
change = None
if prev_value is not None:
if metric == "nps":
change = value - prev_value
else:
change = round(value - prev_value, 1)
normalized = normalize_score(metric, value)
rating = rate_metric(metric, value)
metrics_results.append({
"metric": metric,
"value": value,
"previous": prev_value,
"change": change,
"change_direction": "up" if change and change > 0 else ("down" if change and change < 0 else "flat"),
"normalized": round(normalized, 1),
"rating": rating,
})
if metrics_results:
dim_avg = sum(m["normalized"] for m in metrics_results) / len(metrics_results)
dimension_scores[dim_name] = {
"score": round(dim_avg, 1),
"weight": dim_config["weight"],
"weighted_score": round(dim_avg * dim_config["weight"], 1),
"metrics": metrics_results,
}
# Overall brand health index
total_weighted = sum(d["weighted_score"] for d in dimension_scores.values())
total_weight = sum(d["weight"] for d in dimension_scores.values())
overall = (total_weighted / total_weight) if total_weight > 0 else 0
# Strengths and weaknesses
all_metrics = []
for dim in dimension_scores.values():
all_metrics.extend(dim["metrics"])
strengths = sorted([m for m in all_metrics if m["rating"] in ("good", "excellent")],
key=lambda x: x["normalized"], reverse=True)[:3]
weaknesses = sorted([m for m in all_metrics if m["rating"] in ("poor", "average")],
key=lambda x: x["normalized"])[:3]
# Trends
improving = [m for m in all_metrics if m.get("change_direction") == "up"]
declining = [m for m in all_metrics if m.get("change_direction") == "down"]
return {
"brand_health_index": round(overall, 1),
"grade": _grade(overall),
"dimensions": dimension_scores,
"strengths": [{"metric": s["metric"], "value": s["value"], "rating": s["rating"]} for s in strengths],
"weaknesses": [{"metric": w["metric"], "value": w["value"], "rating": w["rating"]} for w in weaknesses],
"trends": {
"improving": [{"metric": m["metric"], "change": m["change"]} for m in improving],
"declining": [{"metric": m["metric"], "change": m["change"]} for m in declining],
},
}
def _grade(score):
if score >= 80:
return "A"
elif score >= 65:
return "B"
elif score >= 50:
return "C"
elif score >= 35:
return "D"
return "F"
def get_demo_data():
"""Return demo data for testing."""
return {
"current": {
"unaided_awareness": 42,
"aided_awareness": 68,
"top_of_mind": 18,
"nps": 45,
"brand_sentiment": 78,
"attribute_association": 62,
"purchase_intent": 55,
"preference_vs_competitors": 48,
"recommendation_likelihood": 61,
"repeat_purchase_rate": 72,
"share_of_wallet": 38,
"advocacy_rate": 28,
},
"previous": {
"unaided_awareness": 38,
"aided_awareness": 65,
"top_of_mind": 15,
"nps": 38,
"brand_sentiment": 74,
"attribute_association": 58,
"purchase_intent": 52,
"preference_vs_competitors": 45,
"recommendation_likelihood": 57,
"repeat_purchase_rate": 68,
"share_of_wallet": 35,
"advocacy_rate": 25,
},
}
def format_report(analysis):
"""Format human-readable dashboard."""
lines = []
lines.append("=" * 65)
lines.append("BRAND HEALTH DASHBOARD")
lines.append("=" * 65)
lines.append(f"Brand Health Index: {analysis['brand_health_index']}/100 (Grade: {analysis['grade']})")
lines.append("")
# Dimension scores
lines.append("--- DIMENSION SCORES ---")
for dim_name, dim_data in analysis["dimensions"].items():
bar_len = int(dim_data["score"] / 5)
bar = "#" * bar_len + "." * (20 - bar_len)
lines.append(f" {dim_name.title():<20} [{bar}] {dim_data['score']:.0f}/100")
for m in dim_data["metrics"]:
change_str = ""
if m["change"] is not None:
arrow = "+" if m["change"] > 0 else ""
change_str = f" ({arrow}{m['change']})"
lines.append(f" {m['metric']:<30} {m['value']:>6}{change_str:<10} [{m['rating']}]")
lines.append("")
# Strengths
if analysis["strengths"]:
lines.append("--- STRENGTHS ---")
for s in analysis["strengths"]:
lines.append(f" {s['metric']}: {s['value']} [{s['rating']}]")
lines.append("")
# Weaknesses
if analysis["weaknesses"]:
lines.append("--- AREAS FOR IMPROVEMENT ---")
for w in analysis["weaknesses"]:
lines.append(f" {w['metric']}: {w['value']} [{w['rating']}]")
lines.append("")
# Trends
trends = analysis["trends"]
if trends["improving"]:
lines.append("--- IMPROVING ---")
for t in trends["improving"]:
lines.append(f" {t['metric']}: +{t['change']}")
if trends["declining"]:
lines.append("--- DECLINING ---")
for t in trends["declining"]:
lines.append(f" {t['metric']}: {t['change']}")
return "\n".join(lines)
def main():
parser = argparse.ArgumentParser(description="Calculate brand health metrics dashboard")
parser.add_argument("input", nargs="?", help="JSON file with brand metrics data")
parser.add_argument("--json", action="store_true", dest="json_output", help="Output JSON")
parser.add_argument("--demo", action="store_true", help="Run with demo data")
args = parser.parse_args()
if args.demo:
data = get_demo_data()
elif args.input:
try:
with open(args.input, "r", encoding="utf-8") as f:
data = json.load(f)
except FileNotFoundError:
print(f"Error: File not found: {args.input}", file=sys.stderr)
sys.exit(1)
except json.JSONDecodeError as e:
print(f"Error: Invalid JSON: {e}", file=sys.stderr)
sys.exit(1)
else:
parser.print_help()
sys.exit(1)
analysis = calculate_health(data)
if args.json_output:
print(json.dumps(analysis, indent=2))
else:
print(format_report(analysis))
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""Positioning Map Generator - Create competitive positioning maps from attribute data.
Plots brands on a 2-axis positioning map, identifies white space opportunities,
and calculates positioning distance between competitors.
Usage:
python positioning_map_generator.py competitors.json
python positioning_map_generator.py competitors.json --json
python positioning_map_generator.py --demo
"""
import argparse
import json
import math
import sys
def calculate_distance(brand_a, brand_b):
"""Calculate Euclidean distance between two brands on the map."""
dx = brand_a["x"] - brand_b["x"]
dy = brand_a["y"] - brand_b["y"]
return math.sqrt(dx * dx + dy * dy)
def find_clusters(brands, threshold=20):
"""Find clusters of brands that are positioned closely."""
clusters = []
assigned = set()
for i, brand_a in enumerate(brands):
if i in assigned:
continue
cluster = [brand_a]
assigned.add(i)
for j, brand_b in enumerate(brands):
if j in assigned:
continue
if calculate_distance(brand_a, brand_b) < threshold:
cluster.append(brand_b)
assigned.add(j)
if len(cluster) > 1:
clusters.append(cluster)
return clusters
def find_white_space(brands, grid_size=10):
"""Find positioning white space (empty areas on the map)."""
# Create a grid and mark occupied cells
grid = {}
for x in range(0, 101, grid_size):
for y in range(0, 101, grid_size):
grid[(x, y)] = True # True = available
for brand in brands:
bx = round(brand["x"] / grid_size) * grid_size
by = round(brand["y"] / grid_size) * grid_size
# Mark surrounding cells as occupied
for dx in range(-grid_size, grid_size * 2, grid_size):
for dy in range(-grid_size, grid_size * 2, grid_size):
key = (bx + dx, by + dy)
if key in grid:
grid[key] = False
# Find available spaces
white_spaces = []
for (x, y), available in grid.items():
if available and 10 <= x <= 90 and 10 <= y <= 90:
# Calculate minimum distance to nearest brand
min_dist = min(
math.sqrt((x - b["x"]) ** 2 + (y - b["y"]) ** 2)
for b in brands
) if brands else 100
white_spaces.append({
"x": x,
"y": y,
"distance_to_nearest": round(min_dist, 1),
})
# Sort by distance (most open space first)
white_spaces.sort(key=lambda w: w["distance_to_nearest"], reverse=True)
return white_spaces[:5] # Top 5 opportunities
def generate_text_map(brands, x_axis, y_axis, width=60, height=25):
"""Generate an ASCII text positioning map."""
lines = []
lines.append(f" {y_axis.get('high', 'High')}")
lines.append(" " + "-" * width)
# Create grid
grid = [[" " for _ in range(width)] for _ in range(height)]
# Place brands on grid
for brand in brands:
gx = int(brand["x"] / 100 * (width - 1))
gy = int((100 - brand["y"]) / 100 * (height - 1)) # Invert Y
gx = max(0, min(width - 1, gx))
gy = max(0, min(height - 1, gy))
label = brand.get("name", "?")[:6]
# Place marker
for ci, ch in enumerate(label):
if gx + ci < width:
grid[gy][gx + ci] = ch
# Add axis labels and borders
for row in grid:
lines.append(" |" + "".join(row) + "|")
lines.append(" " + "-" * width)
x_low = x_axis.get("low", "Low")
x_high = x_axis.get("high", "High")
padding = width - len(x_low) - len(x_high)
lines.append(f" {x_low}{' ' * max(padding, 1)}{x_high}")
lines.append(f" {y_axis.get('low', 'Low')}")
return "\n".join(lines)
def analyze_positioning(data):
"""Analyze competitive positioning data."""
brands = data.get("brands", data.get("competitors", []))
x_axis = data.get("x_axis", {"label": "X Axis", "low": "Low", "high": "High"})
y_axis = data.get("y_axis", {"label": "Y Axis", "low": "Low", "high": "High"})
# Calculate distances between all pairs
distances = []
for i, a in enumerate(brands):
for j, b in enumerate(brands):
if i < j:
dist = calculate_distance(a, b)
distances.append({
"brand_a": a.get("name", f"Brand {i}"),
"brand_b": b.get("name", f"Brand {j}"),
"distance": round(dist, 1),
})
distances.sort(key=lambda d: d["distance"])
# Find clusters and white space
clusters = find_clusters(brands)
white_spaces = find_white_space(brands)
# Identify the most differentiated brand
avg_distances = {}
for brand in brands:
name = brand.get("name", "unknown")
dists = [d["distance"] for d in distances if name in (d["brand_a"], d["brand_b"])]
avg_distances[name] = round(sum(dists) / len(dists), 1) if dists else 0
most_differentiated = max(avg_distances.items(), key=lambda x: x[1]) if avg_distances else None
least_differentiated = min(avg_distances.items(), key=lambda x: x[1]) if avg_distances else None
# Quadrant analysis
quadrants = {"top_right": [], "top_left": [], "bottom_right": [], "bottom_left": []}
for brand in brands:
name = brand.get("name", "unknown")
if brand["x"] >= 50 and brand["y"] >= 50:
quadrants["top_right"].append(name)
elif brand["x"] < 50 and brand["y"] >= 50:
quadrants["top_left"].append(name)
elif brand["x"] >= 50 and brand["y"] < 50:
quadrants["bottom_right"].append(name)
else:
quadrants["bottom_left"].append(name)
return {
"brands": brands,
"axes": {"x": x_axis, "y": y_axis},
"distances": distances,
"clusters": [[b.get("name", "?") for b in c] for c in clusters],
"white_space_opportunities": white_spaces,
"quadrants": quadrants,
"most_differentiated": {"name": most_differentiated[0], "avg_distance": most_differentiated[1]} if most_differentiated else None,
"least_differentiated": {"name": least_differentiated[0], "avg_distance": least_differentiated[1]} if least_differentiated else None,
"avg_distances": avg_distances,
}
def get_demo_data():
return {
"x_axis": {"label": "Innovation", "low": "Traditional", "high": "Innovative"},
"y_axis": {"label": "Price", "low": "Affordable", "high": "Premium"},
"brands": [
{"name": "Us", "x": 75, "y": 60},
{"name": "CompA", "x": 30, "y": 80},
{"name": "CompB", "x": 80, "y": 85},
{"name": "CompC", "x": 25, "y": 30},
{"name": "CompD", "x": 60, "y": 40},
],
}
def format_report(analysis):
"""Format human-readable positioning analysis."""
lines = []
lines.append("=" * 65)
lines.append("COMPETITIVE POSITIONING MAP ANALYSIS")
lines.append("=" * 65)
# Text map
lines.append(generate_text_map(
analysis["brands"],
analysis["axes"]["x"],
analysis["axes"]["y"],
))
lines.append("")
# Quadrant analysis
lines.append("--- QUADRANT ANALYSIS ---")
x_axis = analysis["axes"]["x"]
y_axis = analysis["axes"]["y"]
lines.append(f" {y_axis.get('high','High')} + {x_axis.get('high','High')}: {', '.join(analysis['quadrants']['top_right']) or 'Empty'}")
lines.append(f" {y_axis.get('high','High')} + {x_axis.get('low','Low')}: {', '.join(analysis['quadrants']['top_left']) or 'Empty'}")
lines.append(f" {y_axis.get('low','Low')} + {x_axis.get('high','High')}: {', '.join(analysis['quadrants']['bottom_right']) or 'Empty'}")
lines.append(f" {y_axis.get('low','Low')} + {x_axis.get('low','Low')}: {', '.join(analysis['quadrants']['bottom_left']) or 'Empty'}")
lines.append("")
# Closest competitors
if analysis["distances"]:
lines.append("--- CLOSEST COMPETITORS ---")
for d in analysis["distances"][:3]:
lines.append(f" {d['brand_a']} <-> {d['brand_b']}: distance {d['distance']}")
lines.append("")
# Clusters
if analysis["clusters"]:
lines.append("--- COMPETITIVE CLUSTERS ---")
for i, cluster in enumerate(analysis["clusters"], 1):
lines.append(f" Cluster {i}: {', '.join(cluster)}")
lines.append("")
# Differentiation
if analysis["most_differentiated"]:
md = analysis["most_differentiated"]
lines.append(f"Most differentiated: {md['name']} (avg distance: {md['avg_distance']})")
if analysis["least_differentiated"]:
ld = analysis["least_differentiated"]
lines.append(f"Least differentiated: {ld['name']} (avg distance: {ld['avg_distance']})")
lines.append("")
# White space
if analysis["white_space_opportunities"]:
lines.append("--- WHITE SPACE OPPORTUNITIES ---")
for ws in analysis["white_space_opportunities"][:3]:
lines.append(f" Position ({ws['x']}, {ws['y']}): {ws['distance_to_nearest']:.0f} units from nearest competitor")
return "\n".join(lines)
def main():
parser = argparse.ArgumentParser(description="Generate competitive positioning maps")
parser.add_argument("input", nargs="?", help="JSON file with competitor positioning data")
parser.add_argument("--json", action="store_true", dest="json_output", help="Output JSON")
parser.add_argument("--demo", action="store_true", help="Run with demo data")
args = parser.parse_args()
if args.demo:
data = get_demo_data()
elif args.input:
try:
with open(args.input, "r", encoding="utf-8") as f:
data = json.load(f)
except FileNotFoundError:
print(f"Error: File not found: {args.input}", file=sys.stderr)
sys.exit(1)
except json.JSONDecodeError as e:
print(f"Error: Invalid JSON: {e}", file=sys.stderr)
sys.exit(1)
else:
parser.print_help()
sys.exit(1)
analysis = analyze_positioning(data)
if args.json_output:
print(json.dumps(analysis, indent=2))
else:
print(format_report(analysis))
if __name__ == "__main__":
main()
Related skills
FAQ
Which brand architecture models does brand-strategist compare?
brand-strategist compares four models: Branded House (monolithic), House of Brands, Endorsed, and Hybrid. The bundled Python analyzer scores portfolio overlap, audience similarity, and strategic objectives to recommend one approach.
How do you run the brand-strategist analyzer?
brand-strategist uses brand_architecture_analyzer.py with a portfolio.json input, optional --json output, or --demo for a sample run. The script returns a recommended architecture with rationale based on portfolio data.
Is Brand Strategist safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.