pricing-finder について
pricing-finder は、創業者やプロダクトチーム向けに競合の価格情報を調査・比較するスキルです。取得したページや検索スニペットを根拠に、価格分析と提案を行います。
主な機能
- 01比較対象となる競合を5社発見
- 02競合の価格ページを取得
- 03モデル、価格、機能制限を比較
- 04価格ポジショニングを可視化
- 05ベンチマークに基づく価格提案
- 06価格情報を出典にひも付け
活用シーン
- 01価格設定前のベンチマーク
- 02競合プランと機能制限の比較
- 03出典付き価格レビューの準備
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ varnan-tech/ opendirectory --skill pricing-finderインストール後、Claude Code で/pricing-finderを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
.env.exampleREADME.mdrequirements.txtevals/evals.jsonreferences/extraction-guide.mdpositioning-guide.mdpricing-models.mdscripts/research.py詳細
Pricing Finder
製品の URL または説明を入力すると、競合他社を 5 社見つけ、実際の料金ページを取得し、完全な料金インテリジェンスレポートを返します。レポートには、その分野で主流の料金モデル、ベンチマーク価格表、機能の利用条件分析、ポジショニングマップ、製品に対する具体的な料金の推奨案が含まれます。
必須の API キーはありません。 無料の pip 依存パッケージだけで動作します。API キーを追加すると、品質が向上します。
ハルシネーションをゼロにする方針: 出力に含まれる価格、プラン名、機能の利用条件はすべて、取得した料金ページの内容または DuckDuckGo の検索スニペットに基づいている必要があります。これは次の内容に適用されます。
- 競合他社の価格:取得したページの内容からのみ、原文どおりに抽出します
- 「Contact Sales」:そのまま記録し、推定したり数値に置き換えたりしません
- プラン名:言い換えず、ページに記載されているとおりに転記します
- 機能一覧:製品知識から推測せず、ページの内容から抽出します
- ポジショニングに関する所見:ベンチマーク表のデータのみをもとに導き出します
よくある間違い
| エージェントがしようとすること | それが間違いである理由 |
|---|---|
| 「Contact Sales」を推定価格で補う | エンタープライズ向けの料金を推定してはいけません。「Contact Sales」とそのまま記録します。 |
| 競合他社の価格に学習データの知識を使う | 価格はすべて、取得したページの内容または検索スニペットに基づいている必要があります。 |
| 競合他社を確認する手順を省略する | 見つかった競合他社を必ず提示し、ユーザーの確認を待ちます。競合他社を間違えると、ベンチマークも間違ったものになります。 |
| ベンチマークデータを参照せずに価格を推奨する | 価格の推奨にはすべて、ベンチマーク表の具体的な数値を引用する必要があります。 |
| 内容が 500 文字未満のページを高品質と判定する | 500 文字未満の場合、ページを取得できていません。data_quality を 'low' に設定し、検索スニペットを代替として使用します。 |
| 出力で em ダッシュを使う | em ダッシュはすべてハイフンに置き換えます。 |
実行のたびに参照ファイルを読む
cat references/pricing-models.md
cat references/extraction-guide.md
cat references/positioning-guide.md
ステップ 1:セットアップの確認
echo "TAVILY_API_KEY: ${TAVILY_API_KEY:+set (search quality enhanced)}${TAVILY_API_KEY:-not set, DuckDuckGo will be used (free)}"
echo "FIRECRAWL_API_KEY: ${FIRECRAWL_API_KEY:+set (JS rendering enhanced)}${FIRECRAWL_API_KEY:-not set, requests+BS4 will be used (free)}"
echo ""
python3 -c "from ddgs import DDGS; import requests, bs4, html2text; print('Dependencies OK')" 2>/dev/null \
|| echo "ERROR: Missing dependencies. Run: pip install ddgs requests beautifulsoup4 html2text"
依存パッケージが不足している場合: 直ちに停止します。ユーザーに次のように伝えます:「Python の依存パッケージが不足しています。インストールするには、次を実行してください:pip install ddgs requests beautifulsoup4 html2text -- すべて無料で、アカウントも不要です。その後、もう一度お試しください。」
API キーのみ不足している場合: 続行します。DuckDuckGo と requests+BS4 は無料で使えるデフォルトの手段です。
製品のスラッグを作成します。
PRODUCT_SLUG=$(python3 -c "
from urllib.parse import urlparse
import sys, re
url = 'URL_HERE'
if url.startswith('http'):
host = urlparse(url).netloc.replace('www.', '')
print(host.split('.')[0])
else:
print(re.sub(r'[^a-z0-9]', '-', url[:30].lower()).strip('-'))
")
echo "Product slug: $PRODUCT_SLUG"
ステップ 2:入力の解析
会話から次の情報を収集します。
product_url:取得する URL(必須。ただし、ユーザーが説明を直接貼り付けた場合を除く)geography:任意 -- US / Europe / India / global。デフォルト:US
ユーザーが貼り付けた説明のみを提供した場合(URL なし): ステップ 3 と 4 を省略します。貼り付けられたテキストを product_content として使用し、ステップ 4(製品分析)に進みます。page_source を user_description に設定し、data_quality_flags にその旨を記録します。
URL も説明もない場合: 次のように尋ねます:「製品またはスタートアップの URL を教えてください。または、製品の機能、対象ユーザー、他製品との違いを含む短い説明を貼り付けてください。」
ステップ 3:製品ページの取得
優先手段:Firecrawl(FIRECRAWL_API_KEY が設定されている場合)
curl -s -X POST https://api.firecrawl.dev/v1/scrape \
-H "Authorization: Bearer $FIRECRAWL_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "URL_HERE", "formats": ["markdown"], "onlyMainContent": true}' \
| python3 -c "
import sys, json
d = json.load(sys.stdin)
content = d.get('data', {}).get('markdown', '') or d.get('markdown', '')
print(f'Fetched via Firecrawl: {len(content)} characters')
open('/tmp/pf-product-raw.md', 'w').write(content)
"
代替手段:requests + BS4(無料で、常に利用可能)
python3 << 'PYEOF'
import requests, html2text, random
USER_AGENTS = [
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
]
headers = {"User-Agent": random.choice(USER_AGENTS), "Accept": "text/html,application/xhtml+xml;q=0.9,*/*;q=0.8"}
resp = requests.get("URL_HERE", headers=headers, timeout=20, allow_redirects=True)
converter = html2text.HTML2Text()
converter.ignore_images = True
converter.body_width = 0
content = converter.handle(resp.text)[:8000]
print(f'Fetched via requests+BS4: {len(content)} characters')
open('/tmp/pf-product-raw.md', 'w').write(content)
PYEOF
チェックポイント:
python3 -c "
content = open('/tmp/pf-product-raw.md').read()
if len(content) < 200:
print('ERROR: fewer than 200 characters fetched -- page may be JS-rendered')
else:
print(f'Content OK: {len(content)} characters')
"
内容が 200 文字未満の場合: ユーザーに次のように伝えます:「製品ページから取得できた内容が少なすぎます。サイトは JavaScript でレンダリングされている可能性があります。製品の機能、対象ユーザー、競合他社との違いを含む短い説明を貼り付けてください。」
Step 4: 製品分析(AI)
ページの内容を出力します:
python3 -c "
content = open('/tmp/pf-product-raw.md').read()[:5000]
print('=== PRODUCT PAGE (first 5000 chars) ===')
print(content)
"
AI への指示: 上記の製品ページを分析し、以下を抽出します。
product_name:製品名または会社名one_line_description:何を、誰のために提供し、どのような中核的価値があるか。20 語未満。マーケティング的な表現は使用しないでください。industry_taxonomy:l1(最上位カテゴリ:developer tools / fintech / healthtech / consumer / など)、l2(セクター:devops / payments / hr / など)、l3(具体的なニッチ:CI/CD automation / embedded payments / async video / など)differentiators:この製品を際立たせる具体的な特徴を、2〜3 個ちょうど挙げてください。これらは推奨に使用されるため、具体的に記述してください。「使いやすい」などの一般的な回答は認められません。icp:buyer_persona(役職)、company_type、company_sizegeography_bias:US / Europe / India / globalpage_source:"live_page" または "user_description"
/tmp/pf-product-analysis.json に書き込みます。
python3 << 'PYEOF'
import json
analysis = {
# FILL from your analysis above
"product_name": "",
"one_line_description": "",
"industry_taxonomy": {"l1": "", "l2": "", "l3": ""},
"differentiators": [],
"icp": {"buyer_persona": "", "company_type": "", "company_size": ""},
"geography_bias": "US",
"page_source": "live_page"
}
json.dump(analysis, open('/tmp/pf-product-analysis.json', 'w'), indent=2)
print('Product analysis written.')
PYEOF
確認します:
python3 -c "
import json
a = json.load(open('/tmp/pf-product-analysis.json'))
print('Product:', a['product_name'])
print('Industry:', a['industry_taxonomy']['l1'], '>', a['industry_taxonomy']['l2'], '>', a['industry_taxonomy']['l3'])
print('Differentiators:')
for d in a['differentiators']:
print(f' - {d}')
"
Step 4b: フェーズ 1 -- 競合の発見
ls scripts/research.py 2>/dev/null && echo "script found" || echo "ERROR: scripts/research.py not found -- cannot continue"
python3 scripts/research.py \
--phase discover \
--product-analysis /tmp/pf-product-analysis.json \
--output /tmp/pf-competitors-raw.json
AI に確認してもらうため、結果を出力します。
python3 -c "
import json
data = json.load(open('/tmp/pf-competitors-raw.json'))
print(f'Searches run: {len(data[\"competitor_searches\"])}')
for s in data['competitor_searches']:
print(f'\nQuery: {s[\"query\"]}')
for r in s.get('results', [])[:6]:
print(f' - {r[\"title\"]} | {r[\"url\"]}')
print(f' {r.get(\"snippet\",\"\")[:150]}')
"
AI への指示: 上記の検索結果を読みます。以下の条件を満たす競合企業を、ちょうど 5 社選んでください。
- 検索結果のタイトルまたはスニペットに名前が記載されている
- 分析対象の製品と同じ L3 ニッチに属している
- 実際のソフトウェア製品である(代理店、一覧記事、レビューサイトではない)
- 互いに異なる企業である
各競合について、name、url、pricing_url(料金ページ。スニペットに見つからない場合は [url]/pricing と推定)、description(スニペットに基づく 1 文の説明)、source_url を記入してください。
Step 5: 競合の確認
python3 << 'PYEOF'
import json
analysis = json.load(open('/tmp/pf-product-analysis.json'))
# FILL: 5 competitors from the search results above
candidates = [
# {"name": str, "url": str, "pricing_url": str, "description": str, "source_url": str}
]
print(f"\nFound 5 competitors for {analysis['product_name']} in {analysis['industry_taxonomy']['l3']}:\n")
for i, c in enumerate(candidates, 1):
print(f" {i}. {c['name']} -- {c['description']}")
print(f" Product: {c['url']}")
print(f" Pricing: {c['pricing_url']}")
data = json.load(open('/tmp/pf-competitors-raw.json'))
data['competitor_candidates'] = candidates
json.dump(data, open('/tmp/pf-competitors-raw.json', 'w'), indent=2)
PYEOF
ユーザーに次のように伝えます:「料金データを取得する競合企業はこの 5 社です。追加、削除、入れ替えを行うか、「問題ありません」と伝えて続行してください。」
確認を待ちます。 ユーザーがリストを編集した場合は、それに応じて候補を更新します。その後、確認済みのリストを書き込みます。
python3 << 'PYEOF'
import json
# FILL: confirmed competitor list (after user review)
confirmed = [
# {"name": str, "url": str, "pricing_url": str}
]
json.dump({"confirmed_competitors": confirmed}, open('/tmp/pf-competitors-confirmed.json', 'w'), indent=2)
print(f"Confirmed {len(confirmed)} competitors for pricing research.")
for c in confirmed:
print(f" - {c['name']} | pricing: {c['pricing_url']}")
PYEOF
Step 6: フェーズ 2 -- 料金ページの取得
python3 scripts/research.py \
--phase fetch-pricing \
--competitors /tmp/pf-competitors-confirmed.json \
--output /tmp/pf-pricing-raw.json
次の 3 段階のフォールバックを使用して、各競合の料金ページを取得します。
- 直接取得:
requests+beautifulsoup4+html2text - Google キャッシュ:
webcache.googleusercontent.com/search?q=cache:[url] - DuckDuckGo 検索:
"[competitor]" pricing plans cost per month(スニペットをフォールバックとして使用)
取得の概要を出力します。
python3 -c "
import json
data = json.load(open('/tmp/pf-pricing-raw.json'))
print(f'Competitors fetched: {data[\"competitors_fetched\"]}')
print()
for r in data['results']:
quality_label = {'high': 'GOOD', 'medium': 'OK', 'low': 'SNIPPET ONLY'}.get(r['data_quality'], r['data_quality'])
print(f' {r[\"name\"]:20} {r[\"source\"]:15} {r[\"content_length\"]:5} chars [{quality_label}]')
"
競合の data_quality: low の場合: 料金ページがブロックされているか、JavaScript で描画されていることを意味します。分析は検索スニペットを使用して続行しますが、その競合に関する信頼度は低いものとして記録されます。
Step 7: 料金情報の抽出(AI)
料金に関する未加工のコンテンツをすべて出力します。
python3 -c "
import json
data = json.load(open('/tmp/pf-pricing-raw.json'))
for r in data['results']:
print(f'\n=== {r[\"name\"]} (source: {r[\"source\"]}, quality: {r[\"data_quality\"]}) ===')
print(f'Pricing URL: {r[\"pricing_url\"]}')
print(r['content'][:4000])
print('---')
"
AI への指示: 各競合について、上記のコンテンツから構造化された料金データを抽出します。料金プラン、価格、制限、CTA の特定方法については、references/extraction-guide.md に従ってください。
幻覚をゼロにするためのルール:
- コンテンツに記載された価格をそのまま抽出します。価格がコンテンツにない場合は、
nullと記入してください。 - 「Contact Sales」はそのまま記録してください。推定値に置き換えてはいけません。
data_quality: lowは、データが検索スニペットに由来することを意味します。記載されている内容を抽出し、学習データの知識で不足分を補わないでください。- コンテンツに存在しないフィールドには、
"not found in page data"と記入してください。 - 年額料金については、年間の合計額とあわせて、必ず月額換算額も記録してください。
/tmp/pf-pricing-extracted.json に書き込みます。
python3 << 'PYEOF'
import json
# FILL: one object per competitor, following the schema below
extracted = [
# {
# "competitor": str,
# "pricing_url": str,
# "data_quality": "high" | "medium" | "low",
# "pricing_model": "per-seat" | "flat-rate" | "usage-based" | "freemium" | "tiered-flat" | "hybrid",
# "billing_cadence": ["monthly"] | ["annual"] | ["monthly", "annual"],
# "annual_discount": str, # e.g. "20%" or "not found in page data"
# "free_tier": true | false,
# "free_trial": true | false,
# "free_trial_days": int | null,
# "tiers": [
# {
# "name": str,
# "price_monthly": float | null, # null if Contact Sales
# "price_annual_monthly": float | null, # per-month equivalent when billed annually
# "price_note": str, # "Contact Sales", "Free", or empty
# "seats": str, # "per seat", "unlimited", "up to 5", etc.
# "key_limits": [str], # storage, API calls, projects, etc.
# "key_features": [str] # top 3-5 features in this tier
# }
# ],
# "enterprise_tier": true | false,
# "enterprise_pricing": str, # "Contact Sales" or actual price
# "regional_pricing": str | null # e.g. "India: ₹999/mo" or null
# }
]
json.dump(extracted, open('/tmp/pf-pricing-extracted.json', 'w'), indent=2)
print(f'Extracted pricing for {len(extracted)} competitors.')
for c in extracted:
tier_count = len(c.get('tiers', []))
print(f" {c['competitor']:20} model={c['pricing_model']:15} tiers={tier_count} quality={c['data_quality']}")
PYEOF
Step 8: パターン分析(AI)
抽出した料金データをすべて出力します。
python3 -c "
import json
data = json.load(open('/tmp/pf-pricing-extracted.json'))
for c in data:
print(f'\n{c[\"competitor\"]} ({c[\"pricing_model\"]}, quality={c[\"data_quality\"]})')
for t in c.get('tiers', []):
price = t.get('price_monthly')
label = t.get('price_note', '')
print(f' {t[\"name\"]:15} \${price}/mo' if price is not None else f' {t[\"name\"]:15} {label}')
"
AI への指示: 抽出したすべての料金データを分析し、パターンをまとめます。ポジショニング分析については、references/positioning-guide.md に従ってください。
/tmp/pf-patterns.json に書き込みます。
python3 << 'PYEOF'
import json
patterns = {
# FILL from analysis
# Dominant model across 5 competitors
"dominant_model": "", # the most common model
"model_breakdown": {}, # {"per-seat": 3, "flat-rate": 1, "freemium": 1}
"model_explanation": "", # 2 sentences: why this model dominates this space
# Price benchmarks (USD/mo, monthly billing)
"entry_tier": {
"min": None, "max": None, "median": None,
"currency": "USD/mo",
"note": "" # e.g. "based on 4/5 competitors (1 was search snippet only)"
},
"mid_tier": {
"min": None, "max": None, "median": None,
"currency": "USD/mo",
"note": ""
},
"enterprise_floor": "", # e.g. "$99+/mo" or "Contact Sales (4/5 competitors)"
# Billing patterns
"annual_discount_typical": "", # e.g. "15-20%"
"billing_cadence_dominant": "", # "monthly + annual", "monthly only", "annual only"
# Free tier / trial prevalence
"free_tier_count": 0, # how many of 5 offer free tier
"free_trial_count": 0, # how many of 5 offer free trial
"free_tier_typical_limits": [], # what's typically in a free tier
# Feature gates
"always_free_features": [], # features present in all free/entry tiers
"always_paid_features": [], # features locked behind paid in all competitors
"variable_features": [], # features that vary most across competitors
# Regional pricing
"regional_pricing_flags": [], # competitors with region-specific pricing
# Data quality
"high_quality_count": 0, # competitors with fetched page data
"low_quality_count": 0, # competitors with snippet-only data
"data_quality_flags": []
}
json.dump(patterns, open('/tmp/pf-patterns.json', 'w'), indent=2)
print('Patterns written.')
print(f"Dominant model: {patterns['dominant_model']}")
print(f"Entry tier: ${patterns['entry_tier']['min']}-${patterns['entry_tier']['max']}/mo (median ${patterns['entry_tier']['median']})")
print(f"Free tier: {patterns['free_tier_count']}/5 | Free trial: {patterns['free_trial_count']}/5")
PYEOF
ステップ 9:ポジショニングマップと推奨事項(AI)
統合データを出力します:
python3 -c "
import json
analysis = json.load(open('/tmp/pf-product-analysis.json'))
extracted = json.load(open('/tmp/pf-pricing-extracted.json'))
patterns = json.load(open('/tmp/pf-patterns.json'))
print('=== PRODUCT ===')
print(f'Name: {analysis[\"product_name\"]}')
print(f'What it does: {analysis[\"one_line_description\"]}')
print('Differentiators:')
for d in analysis['differentiators']:
print(f' - {d}')
print()
print('=== PATTERNS ===')
print(f'Dominant model: {patterns[\"dominant_model\"]} breakdown: {patterns[\"model_breakdown\"]}')
print(f'Entry tier: \${patterns[\"entry_tier\"][\"min\"]}-\${patterns[\"entry_tier\"][\"max\"]}/mo (median \${patterns[\"entry_tier\"][\"median\"]})')
print(f'Mid tier: \${patterns[\"mid_tier\"][\"min\"]}-\${patterns[\"mid_tier\"][\"max\"]}/mo (median \${patterns[\"mid_tier\"][\"median\"]})')
print(f'Enterprise: {patterns[\"enterprise_floor\"]}')
print(f'Free tier: {patterns[\"free_tier_count\"]}/5 | Free trial: {patterns[\"free_trial_count\"]}/5')
print()
print('=== COMPETITOR PRICING SUMMARY ===')
for c in extracted:
print(f'{c[\"competitor\"]} ({c[\"pricing_model\"]}):')
for t in c.get('tiers', []):
p = t.get('price_monthly')
print(f' {t[\"name\"]}: \${p}/mo' if p is not None else f' {t[\"name\"]}: {t.get(\"price_note\",\"\")}')
"
AI への指示 -- 幻覚を一切起こさないためのルール:
- ポジショニングマップ: 抽出データに含まれる具体的な競合他社を挙げます。観察内容を捏造しないでください。
- 未開拓の市場機会: データに存在しない具体的な価格帯またはモデルタイプに言及する必要があります。
- すべての価格に関する推奨事項: patterns JSON の具体的な数値(entry_tier.median、mid_tier.median など)を引用する必要があります。
- 無料プランの推奨事項: patterns の
free_tier_countを参照する必要があります(例:「競合他社 5 社のうち 3 社が無料プランを提供しているため、提供しないのはリスクです」)。 - 差別化要因の条件: 分析に含まれる製品の
differentiatorsリストから選びます。機能を捏造しないでください。 - エムダッシュを使用しないでください。禁止語(powerful、seamless、game-changing、revolutionary、cutting-edge、leverage)を使用しないでください。
以下を生成します:
- ポジショニングマップ:各象限(低価格+シンプル、中価格帯、エンタープライズ)でどの製品が優位か、未開拓の市場機会
- 推奨価格戦略:モデル+すべてのプラン価格+無料プランの採否+年間割引+有料にする機能
/tmp/pf-final.json に書き込みます:
python3 << 'PYEOF'
import json
result = {
"product_summary": {
# FILL from analysis
"product_name": "",
"one_line_description": "",
"differentiators": []
},
"competitors_researched": [], # FILL: list of competitor names
# Filled from patterns
"pricing_model_analysis": {
"dominant_model": "",
"model_breakdown": {},
"model_explanation": "",
"free_tier_count": 0,
"free_trial_count": 0,
"annual_discount_typical": ""
},
# Benchmark table (filled from extracted data)
"benchmark_table": [
# Per competitor:
# {"name": str, "model": str, "entry_price": str, "mid_price": str,
# "top_price": str, "free_tier": bool, "free_trial": bool, "data_quality": str}
],
# Market ranges
"market_ranges": {
"entry": {"min": None, "max": None, "median": None},
"mid": {"min": None, "max": None, "median": None},
"enterprise": ""
},
# Feature gate analysis
"feature_gates": {
"always_free": [],
"always_paid": [],
"most_variable": []
},
# Positioning map
"positioning_map": {
"cheap_simple": {"competitor": "", "price": ""},
"middle_market": [],
"enterprise": {"competitor": "", "note": ""},
"underserved_gap": ""
},
# Recommendation
"recommendation": {
"model": "",
"model_justification": "", # references specific data from model_breakdown
"entry_price": "", # e.g. "$12/mo"
"entry_justification": "", # references entry_tier.median
"mid_price": "",
"mid_justification": "",
"top_price": "", # price or "Contact Sales"
"top_justification": "",
"free_tier": True, # bool
"free_tier_justification": "", # references free_tier_count
"annual_discount": "", # e.g. "17%"
"annual_justification": "",
"gate_behind_paid": "", # specific differentiator from product analysis
"gate_justification": ""
},
"data_quality_flags": []
}
json.dump(result, open('/tmp/pf-final.json', 'w'), indent=2)
print('Synthesis written.')
print(f'Benchmark table: {len(result.get("benchmark_table", []))} competitors')
print(f'Recommendation model: {result.get("recommendation", {}).get("model", "--")}')
PYEOF
ステップ 10:自己 QA、提示、保存
自己 QA:
python3 << 'PYEOF'
import json
result = json.load(open('/tmp/pf-final.json'))
failures = []
# Check 1: em dashes
full_text = json.dumps(result)
if '—' in full_text:
result = json.loads(full_text.replace('—', '-'))
failures.append('Fixed: em dashes replaced with hyphens')
# Check 2: banned words
banned = ['powerful', 'seamless', 'innovative', 'game-changing', 'revolutionize',
'cutting-edge', 'best-in-class', 'world-class', 'leverage', 'disrupt', 'transform']
for word in banned:
if word.lower() in json.dumps(result).lower():
failures.append(f'Warning: banned word "{word}" found in output')
# Check 3: recommendation completeness
rec = result.get('recommendation', {})
required = ['model', 'entry_price', 'mid_price', 'top_price', 'free_tier',
'entry_justification', 'mid_justification', 'gate_behind_paid']
for field in required:
if not rec.get(field) and rec.get(field) is not False:
failures.append(f'Warning: recommendation missing field: {field}')
# Check 4: no Contact Sales replaced with numbers
for row in result.get('benchmark_table', []):
for field in ['entry_price', 'mid_price', 'top_price']:
val = str(row.get(field, ''))
if 'contact' in val.lower():
pass # correct
elif row.get('data_quality') == 'low' and '$' in val:
failures.append(f'Warning: {row["name"]} has dollar prices from low-quality source')
# Check 5: benchmark table populated
if len(result.get('benchmark_table', [])) < 3:
failures.append(f'Warning: benchmark table has only {len(result.get("benchmark_table", []))} competitors -- need at least 3 for reliable benchmarks')
# Check 6: "not found in page data" count
nf = json.dumps(result).count('not found in page data')
if nf > 0:
failures.append(f'INFO: {nf} field(s) marked "not found in page data"')
if 'data_quality_flags' not in result:
result['data_quality_flags'] = []
result['data_quality_flags'].extend(failures)
json.dump(result, open('/tmp/pf-final.json', 'w'), indent=2)
print(f'QA complete. {len(failures)} issues.')
for f in failures:
print(f' - {f}')
if not failures:
print('All QA checks passed.')
PYEOF
出力を提示します:
## Pricing Intel: [product_name]
Date: [today] | Competitors: [list] | Geography: [geography]
---
### Your Product
[one_line_description]
Differentiators: [list]
---
### 1. Pricing Model Analysis
Dominant model: [dominant_model] ([N]/5 competitors)
[model_explanation -- 2-3 sentences on why this model dominates the space]
Free tier: [N]/5 competitors | Free trial: [N]/5 | Annual discount: typical [X]%
---
### 2. Price Point Benchmark Table
| Competitor | Model | Entry | Mid | Top | Free tier | Free trial | Data quality |
|---|---|---|---|---|---|---|---|
[one row per competitor from benchmark_table]
Market ranges:
- Entry tier: $[min]-$[max]/mo (median $[median])
- Mid tier: $[min]-$[max]/mo (median $[median])
- Enterprise: [enterprise_floor]
---
### 3. Feature Gate Analysis
Always free: [always_free list]
Always behind paid: [always_paid list]
Most variable across competitors: [most_variable list]
---
### 4. Competitive Positioning Map
Cheap + simple: [competitor] at $[X]/mo
Middle market: [competitors] at $[X]-$[Y]/mo
Enterprise: [competitor] (Contact Sales)
Underserved gap: [underserved_gap -- specific observation]
---
### 5. Recommended Pricing for [product_name]
Model: [model] -- [model_justification]
Entry: [entry_price] -- [entry_justification]
Mid: [mid_price] -- [mid_justification]
Top: [top_price] -- [top_justification]
Free tier: [Yes/No] -- [free_tier_justification]
Annual discount: [annual_discount] -- [annual_justification]
Gate behind paid: [gate_behind_paid] -- [gate_justification]
---
Data notes: [data_quality_flags or "None"]
Saved to: docs/pricing-intel/[PRODUCT_SLUG]-[DATE].md
ファイルに保存してクリーンアップします:
DATE=$(date +%Y-%m-%d)
OUTPUT_FILE="docs/pricing-intel/${PRODUCT_SLUG}-${DATE}.md"
mkdir -p docs/pricing-intel
echo "Saved to: $OUTPUT_FILE"
rm -f /tmp/pf-product-raw.md /tmp/pf-product-analysis.json \
/tmp/pf-competitors-raw.json /tmp/pf-competitors-confirmed.json \
/tmp/pf-pricing-raw.json /tmp/pf-pricing-extracted.json \
/tmp/pf-patterns.json /tmp/pf-final.json
echo "Temp files cleaned up."