
Pricing Strategy
- 97 installs
- 451 repo stars
- Updated July 21, 2026
- borghei/claude-skills
Pricing Strategy is a Claude skill for designing and optimizing SaaS pricing, covering value-metric selection, tier architecture, willingness-to-pay research, pricing-page design and price-increase execution.
About
Pricing Strategy is a SaaS pricing framework covering value-metric selection, tier architecture, price-point research, pricing-page design, price-increase execution and competitive positioning. A founder or PM uses it when designing pricing from scratch, restructuring tiers, or planning a price increase without burning customers. It defines three operating modes (design, optimize, increase) and ships analyzer, sensitivity and increase-modeler Python scripts.
- Three pricing axes in order: value metric, then packaging, then price point
- Van Westendorp, MaxDiff and WTP research methods plus competitor benchmarking
- Price-increase playbook with grandfathering, comms window and churn modeling
Pricing Strategy by the numbers
- 97 all-time installs (skills.sh)
- Ranked #448 of 853 Sales & Marketing skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
pricing-strategy capabilities & compatibility
- Capabilities
- popup cro · referral program · competitive teardown
- Use cases
- marketing
- Pricing
- Free
What pricing-strategy says it does
SaaS pricing design and optimization covering value metric selection, tier architecture, price point research, pricing page design, price increase
lock the value metric (how it scales), then packaging (what's in each tier), then test the price point (the number). Most teams skip to price point; that is backwards.
Van Westendorp analysis requires minimum 30 survey respondents for statistical validity.
npx skills add https://github.com/borghei/claude-skills --skill pricing-strategyAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 97 |
|---|---|
| repo stars | ★ 451 |
| Last updated | July 21, 2026 |
| Repository | borghei/claude-skills ↗ |
What it does
Choose a value metric, architect Good-Better-Best tiers, research willingness-to-pay and execute price increases for a SaaS product.
Who is it for?
Designing a SaaS value metric and tiers or executing a price increase.
Skip if: Pricing-page visual CRO or wiring pricing into a billing system like Stripe.
When should I use this skill?
You need to design pricing, set prices, choose a value metric, restructure tiers or plan a price increase.
What you get
A value-metric, tier structure and price points validated against the pricing corridor, plus a price-increase execution plan.
- value metric and tier structure
- price points tested against the corridor
- price-increase plan
By the numbers
- 3 operating modes
- ships 3 Python scripts (analyzer, sensitivity calculator, increase modeler)
Files
Pricing Strategy
Production-grade SaaS pricing framework covering the three pricing axes (value metric, packaging, price point), value-based pricing methodology, tier architecture, pricing research methods, pricing page design, price increase execution, and competitive pricing positioning. Pricing is positioning -- the right price communicates as much about your product as your marketing does.
Core Capabilities
- Three pricing axes (in order) — lock the value metric (how it scales), then packaging (what's in each tier), then test the price point (the number). Most teams skip to price point; that is backwards.
- Value metric & tier design — select a metric that scales with customer value and is hard to game; architect Good-Better-Best tiers with deliberate feature allocation and naming.
- Value-based pricing — price inside the corridor (above the next-best alternative, below perceived value), at 10-20% of documented value delivered.
- Pricing research — Van Westendorp, MaxDiff, competitor benchmarking, willingness-to-pay interviews.
- Pricing page, price increases & competition — page design (above/below fold, annual toggle), price-increase playbook (strategy, timeline, comms, impact), competitive positioning and health diagnostics.
Use when
- The user asks to "design pricing", "set prices", or "choose a value metric"
- Pricing tiers need to be restructured (Good-Better-Best, add/remove tiers, repackage features)
- A price increase is planned and needs execution design (strategy, timing, communication, grandfathering)
- Conversion on the pricing page is flat or declining
- Freemium vs free trial decision needs to be made, or the freemium tier is cannibalizing paid
- Competitor pricing shifts require a positioning response
- The user says "our pricing feels off" or asks for a pricing audit
Operating Modes
- Mode 1 — Design From Scratch: No pricing exists or full rebuild needed. Work value metric → tier structure → price points → page design. Validate: value metric chosen before tier design; tiers locked before price points; price points tested against the corridor before page design.
- Mode 2 — Optimize Existing Pricing: Pricing exists but conversion is low, expansion flat, or customers feel mispriced. Audit, benchmark, find specific improvements. Validate: the diagnosis names a specific failure mode (e.g., "middle tier too narrow") before any change is proposed.
- Mode 3 — Price Increase: Prices need to go up without burning relationships. Validate: grandfather policy defined, communication window set (90+ days for annual customers), and expected churn modeled before the first notice.
References
Load the reference that matches the task — keep this file lean and pull detail on demand:
- [references/pricing-models.md](references/pricing-models.md) — the three pricing axes, value metric selection (table, criteria, red flags), tier architecture (Good-Better-Best, feature allocation, naming), and value-based pricing corridor. Read when designing or restructuring pricing.
- [references/research-and-page-design.md](references/research-and-page-design.md) — Van Westendorp, MaxDiff, competitor benchmarking, WTP interviews, pricing page design, and the freemium vs free trial decision. Read when researching willingness-to-pay or designing the page.
- [references/price-increase-and-competitive.md](references/price-increase-and-competitive.md) — price increase strategy/timeline/comms/impact, competitive position map and positioning strategy, and pricing health signals. Read when raising prices or positioning against competitors.
- [references/tools-and-diagnostics.md](references/tools-and-diagnostics.md) — output artifacts, the three Python scripts (analyzer, sensitivity calculator, increase modeler), troubleshooting table, success criteria, and anti-patterns. Read when producing deliverables, running tools, or debugging a pricing problem.
Scope & Limitations
In scope: Value metric selection, tier architecture design, price point research (Van Westendorp, competitor benchmarking, willingness-to-pay interviews), pricing page design specifications, price increase strategy and execution, freemium vs free trial decision frameworks, competitive pricing analysis and positioning, and pricing health diagnostics.
Out of scope: Pricing page visual design and CRO (use page-cro), in-app upgrade prompts and paywalls (use paywall-upgrade-cro), signup flow optimization after pricing page (use signup-flow-cro), churn intervention when churn is the root cause (use churn-prevention), and full competitive analysis beyond pricing (use competitive-teardown). Scripts do not integrate with billing systems (Stripe, Chargebee, etc.) or analytics platforms.
Limitations: Van Westendorp analysis requires minimum 30 survey respondents for statistical validity. Pricing benchmarks are based on aggregate SaaS industry data and vary significantly by vertical, company stage, and geography. Credit-based and usage-based pricing models (growing to 38% of SaaS in 2026) have different optimization dynamics than flat-rate or per-seat models. Price elasticity varies by customer segment -- enterprise buyers are less price-sensitive than SMB.
Integration Points
- page-cro -- Pricing page layout, CTA placement, and social proof design should follow page-cro best practices
- paywall-upgrade-cro -- In-app upgrade screens must reflect the same tier structure and messaging as the public pricing page
- competitive-teardown -- Competitive pricing data from teardowns feeds directly into pricing position map and tier design
- churn-prevention -- Churn analysis by price point and tier informs whether pricing is causing retention issues
- signup-flow-cro -- Signup flow design depends on pricing model (CC-required vs free trial vs freemium)
- revenue-operations -- GTM efficiency metrics (LTV:CAC, Magic Number) validate whether pricing supports unit economics
Related Skills
- page-cro -- Use for optimizing the pricing page conversion rate (layout, CTA, social proof). Not for pricing structure or tier design.
- churn-prevention -- Use when churn is the underlying issue. Fix retention before raising prices.
- competitive-teardown -- Use for comprehensive competitive analysis. Feed teardown pricing data into this skill.
- paywall-upgrade-cro -- Use for in-app upgrade prompts and paywalls. Different from public pricing page design.
- signup-flow-cro -- Use for optimizing the signup flow that follows pricing page conversion.
Price Increases & Competitive Positioning
Read this when planning a price increase (strategy, timeline, communication, impact modeling) or positioning price against competitors, and for the pricing health diagnostics that flag when to act.
Price Increase Playbook
Strategy Selection
| Strategy | Risk Level | Use When |
|---|---|---|
| New customers only | Low | Testing market response, significant uncertainty |
| Grandfather + scheduled increase | Medium | Loyal customer base, want to preserve relationships |
| Tied to new value | Low | Clear product improvements justify the increase |
| Plan restructure | Medium | Packaging changes alongside price changes |
| Uniform increase | Medium-High | Price is clearly below market, confident in value |
Execution Timeline
| Week | Action |
|---|---|
| Week -12 | Decide strategy, model revenue impact at 80%, 90%, 100% retention |
| Week -8 | Segment customers by risk (annual contracts, champions vs detractors, usage level) |
| Week -6 | Prepare communication (email, in-app, FAQ, CS talking points) |
| Week -4 | Announce to existing customers (60+ day notice for annual contracts) |
| Week -4 | Offer lock-in: "Keep current price for 12 months with annual commitment" |
| Week 0 | New pricing goes live for new customers |
| Week +4 | Existing customer pricing changes (if not grandfathered) |
| Week +12 | Review: churn rate, downgrade rate, support ticket volume, revenue impact |
Communication Template
Subject: "Changes to your [Product] plan"
- Paragraph 1: What is changing and when
- Paragraph 2: Why (new features, investment in X, market alignment)
- Paragraph 3: What this means for them specifically (old price -> new price)
- Paragraph 4: Options (lock in current price with annual, downgrade, contact support)
- CTA: "View your options" or "Talk to your account manager"
Expected Impact
For a 20-30% price increase:
- Expected churn: 5-15% of affected customers
- Revenue impact: Net positive if churn < (increase % / (100% + increase %))
- Example: 25% increase is net positive if churn < 20%
---
Competitive Pricing Analysis
Pricing Position Map
Premium ($$$)
│
│
[Competitor B] │ [Your Product?]
│
Narrow ─────────────────┼──────────────────── Broad
Feature Set │ Feature Set
│
[Competitor C] │ [Competitor A]
│
│
Value ($)Positioning Strategy
| Your Position | Pricing Approach | Messaging |
|---|---|---|
| Premium | 20-40% above market average | "The [category] built for teams that demand the best" |
| Value leader | At or slightly below market | "Enterprise features at [segment] prices" |
| Disruptor | Radically different model | "Why pay per seat? [Product] is [price] for unlimited users" |
| Challenger | Slightly below the leader | "Everything [Leader] does, at half the price" |
---
Pricing Signals and Diagnostics
Pricing Health Check
| Signal | Diagnosis | Action |
|---|---|---|
| Trial-to-paid > 40% | Underpriced | Test 20-30% increase |
| All customers on middle tier | No upsell path | Add enterprise features or higher tier |
| Customers never ask about price | Too cheap | Increase price |
| Churn rate > 5% monthly | Fix churn before pricing changes | Use churn-prevention first |
| Price unchanged for 2+ years | Inflation alone justifies 10-15% increase | Plan an increase |
| Only one pricing option | No anchoring, no upsell | Add tiers |
| Frequent discount requests | Possible overpricing or poor value communication | Audit value proposition |
Pricing Models — Axes, Value Metrics, Tiers, Value-Based Pricing
Read this when designing pricing from scratch or restructuring tiers: the three pricing axes (in order), how to pick a value metric, how to architect tiers, and how to price inside the value corridor.
The Three Pricing Axes
Every pricing decision lives across three axes. Most teams skip to price point. That is backwards.
┌──────────────────┐
│ VALUE METRIC │ What do you charge for?
│ (how it scales) │ (per seat, per usage, per feature)
└────────┬─────────┘
│
┌────────┴─────────┐
│ PACKAGING │ What is in each tier?
│ (what you get) │ (feature bundles, limits, support levels)
└────────┬─────────┘
│
┌────────┴─────────┐
│ PRICE POINT │ How much?
│ (the number) │ (actual dollar amount)
└──────────────────┘Lock in the value metric first, then packaging, then test the price point.
---
Value Metric Selection
Common Value Metrics
| Metric | Best For | Examples | Scales With Value? |
|---|---|---|---|
| Per seat / user | Collaboration tools, CRMs | Salesforce, Notion, Linear | Yes if all users are active |
| Per usage | APIs, infrastructure, AI | Stripe, Twilio, OpenAI | Yes |
| Per feature | Platform plays, modular products | HubSpot, Intercom | Somewhat |
| Flat fee | Simple products, SMB market | Basecamp, Calendly | No (subsidizes heavy users) |
| Per outcome | Measurable ROI products | Commission-based tools | Perfectly |
| Hybrid | Most mature SaaS | Base fee + usage, seat + features | Yes |
Selection Criteria
Answer these 4 questions:
| Question | Answer Points To |
|---|---|
| What makes a customer willing to pay MORE? | That is your value metric |
| Does the metric scale with their success? | If they grow, you should grow |
| Is it easy to understand? | Complexity kills conversion |
| Is it hard to game? | Customers should not be able to work around it |
Value Metric Red Flags
| Red Flag | Problem | Fix |
|---|---|---|
| Per-seat in a tool where 1 power user does all the work | Seats do not scale with value | Switch to usage or feature-based |
| Flat fee when some customers get 10x the value of others | Subsidizing heavy users | Add usage tiers or hybrid model |
| Per-API-call when volume varies wildly week to week | Unpredictable bills cause churn | Add usage bands or committed minimums |
| Per-feature when core value requires multiple features | Nickel-and-diming perception | Bundle core features, gate advanced only |
---
Tier Architecture
Good-Better-Best (3 Tiers)
Three tiers is the standard because it anchors perception.
| Tier | Role | Pricing Rule | Feature Rule |
|---|---|---|---|
| Entry (Good) | Captures price-sensitive segment | Covers your costs minimum | Core product, limited usage |
| Middle (Better) | Where you push most customers | 2-3x entry tier | Everything a growing company needs |
| Top (Best) | High-value enterprise customers | 3-5x entry or custom | SSO, audit logs, SLA, dedicated support |
Feature Allocation Framework
| Feature Category | Entry Tier | Middle Tier | Top Tier |
|---|---|---|---|
| Core product | Limited | Full | Full |
| Usage limits | Low | Medium | High/Unlimited |
| Users/seats | 1-3 | 5-25 or unlimited | Unlimited |
| Integrations | Basic (3-5) | Full | Full + custom |
| Reporting | Basic | Advanced | Custom |
| Support | Email (48h) | Priority (24h) | Dedicated CSM |
| Admin features | -- | -- | SSO, SCIM, audit logs |
| SLA | -- | -- | 99.9% uptime |
| Data retention | 90 days | 1 year | Unlimited |
| API access | -- | Rate-limited | Full |
Tier Naming
| Approach | Examples | Best For |
|---|---|---|
| Size-based | Starter, Growth, Enterprise | Universal SaaS |
| Capability-based | Basic, Pro, Enterprise | Feature-differentiated products |
| Audience-based | Individual, Team, Organization | Collaboration tools |
| Persona-based | Freelancer, Agency, Enterprise | Audience-segmented products |
Naming rules:
- Names should be instantly understandable
- Avoid jargon or made-up words
- The default/recommended plan should be visually highlighted
---
Value-Based Pricing
The Pricing Corridor
[Cost floor] ... [Next-best alternative] ... [YOUR PRICE] ... [Perceived value]Step-by-Step
Step 1: Define the next-best alternative
- What would the customer do without your product?
- What does that cost them? (competitor, manual process, hiring)
Step 2: Estimate value delivered
- Time saved x hourly rate of the person using it
- Revenue generated or protected
- Cost of errors/risk avoided
- Ask customers: "What would you lose if you stopped using us?"
Step 3: Price in the corridor
- Price at 10-20% of documented value delivered
- Above the next-best alternative (signals confidence)
- Below the perceived value ceiling (customer feels good ROI)
Conversion Rate as a Pricing Signal
| Trial-to-Paid Rate | Signal | Action |
|---|---|---|
| > 40% | Likely underpriced | Test a 20-30% price increase |
| 15-30% | Healthy for most SaaS | Optimize packaging, not price |
| < 10% | Possibly overpriced OR trial experience is broken | Investigate whether the issue is price or activation |
Pricing Research & Page Design
Read this when you need to discover willingness-to-pay (Van Westendorp, MaxDiff, benchmarking, WTP interviews), design the pricing page, or decide between freemium and free trial.
Pricing Research Methods
Van Westendorp Price Sensitivity Meter
Four questions asked to 30+ current customers or qualified prospects:
1. At what price would this be so cheap you would question its quality? 2. At what price would this be a great deal? 3. At what price would this start to feel expensive but still acceptable? 4. At what price would this be too expensive to consider?
Interpretation: Plot four curves. The intersection of "too cheap" and "too expensive" gives the acceptable range. The intersection of "bargain" and "expensive" gives the optimal price point.
MaxDiff Analysis
Show respondents sets of features and ask which they value most and least. Reveals relative value of each feature for tier allocation decisions.
When to use: Deciding which features go in which tier.
Competitor Benchmarking
| Step | Action |
|---|---|
| 1 | List direct competitors and alternatives customers compare you to |
| 2 | Record published pricing (plans, prices, value metrics) |
| 3 | Note what is included at each tier |
| 4 | Identify where you over-deliver and under-deliver vs each |
| 5 | Position relative to market: premium (+20-40%), parity, value (-10-20%) |
Do not copy competitor prices. Their pricing reflects their cost structure and positioning, not yours.
Customer Willingness-to-Pay Interview
Ask existing customers (especially champions):
- "How would you describe the ROI of [product] to your CFO?"
- "What would you do if the price doubled? Tripled?"
- "What is the most you would pay before you would switch?"
- "If you had to cut 50% of your budget, would this survive?"
---
Pricing Page Design
Above the Fold
Required elements:
- Plan names with clear positioning
- Prices with monthly/annual toggle (annual shows savings: "Save 20%" or "2 months free")
- 3-5 bullet differentiators per plan
- CTA button per plan
- "Most Popular" or "Recommended" badge on the middle tier
- "Most Popular" plan should be the default tab/column
Below the Fold
- Full feature comparison table -- Comprehensive, scannable, uses checkmarks and X marks
- FAQ section -- The 5 objections that stop purchases:
1. "Can I cancel anytime?" 2. "What happens when I hit limits?" 3. "Do you offer refunds?" 4. "Is my data secure?" 5. "Can I switch plans later?"
- Social proof -- Logos, testimonials, case studies relevant to each tier
- Security badges -- SOC2, ISO 27001, GDPR (if applicable)
Annual vs Monthly Toggle
- Default to showing annual pricing (it improves LTV)
- Show savings explicitly: "Save 20%" or "$X/year (saves $Y)"
- Do NOT hide monthly pricing -- hiding it creates distrust
- Monthly/annual toggle should be above the plan cards
Enterprise Tier Design
| Approach | When to Use |
|---|---|
| Published price | When enterprise pricing is standardized |
| "Contact Sales" | When pricing requires custom scoping |
| "Starting at $X" | Balance transparency with flexibility |
---
Freemium vs Free Trial Decision
| Factor | Freemium | Free Trial |
|---|---|---|
| Product complexity | Simple, quick time-to-value | Complex, needs exploration |
| Network effects | Strong (value increases with users) | Weak |
| Market size | Very large TAM, need top-of-funnel | Focused market |
| Sales motion | Self-serve, product-led | Sales-assisted |
| Conversion rate target | 2-5% free-to-paid | 15-30% trial-to-paid |
| Revenue urgency | Can afford long payback | Need revenue sooner |
Freemium Tier Design Rules
- Free tier must deliver real, ongoing value (not a crippled experience)
- The limit that triggers upgrade should be a natural success indicator
- Free users should be able to see what paid features look like (soft gates, previews)
- Do not remove value from free tier once established (erodes trust)
Output Artifacts, Tools, Troubleshooting & Success Criteria
Read this when producing deliverables, running the Python scripts, debugging a pricing problem, checking success criteria, or avoiding common anti-patterns.
Output Artifacts
| Artifact | Format | Description |
|---|---|---|
| Pricing Strategy Document | Structured analysis | Value metric, packaging, price points with rationale |
| Tier Architecture | Feature allocation table | What goes in each tier with justification |
| Pricing Page Specification | Layout + copy | Above-fold design, feature table, FAQ, toggle behavior |
| Price Increase Plan | Timeline + communications | Strategy selection, rollout schedule, email templates |
| Competitive Pricing Analysis | Comparison table + position map | Market pricing landscape with positioning recommendation |
| Van Westendorp Survey | Question set + interpretation guide | Ready-to-deploy pricing research |
| Pricing Health Scorecard | Signal + diagnosis table | Current pricing health assessment with action items |
---
Tool Reference
1. pricing_model_analyzer.py
Analyzes a SaaS pricing model against best practices. Evaluates value metric alignment, tier architecture, feature allocation, and identifies pricing anti-patterns. Outputs a health scorecard with prioritized recommendations.
python scripts/pricing_model_analyzer.py pricing.json --format text
python scripts/pricing_model_analyzer.py pricing.json --format json| Flag | Type | Description |
|---|---|---|
pricing.json | positional | Path to JSON file with pricing model configuration |
--format | optional | Output format: text (default) or json |
2. price_sensitivity_calculator.py
Implements the Van Westendorp Price Sensitivity Meter. Takes survey responses (too cheap, bargain, expensive, too expensive) and calculates the optimal price point, acceptable price range, and indifference price point.
python scripts/price_sensitivity_calculator.py survey.json --format text
python scripts/price_sensitivity_calculator.py survey.json --format json| Flag | Type | Description |
|---|---|---|
survey.json | positional | Path to JSON file with Van Westendorp survey responses |
--format | optional | Output format: text (default) or json |
3. price_increase_modeler.py
Models the revenue impact of price increases at various retention scenarios. Takes current customer base, pricing, and proposed increase, then projects revenue impact at 80%, 90%, and 100% retention with break-even analysis.
python scripts/price_increase_modeler.py increase.json --format text
python scripts/price_increase_modeler.py increase.json --format json| Flag | Type | Description |
|---|---|---|
increase.json | positional | Path to JSON file with price increase scenario data |
--format | optional | Output format: text (default) or json |
---
Troubleshooting
| Problem | Likely Cause | Resolution |
|---|---|---|
| Trial-to-paid conversion above 40% | Product is likely underpriced -- customers convert too easily because price is well below perceived value | Test a 20-30% price increase on new customers first; monitor conversion rate and revenue per user |
| All customers concentrate on middle tier | No compelling reason to upgrade to top tier; enterprise features missing or unclear | Add SSO, audit logs, dedicated support, SLA, and custom integrations to top tier; ensure 3-5x price jump from middle |
| Frequent discount requests from prospects | Price may exceed perceived value, or value proposition is poorly communicated | Audit sales collateral for ROI messaging; consider adding a lighter entry tier rather than discounting |
| Price unchanged for 2+ years | Inflation alone justifies 10-15% increase; likely leaving significant revenue on the table | Plan a structured price increase using the execution timeline; start with new customers only to test |
| High involuntary churn on usage-based pricing | Unpredictable bills cause customers to cancel; usage spikes create bill shock | Add usage bands, committed minimums, or spending caps with alerts at 80% threshold |
| Customers game the value metric | Per-seat pricing with shared logins, or usage metrics that can be artificially reduced | Switch to a harder-to-game metric; add audit capabilities; consider hybrid model |
| Pricing page has low conversion but product is strong | Pricing page design issues (too many tiers, unclear differentiation, hidden annual toggle) | Simplify to 3 tiers, highlight recommended plan, show annual savings prominently, add FAQ |
---
Success Criteria
- Trial-to-paid conversion rate stabilizes at 15-30% (healthy SaaS range) after pricing optimization
- Tier distribution shows healthy spread: 20-30% entry, 50-60% middle, 15-25% top tier
- Net revenue retention exceeds 110% (expansion revenue from upsells outpaces contraction)
- Price increase execution retains 85%+ of affected customers within 90 days
- Annual plan adoption reaches 50%+ when toggle defaults to annual pricing
- Van Westendorp survey confirms current price falls within the acceptable range for 70%+ of respondents
- Pricing page conversion rate improves by 15%+ after redesign implementing best practices
---
Anti-patterns
| Anti-pattern | Failure mode | Fix |
|---|---|---|
| Jumping to the price point before locking the value metric | Discounts and "just lower the price" become the only lever; packaging is stuck | Work the axes in order: value metric → packaging → price point |
| Copying a competitor's pricing model | Inherits their positioning and unit economics — which may not fit the product | Use competitor pricing as a data point for the corridor, not a template |
| Per-seat pricing on a tool where one power user does the work | Usage grows but seats don't; revenue stalls | Switch to usage-based, hybrid (base + usage), or per-feature |
| Raising prices to fix a churn problem | Churn accelerates; pricing gets blamed for a retention problem | Diagnose churn drivers first; if the product is the issue, price increases amplify the damage |
| Adding a fourth tier to "capture more willingness to pay" | Paradox of choice collapses conversion; sales cycle lengthens | Keep 3 tiers public; put the fourth behind "Contact Sales" if enterprise-specific |
| Announcing a price increase without grandfathering existing customers | Immediate churn spike; NPS collapse; public backlash | Grandfather for 6-12 months on annual plans; communicate 90+ days in advance |
| Using MSRP or list price internally for forecasting | Actual ACV diverges from list by 20-40% due to discounts; forecasts miss | Forecast on expected-realized price net of standard discount, not list |
| Freemium tier that gives away the core value metric | Free users never convert; paid tier cannibalized | Gate the value metric (volume, seats, integrations) — not feature access only |
#!/usr/bin/env python3
"""Price Increase Modeler - Model revenue impact of price increases at various retention scenarios.
Takes current customer base, pricing, and proposed increase, then projects revenue
impact at multiple retention scenarios with break-even analysis.
Usage:
python price_increase_modeler.py increase.json
python price_increase_modeler.py increase.json --format json
"""
import argparse
import json
import sys
from typing import Any
def safe_divide(num: float, den: float, default: float = 0.0) -> float:
"""Safely divide."""
return num / den if den != 0 else default
def model_increase(data: dict) -> dict:
"""Model a price increase across multiple retention scenarios."""
current = data.get("current", {})
proposed = data.get("proposed", {})
current_price = current.get("price_monthly", 0)
current_customers = current.get("customer_count", 0)
current_mrr = current_price * current_customers
new_price = proposed.get("new_price_monthly", 0)
increase_pct = safe_divide(new_price - current_price, current_price) * 100
# Affected vs unaffected customers
affected_pct = proposed.get("affected_customer_pct", 100) / 100
affected_customers = int(current_customers * affected_pct)
unaffected_customers = current_customers - affected_customers
# Retention scenarios
retention_rates = proposed.get("retention_scenarios", [100, 95, 90, 85, 80])
scenarios = []
for retention_pct in retention_rates:
retention = retention_pct / 100
retained_customers = int(affected_customers * retention)
churned_customers = affected_customers - retained_customers
# Revenue calculation
new_mrr_affected = retained_customers * new_price
new_mrr_unaffected = unaffected_customers * current_price
new_total_mrr = new_mrr_affected + new_mrr_unaffected
mrr_change = new_total_mrr - current_mrr
mrr_change_pct = safe_divide(mrr_change, current_mrr) * 100
annual_impact = mrr_change * 12
# Revenue from increase vs revenue lost from churn
revenue_gained = retained_customers * (new_price - current_price)
revenue_lost = churned_customers * current_price
scenario = {
"retention_pct": retention_pct,
"retained_customers": retained_customers,
"churned_customers": churned_customers,
"new_mrr": round(new_total_mrr, 2),
"mrr_change": round(mrr_change, 2),
"mrr_change_pct": round(mrr_change_pct, 2),
"annual_revenue_impact": round(annual_impact, 2),
"revenue_gained_from_increase": round(revenue_gained, 2),
"revenue_lost_from_churn": round(revenue_lost, 2),
"net_positive": mrr_change > 0,
}
scenarios.append(scenario)
# Break-even calculation
# At what retention rate does the increase become net-negative?
# Break-even: retained * new_price = affected * current_price
# retention_break_even = (affected * current_price) / (affected * new_price)
if new_price > 0:
break_even_retention = safe_divide(current_price, new_price) * 100
else:
break_even_retention = 0
# Lock-in offer analysis
lock_in = proposed.get("lock_in_offer", {})
lock_in_analysis = None
if lock_in:
lock_in_uptake = lock_in.get("expected_uptake_pct", 30) / 100
lock_in_months = lock_in.get("lock_in_months", 12)
lock_in_price = lock_in.get("lock_in_price_monthly", current_price)
locked_customers = int(affected_customers * lock_in_uptake)
non_locked = affected_customers - locked_customers
# Revenue during lock-in period
locked_revenue = locked_customers * lock_in_price * lock_in_months
# Non-locked face the increase (assume 90% retention)
non_locked_retained = int(non_locked * 0.9)
non_locked_revenue = non_locked_retained * new_price * lock_in_months
total_lock_in_revenue = locked_revenue + non_locked_revenue
baseline_revenue = affected_customers * current_price * lock_in_months
lock_in_analysis = {
"locked_in_customers": locked_customers,
"lock_in_price": lock_in_price,
"lock_in_period_months": lock_in_months,
"revenue_during_lock_in": round(total_lock_in_revenue, 2),
"baseline_revenue_same_period": round(baseline_revenue, 2),
"net_impact": round(total_lock_in_revenue - baseline_revenue, 2),
}
# Timeline
timeline = [
{"week": -12, "action": "Decide strategy, model revenue impact"},
{"week": -8, "action": "Segment customers by risk (annual, champions, detractors, usage)"},
{"week": -6, "action": "Prepare communications (email, in-app, FAQ, CS talking points)"},
{"week": -4, "action": "Announce to existing customers (60+ day notice for annual)"},
{"week": 0, "action": "New pricing live for new customers"},
{"week": 4, "action": "Existing customer pricing changes (if not grandfathered)"},
{"week": 12, "action": "Review: churn rate, downgrade rate, support tickets, revenue"},
]
return {
"summary": {
"current_price": current_price,
"new_price": new_price,
"increase_pct": round(increase_pct, 1),
"current_mrr": round(current_mrr, 2),
"total_customers": current_customers,
"affected_customers": affected_customers,
"break_even_retention_pct": round(break_even_retention, 1),
},
"scenarios": scenarios,
"lock_in_analysis": lock_in_analysis,
"recommended_timeline": timeline,
}
def format_text(result: dict) -> str:
"""Format model results as human-readable text."""
lines = []
s = result["summary"]
lines.append("=" * 60)
lines.append("PRICE INCREASE IMPACT MODEL")
lines.append("=" * 60)
lines.append("")
lines.append(f"Current Price: ${s['current_price']:>10,.2f}/mo")
lines.append(f"New Price: ${s['new_price']:>10,.2f}/mo (+{s['increase_pct']}%)")
lines.append(f"Current MRR: ${s['current_mrr']:>10,.2f}")
lines.append(f"Total Customers: {s['total_customers']:>10,}")
lines.append(f"Affected: {s['affected_customers']:>10,}")
lines.append(f"Break-even: {s['break_even_retention_pct']:>10.1f}% retention")
lines.append("")
lines.append("-" * 60)
lines.append("RETENTION SCENARIOS")
lines.append("-" * 60)
lines.append(f"{'Retention':<12} {'New MRR':>12} {'MRR Change':>14} {'Annual':>14} {'Net?':>8}")
lines.append("-" * 60)
for sc in result["scenarios"]:
net = "+" if sc["net_positive"] else "-"
lines.append(
f"{sc['retention_pct']:>6}% "
f"${sc['new_mrr']:>10,.2f} "
f"${sc['mrr_change']:>+12,.2f} "
f"${sc['annual_revenue_impact']:>+12,.2f} "
f" {net}"
)
lines.append("")
for sc in result["scenarios"]:
if not sc["net_positive"]:
lines.append(f" >> Net-negative below {sc['retention_pct']+5}% retention")
break
if result.get("lock_in_analysis"):
li = result["lock_in_analysis"]
lines.append("")
lines.append("-" * 40)
lines.append("LOCK-IN OFFER ANALYSIS")
lines.append("-" * 40)
lines.append(f" Customers who lock in: {li['locked_in_customers']:,}")
lines.append(f" Lock-in price: ${li['lock_in_price']:,.2f}/mo for {li['lock_in_period_months']} months")
lines.append(f" Revenue during period: ${li['revenue_during_lock_in']:,.2f}")
lines.append(f" Baseline same period: ${li['baseline_revenue_same_period']:,.2f}")
lines.append(f" Net impact: ${li['net_impact']:+,.2f}")
lines.append("")
lines.append("-" * 40)
lines.append("RECOMMENDED TIMELINE")
lines.append("-" * 40)
for step in result["recommended_timeline"]:
week = step["week"]
prefix = f"Week {week:+d}" if week != 0 else "Week 0 "
lines.append(f" {prefix:>10}: {step['action']}")
lines.append("")
return "\n".join(lines)
def main() -> None:
"""Main entry point."""
parser = argparse.ArgumentParser(
description="Model revenue impact of price increases at various retention scenarios."
)
parser.add_argument(
"input_file",
help="Path to JSON file with price increase scenario data",
)
parser.add_argument(
"--format",
choices=["text", "json"],
default="text",
help="Output format (default: text)",
)
args = parser.parse_args()
try:
with open(args.input_file, "r") as f:
data = json.load(f)
except FileNotFoundError:
print(f"Error: File not found: {args.input_file}", file=sys.stderr)
sys.exit(1)
except json.JSONDecodeError as e:
print(f"Error: Invalid JSON: {e}", file=sys.stderr)
sys.exit(1)
result = model_increase(data)
if args.format == "json":
print(json.dumps(result, indent=2))
else:
print(format_text(result))
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""Price Sensitivity Calculator - Van Westendorp Price Sensitivity Meter implementation.
Takes survey responses for the four Van Westendorp questions and calculates:
optimal price point, indifference price, acceptable range, and point of marginal
cheapness/expensiveness.
Usage:
python price_sensitivity_calculator.py survey.json
python price_sensitivity_calculator.py survey.json --format json
"""
import argparse
import json
import sys
from typing import Any
def calculate_cumulative_distribution(values: list[float], price_points: list[float], ascending: bool = True) -> list[float]:
"""Calculate cumulative distribution at each price point.
Args:
values: Raw survey responses (price values from respondents).
price_points: The set of price points to evaluate.
ascending: If True, cumulative from low to high (% who said this or less).
If False, cumulative from high to low (% who said this or more).
Returns:
List of cumulative percentages (0-100) at each price point.
"""
n = len(values)
if n == 0:
return [0.0] * len(price_points)
sorted_vals = sorted(values)
result = []
for pp in price_points:
if ascending:
count = sum(1 for v in sorted_vals if v <= pp)
else:
count = sum(1 for v in sorted_vals if v >= pp)
result.append((count / n) * 100)
return result
def find_intersection(x_points: list[float], y1: list[float], y2: list[float]) -> float | None:
"""Find approximate intersection point of two curves using linear interpolation."""
for i in range(len(x_points) - 1):
diff1 = y1[i] - y2[i]
diff2 = y1[i + 1] - y2[i + 1]
if diff1 == 0:
return x_points[i]
if diff2 == 0:
return x_points[i + 1]
# Sign change indicates intersection
if (diff1 > 0 and diff2 < 0) or (diff1 < 0 and diff2 > 0):
# Linear interpolation
t = diff1 / (diff1 - diff2)
return x_points[i] + t * (x_points[i + 1] - x_points[i])
return None
def analyze_sensitivity(data: dict) -> dict:
"""Run Van Westendorp price sensitivity analysis."""
responses = data.get("responses", [])
if len(responses) < 10:
return {"error": f"Need at least 10 responses for valid analysis (got {len(responses)}). Recommended: 30+."}
# Extract the four price arrays
too_cheap = [r["too_cheap"] for r in responses if "too_cheap" in r]
bargain = [r["bargain"] for r in responses if "bargain" in r]
expensive = [r["expensive"] for r in responses if "expensive" in r]
too_expensive = [r["too_expensive"] for r in responses if "too_expensive" in r]
if not all([too_cheap, bargain, expensive, too_expensive]):
return {"error": "Each response must have: too_cheap, bargain, expensive, too_expensive"}
# Validate ordering within each response
validation_issues = []
for i, r in enumerate(responses):
if not all(k in r for k in ["too_cheap", "bargain", "expensive", "too_expensive"]):
continue
if not (r["too_cheap"] <= r["bargain"] <= r["expensive"] <= r["too_expensive"]):
validation_issues.append(f"Response {i+1}: prices not in expected order (too_cheap <= bargain <= expensive <= too_expensive)")
# Generate price points for analysis
all_prices = too_cheap + bargain + expensive + too_expensive
min_price = min(all_prices)
max_price = max(all_prices)
# Create fine-grained price points
step = max((max_price - min_price) / 200, 0.01)
price_points = []
p = min_price
while p <= max_price:
price_points.append(round(p, 2))
p += step
# Calculate cumulative distributions
# "Too cheap" - % who said this price or HIGHER is too cheap (descending)
too_cheap_cum = calculate_cumulative_distribution(too_cheap, price_points, ascending=False)
# "Bargain" - % who said this price or HIGHER is a bargain (descending)
bargain_cum = calculate_cumulative_distribution(bargain, price_points, ascending=False)
# "Expensive" - % who said this price or LOWER is expensive (ascending)
expensive_cum = calculate_cumulative_distribution(expensive, price_points, ascending=True)
# "Too expensive" - % who said this price or LOWER is too expensive (ascending)
too_expensive_cum = calculate_cumulative_distribution(too_expensive, price_points, ascending=True)
# Find key intersection points
# OPP (Optimal Price Point): intersection of "too cheap" and "too expensive"
opp = find_intersection(price_points, too_cheap_cum, too_expensive_cum)
# IDP (Indifference Price Point): intersection of "bargain" and "expensive"
idp = find_intersection(price_points, bargain_cum, expensive_cum)
# PMC (Point of Marginal Cheapness): intersection of "too cheap" and "expensive"
pmc = find_intersection(price_points, too_cheap_cum, expensive_cum)
# PME (Point of Marginal Expensiveness): intersection of "bargain" and "too expensive"
pme = find_intersection(price_points, bargain_cum, too_expensive_cum)
# Summary statistics
summary_stats = {
"respondent_count": len(responses),
"too_cheap_median": round(sorted(too_cheap)[len(too_cheap) // 2], 2),
"bargain_median": round(sorted(bargain)[len(bargain) // 2], 2),
"expensive_median": round(sorted(expensive)[len(expensive) // 2], 2),
"too_expensive_median": round(sorted(too_expensive)[len(too_expensive) // 2], 2),
}
# Recommendations
recommendations = []
if opp:
recommendations.append(f"Optimal Price Point (OPP): ${opp:.2f} -- the price that maximizes the number of buyers while minimizing resistance")
if pmc and pme:
recommendations.append(f"Acceptable Price Range: ${pmc:.2f} to ${pme:.2f} -- pricing outside this range risks losing significant market share")
if idp:
recommendations.append(f"Indifference Price Point: ${idp:.2f} -- equal number of respondents find this cheap vs expensive")
if opp and pmc and pme:
if pmc <= opp <= pme:
recommendations.append("OPP falls within the acceptable range -- pricing model is viable")
else:
recommendations.append("WARNING: OPP falls outside the acceptable range -- review survey data quality")
return {
"key_price_points": {
"optimal_price_point": round(opp, 2) if opp else None,
"indifference_price_point": round(idp, 2) if idp else None,
"point_of_marginal_cheapness": round(pmc, 2) if pmc else None,
"point_of_marginal_expensiveness": round(pme, 2) if pme else None,
"acceptable_range": {
"low": round(pmc, 2) if pmc else None,
"high": round(pme, 2) if pme else None,
},
},
"summary_stats": summary_stats,
"recommendations": recommendations,
"validation_issues": validation_issues[:5], # Cap at 5
"methodology": "Van Westendorp Price Sensitivity Meter (4-question model)",
}
def format_text(result: dict) -> str:
"""Format analysis as human-readable text."""
lines = []
if "error" in result:
return f"Error: {result['error']}"
lines.append("=" * 60)
lines.append("VAN WESTENDORP PRICE SENSITIVITY ANALYSIS")
lines.append("=" * 60)
lines.append("")
kp = result["key_price_points"]
lines.append("-" * 40)
lines.append("KEY PRICE POINTS")
lines.append("-" * 40)
lines.append(f" Optimal Price Point (OPP): ${kp['optimal_price_point']:>8.2f}" if kp["optimal_price_point"] else " Optimal Price Point: Could not be calculated")
lines.append(f" Indifference Price (IDP): ${kp['indifference_price_point']:>8.2f}" if kp["indifference_price_point"] else " Indifference Price: Could not be calculated")
lines.append(f" Marginal Cheapness (PMC): ${kp['point_of_marginal_cheapness']:>8.2f}" if kp["point_of_marginal_cheapness"] else " Marginal Cheapness: Could not be calculated")
lines.append(f" Marginal Expensiveness (PME): ${kp['point_of_marginal_expensiveness']:>8.2f}" if kp["point_of_marginal_expensiveness"] else " Marginal Expensiveness: Could not be calculated")
ar = kp.get("acceptable_range", {})
if ar.get("low") and ar.get("high"):
lines.append(f"\n ACCEPTABLE RANGE: ${ar['low']:.2f} - ${ar['high']:.2f}")
lines.append("")
ss = result["summary_stats"]
lines.append("-" * 40)
lines.append(f"SURVEY SUMMARY ({ss['respondent_count']} respondents)")
lines.append("-" * 40)
lines.append(f" Too Cheap median: ${ss['too_cheap_median']:>8.2f}")
lines.append(f" Bargain median: ${ss['bargain_median']:>8.2f}")
lines.append(f" Expensive median: ${ss['expensive_median']:>8.2f}")
lines.append(f" Too Expensive median: ${ss['too_expensive_median']:>8.2f}")
lines.append("")
if result["recommendations"]:
lines.append("-" * 40)
lines.append("RECOMMENDATIONS")
lines.append("-" * 40)
for rec in result["recommendations"]:
lines.append(f" - {rec}")
lines.append("")
if result["validation_issues"]:
lines.append("-" * 40)
lines.append("VALIDATION WARNINGS")
lines.append("-" * 40)
for vi in result["validation_issues"]:
lines.append(f" - {vi}")
lines.append("")
return "\n".join(lines)
def main() -> None:
"""Main entry point."""
parser = argparse.ArgumentParser(
description="Van Westendorp Price Sensitivity Meter calculator."
)
parser.add_argument(
"input_file",
help="Path to JSON file with Van Westendorp survey responses",
)
parser.add_argument(
"--format",
choices=["text", "json"],
default="text",
help="Output format (default: text)",
)
args = parser.parse_args()
try:
with open(args.input_file, "r") as f:
data = json.load(f)
except FileNotFoundError:
print(f"Error: File not found: {args.input_file}", file=sys.stderr)
sys.exit(1)
except json.JSONDecodeError as e:
print(f"Error: Invalid JSON: {e}", file=sys.stderr)
sys.exit(1)
result = analyze_sensitivity(data)
if args.format == "json":
print(json.dumps(result, indent=2))
else:
print(format_text(result))
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""Pricing Model Analyzer - Evaluate SaaS pricing model against best practices.
Analyzes value metric alignment, tier architecture, feature allocation, pricing
signals, and anti-patterns. Outputs a health scorecard with prioritized recommendations.
Usage:
python pricing_model_analyzer.py pricing.json
python pricing_model_analyzer.py pricing.json --format json
"""
import argparse
import json
import sys
from typing import Any
VALID_VALUE_METRICS = [
"per_seat", "per_usage", "per_feature", "flat_fee", "per_outcome", "hybrid",
]
ENTERPRISE_FEATURES = [
"sso", "scim", "audit_logs", "sla", "dedicated_support",
"custom_integrations", "api_access", "advanced_security",
]
TIER_COUNT_GUIDANCE = {
1: {"rating": "Poor", "issue": "Single tier provides no upsell path or anchoring"},
2: {"rating": "Fair", "issue": "Two tiers lack the anchoring effect of Good-Better-Best"},
3: {"rating": "Excellent", "issue": None},
4: {"rating": "Good", "issue": "Four tiers acceptable but verify differentiation is clear"},
}
def safe_divide(num: float, den: float, default: float = 0.0) -> float:
"""Safely divide."""
return num / den if den != 0 else default
def analyze_value_metric(data: dict) -> dict:
"""Analyze value metric selection."""
metric = data.get("value_metric", "")
issues = []
score = 0
if not metric:
return {"score": 0, "issues": ["No value metric defined"], "metric": "unknown"}
if metric not in VALID_VALUE_METRICS:
issues.append(f"Unknown value metric: {metric}")
else:
score += 20
# Check if metric scales with value
if data.get("scales_with_customer_value", False):
score += 30
else:
issues.append("Value metric may not scale with customer success -- review if larger customers pay proportionally more")
# Check if metric is easy to understand
if data.get("metric_easy_to_understand", True):
score += 25
else:
issues.append("Complex value metric reduces conversion -- simplify or explain clearly on pricing page")
# Check for gaming risk
if data.get("metric_gameable", False):
score -= 15
issues.append("Value metric can be gamed by customers (e.g., shared seats, artificial usage reduction)")
# Red flag checks
if metric == "per_seat" and data.get("single_power_user_common", False):
issues.append("Per-seat pricing with single power user pattern -- seats do not scale with value; consider usage-based")
score -= 10
if metric == "flat_fee" and data.get("usage_varies_10x", False):
issues.append("Flat fee with 10x usage variance subsidizes heavy users -- add usage tiers or hybrid model")
score -= 10
return {"score": min(max(score, 0), 100), "issues": issues, "metric": metric}
def analyze_tiers(data: dict) -> dict:
"""Analyze tier architecture."""
tiers = data.get("tiers", [])
issues = []
score = 0
tier_count = len(tiers)
# Tier count
guidance = TIER_COUNT_GUIDANCE.get(tier_count)
if guidance:
if guidance["issue"]:
issues.append(guidance["issue"])
if guidance["rating"] in ("Excellent", "Good"):
score += 30
elif guidance["rating"] == "Fair":
score += 15
elif tier_count > 4:
score += 10
issues.append(f"{tier_count} tiers is too many -- simplify to 3-4 (3-4 tiers outperform 5+ in conversion)")
elif tier_count == 0:
return {"score": 0, "issues": ["No tiers defined"], "tier_analysis": []}
# Price jumps between tiers
tier_analysis = []
for i, tier in enumerate(tiers):
tier_info = {
"name": tier.get("name", f"Tier {i+1}"),
"price": tier.get("price_monthly", 0),
"features": tier.get("features", []),
}
if i > 0:
prev_price = tiers[i-1].get("price_monthly", 0)
if prev_price > 0:
multiplier = safe_divide(tier.get("price_monthly", 0), prev_price)
tier_info["price_multiplier"] = round(multiplier, 1)
if multiplier < 1.5:
issues.append(f"{tier_info['name']} is only {multiplier:.1f}x previous tier -- target 2-3x jumps")
elif multiplier > 5:
issues.append(f"{tier_info['name']} is {multiplier:.1f}x previous tier -- large gap may lose mid-market customers")
else:
score += 10
tier_analysis.append(tier_info)
# Enterprise features check (top tier)
if tiers:
top_tier = tiers[-1]
top_features = [f.lower() for f in top_tier.get("features", [])]
missing_enterprise = []
for ef in ENTERPRISE_FEATURES:
if not any(ef in f for f in top_features):
missing_enterprise.append(ef)
if missing_enterprise and tier_count >= 3:
issues.append(f"Top tier missing enterprise features: {', '.join(missing_enterprise[:4])}")
else:
score += 15
# Recommended plan highlight
has_recommended = any(t.get("is_recommended", False) for t in tiers)
if not has_recommended and tier_count >= 3:
issues.append("No tier marked as 'recommended' -- highlight the middle tier to anchor purchasing decisions")
elif has_recommended:
score += 10
return {"score": min(max(score, 0), 100), "issues": issues, "tier_analysis": tier_analysis}
def analyze_pricing_signals(data: dict) -> dict:
"""Analyze pricing health signals."""
signals = data.get("signals", {})
issues = []
diagnostics = []
trial_conversion = signals.get("trial_to_paid_pct")
if trial_conversion is not None:
if trial_conversion > 40:
diagnostics.append({"signal": "Trial-to-paid > 40%", "diagnosis": "Likely underpriced", "action": "Test 20-30% increase"})
elif 15 <= trial_conversion <= 30:
diagnostics.append({"signal": f"Trial-to-paid {trial_conversion}%", "diagnosis": "Healthy range", "action": "Optimize packaging, not price"})
elif trial_conversion < 10:
diagnostics.append({"signal": f"Trial-to-paid {trial_conversion}%", "diagnosis": "Possibly overpriced or activation issue", "action": "Investigate whether price or trial experience is the problem"})
monthly_churn = signals.get("monthly_churn_pct")
if monthly_churn is not None and monthly_churn > 5:
diagnostics.append({"signal": f"Monthly churn {monthly_churn}%", "diagnosis": "Fix churn before pricing changes", "action": "Use churn-prevention skill first"})
years_since_change = signals.get("years_since_price_change")
if years_since_change is not None and years_since_change >= 2:
diagnostics.append({"signal": f"Price unchanged for {years_since_change} years", "diagnosis": "Inflation alone justifies 10-15%", "action": "Plan a price increase"})
annual_adoption = signals.get("annual_plan_adoption_pct")
if annual_adoption is not None and annual_adoption < 40:
diagnostics.append({"signal": f"Annual adoption {annual_adoption}%", "diagnosis": "Annual plan underperforming", "action": "Default toggle to annual, show savings prominently ('Save 20%' or '2 months free')"})
return {"diagnostics": diagnostics, "issues": issues}
def analyze_pricing(data: dict) -> dict:
"""Run full pricing model analysis."""
vm_result = analyze_value_metric(data)
tier_result = analyze_tiers(data)
signal_result = analyze_pricing_signals(data)
# Overall score
overall_score = int((vm_result["score"] + tier_result["score"]) / 2)
all_issues = vm_result["issues"] + tier_result["issues"] + signal_result["issues"]
if overall_score >= 75:
rating = "Healthy"
elif overall_score >= 50:
rating = "Needs Optimization"
elif overall_score >= 25:
rating = "Significant Issues"
else:
rating = "Critical"
return {
"summary": {
"overall_score": overall_score,
"rating": rating,
"total_issues": len(all_issues),
"value_metric": vm_result["metric"],
"tier_count": len(data.get("tiers", [])),
},
"value_metric_analysis": vm_result,
"tier_analysis": tier_result,
"pricing_signals": signal_result,
"all_issues": all_issues,
}
def format_text(result: dict) -> str:
"""Format analysis as human-readable text."""
lines = []
s = result["summary"]
lines.append("=" * 60)
lines.append("PRICING MODEL ANALYSIS")
lines.append("=" * 60)
lines.append("")
lines.append(f"Overall Score: {s['overall_score']}/100 ({s['rating']})")
lines.append(f"Value Metric: {s['value_metric']}")
lines.append(f"Tiers: {s['tier_count']}")
lines.append(f"Issues Found: {s['total_issues']}")
lines.append("")
# Value metric
vm = result["value_metric_analysis"]
lines.append("-" * 40)
lines.append(f"VALUE METRIC ({vm['score']}/100)")
lines.append("-" * 40)
for issue in vm["issues"]:
lines.append(f" - {issue}")
if not vm["issues"]:
lines.append(" No issues found.")
lines.append("")
# Tiers
ta = result["tier_analysis"]
lines.append("-" * 40)
lines.append(f"TIER ARCHITECTURE ({ta['score']}/100)")
lines.append("-" * 40)
for tier in ta.get("tier_analysis", []):
mult = f" ({tier['price_multiplier']}x prev)" if "price_multiplier" in tier else ""
lines.append(f" {tier['name']}: ${tier['price']:,.0f}/mo{mult}")
lines.append("")
for issue in ta["issues"]:
lines.append(f" - {issue}")
lines.append("")
# Signals
ps = result["pricing_signals"]
if ps["diagnostics"]:
lines.append("-" * 40)
lines.append("PRICING SIGNALS")
lines.append("-" * 40)
for diag in ps["diagnostics"]:
lines.append(f"\n Signal: {diag['signal']}")
lines.append(f" Diagnosis: {diag['diagnosis']}")
lines.append(f" Action: {diag['action']}")
lines.append("")
return "\n".join(lines)
def main() -> None:
"""Main entry point."""
parser = argparse.ArgumentParser(
description="Analyze SaaS pricing model against best practices."
)
parser.add_argument(
"input_file",
help="Path to JSON file with pricing model configuration",
)
parser.add_argument(
"--format",
choices=["text", "json"],
default="text",
help="Output format (default: text)",
)
args = parser.parse_args()
try:
with open(args.input_file, "r") as f:
data = json.load(f)
except FileNotFoundError:
print(f"Error: File not found: {args.input_file}", file=sys.stderr)
sys.exit(1)
except json.JSONDecodeError as e:
print(f"Error: Invalid JSON: {e}", file=sys.stderr)
sys.exit(1)
result = analyze_pricing(data)
if args.format == "json":
print(json.dumps(result, indent=2))
else:
print(format_text(result))
if __name__ == "__main__":
main()
Related skills
FAQ
What order should I make pricing decisions in?
Lock the value metric first, then packaging, then test the price point; the skill notes most teams skip to price point, which is backwards.
How many respondents does a Van Westendorp survey need?
The skill states a minimum of 30 survey respondents for statistical validity.