market simulation: logit конкуренция + сегменты + сценарии входа, MC 400 trials, E[dAUM] по сценариям + чувствительность (t-261009123019-a32bb3)
This commit is contained in:
parent
f8985ca596
commit
54beef9dd7
2 changed files with 341 additions and 0 deletions
106
docs/market-simulation-2026-10-09.md
Normal file
106
docs/market-simulation-2026-10-09.md
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
# Рыночная симуляция Simbiot: конкуренты + клиенты + сценарии входа
|
||||||
|
|
||||||
|
> t-261009123019-a32bb3 [L], 2026-10-09. Модель и прогоны: `scripts/market_sim.py`
|
||||||
|
> (400 trial'ов Монте-Карло, seed 42, горизонт 12 мес). Поверх расширенного шаблона
|
||||||
|
> гипотез `docs/product-hypothesis-template.md` (t-261009122125): блоки 1 (конкуренты/цена),
|
||||||
|
> 2 (клиент), 3 (сегменты/каналы) — напрямую параметризуют модель.
|
||||||
|
> Аргументы запуска: `./venv/bin/python scripts/market_sim.py --trials 400 --seed 42 --sens`
|
||||||
|
|
||||||
|
## 1. Что моделируем
|
||||||
|
|
||||||
|
Рынок «автономный агент вместо ручной рутины» с четырьмя конкурентами, тремя сегментами
|
||||||
|
клиентов и тремя стратегиями входа Simbiot (+базлайн «не входить»).
|
||||||
|
|
||||||
|
**Клиент выбирает по logit-полезности**: U = (value − price) / scale. Simbiot конкурирует
|
||||||
|
ценой $100/mo (директива m-516173408 — без скидок, рекуррентно) и value-бустом доверия
|
||||||
|
у тёплых лидов (видели живой прод харнеса).
|
||||||
|
|
||||||
|
**Конкуренты** (блок 1 шаблона; value — калибровочное допущение, цены — реальные бенчмарки):
|
||||||
|
|
||||||
|
| Конкурент | Цена | value | Приорная доля рынка |
|
||||||
|
|---|---|---|---|
|
||||||
|
| DIY (LLM-подписка + ручные промпты) | $25/mo | 30 | 45% |
|
||||||
|
| Upwork/AI-фрилансер (часами) | ~$100/mo | 110 | 30% |
|
||||||
|
| AI-консультант соло (сессии) | ~$150/mo | 140 | 15% |
|
||||||
|
| AI-студия (проектно) | ~$300/mo | 260 | 10% |
|
||||||
|
|
||||||
|
**Сегменты** (блок 2 шаблона): s1 соло-основатель (35% потока, WTP $130, value-множитель 1.0);
|
||||||
|
s2 индиз/автоматизатор (40%, WTP $60, ×0.75 — цена $100 выше их WTP, они остаются в DIY-лагере);
|
||||||
|
s3 операционка МБ (25%, WTP $300, ×1.3).
|
||||||
|
|
||||||
|
**Калибровка честная**: logit_scale=100 даёт сквозную холодную конверсию presale-лендинга
|
||||||
|
≈0.5–1.1% — верх диапазона плана SALES-PLAN-1000-US; тёплая ≈35% (буст ×1.3). Отток 8%/мес.
|
||||||
|
|
||||||
|
## 2. Сценарии входа
|
||||||
|
|
||||||
|
| Сценарий | Тёплые лиды/мес | Посетители presale/мес (мес1→12) | Платный трафик | Сессии $150 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| D_no_entry | 0 | 0 | — | — |
|
||||||
|
| A_warm_only | 15 | 0 | нет | 12%/тёплых |
|
||||||
|
| B_presale_funnel | 12 | 5k → 95k | 60% лидов, CAC $22 | 5% |
|
||||||
|
| C_mixed | 15 | 3k → 90k | 50% лидов, CAC $22 | 12% |
|
||||||
|
|
||||||
|
Экономика харнеса — из живого замера: burn 124 у.е./мес (28.55/7д, store-BudgetSpent),
|
||||||
|
+1 у.е./мес на активного подписчика, +5 у.е. на сессию (допущения). AUM₀ = 26.7 у.е.
|
||||||
|
|
||||||
|
## 3. Результаты (E[] по 400 trial'ам, 12 мес, у.е.)
|
||||||
|
|
||||||
|
| Сценарий | rev подписки | rev сессии | CAC | burn | **E[ΔAUM]** | P(ΔAUM>0) | подписчики (медиана) | окупаемость |
|
||||||
|
|---|---|---|---|---|---|---|---|---|
|
||||||
|
| D_no_entry | 0 | 0 | 0 | 1 488 | **−1 488** | 0% | 0 | не окупается |
|
||||||
|
| A_warm_only | 24 119 | 1 840 | 0 | 1 852 | **+24 107** | 100% | 40 | 1-й мес |
|
||||||
|
| B_presale_funnel | 97 215 | 906 | 1 835 | 2 675 | **+93 611** | 100% | 100 (капа мест) | 2-й мес |
|
||||||
|
| C_mixed | 95 146 | 1 805 | 1 340 | 2 682 | **+92 929** | 100% | 100 (капа мест) | 2-й мес |
|
||||||
|
|
||||||
|
Доли выбора (сегмент s1): Simbiot $100/mo — 34.8% тёплый / 26.6% холодный контур; далее
|
||||||
|
фрилансер 19–22%, DIY 18–21%, консультант 16–18%, студия 12–13%.
|
||||||
|
|
||||||
|
## 4. Чувствительность (C_mixed)
|
||||||
|
|
||||||
|
| Вариант | E[ΔAUM] | P(>0) | Подписчики |
|
||||||
|
|---|---|---|---|
|
||||||
|
| база | 92 929 | 1.00 | 100 |
|
||||||
|
| churn 5% / 12% | 96 285 / 88 331 | 1.00 | 100 |
|
||||||
|
| logit_scale 80 / 130 (вера в продукт) | 93 333 / 92 280 | 1.00 | 100 |
|
||||||
|
| value −15% / +15% | 91 563 / 93 713 | 1.00 | 100 |
|
||||||
|
| reach ×0.5 / ×2 (качество трафика) | 87 658 / 96 019 | 1.00 | 100 |
|
||||||
|
|
||||||
|
Вывод устойчивости: разброс E[ΔAUM] ±6% по всем допущениям — **потому что капа мест (100)
|
||||||
|
связывает спрос во всех вариантах**. Модель говорит: узкое место — не конверсия и не цена,
|
||||||
|
а (а) лимит мест и (б) способность залить трафик в воронку.
|
||||||
|
|
||||||
|
## 5. Выводы для решений
|
||||||
|
|
||||||
|
1. **Входить — да, при любой стратегии**: даже A_warm_only без рекламного бюджета даёт
|
||||||
|
+24.1k у.е. за год против −1.5k у «не входить», окупаемость с 1-го месяца. Фильтр AUM
|
||||||
|
(+1% = +0.27 у.е.) пробивают все сценарии входа с запасом ×1000.
|
||||||
|
2. **Капа мест — главный ограничение роста**: B/C упираются в 100 мест (медиана). Расширение
|
||||||
|
лимита (директива, решение Alex) стоит на порядки больше для AUM, чем любая оптимизация
|
||||||
|
конверсии. Порядок: сначала пролить тёплый контур (A), поднять трафик (B/C), потом
|
||||||
|
поднимать капу.
|
||||||
|
3. **Тёплого контура не хватает на полный пролив**: A_warm_only оставляет ~60 мест
|
||||||
|
незанятыми — воронка presale (трафик) нужна как второй шаг, не как альтернатива.
|
||||||
|
4. **Сессии $150 — маленький, но почти чистый маржинальный доход** (~1.8k/год в C,
|
||||||
|
burn 5 у.е./сессия) и верх воронки к подписке. Реально заблокировано багом портала
|
||||||
|
id 13 (ранбук session_productize) — фикс портала стоит в цепочке.
|
||||||
|
5. **Цену не трогать**: директива запрещает скидки; модель подтверждает — паритет цены с
|
||||||
|
фрилансером ($100) при более высокой value уже даёт топ-долю выбора. Скидка не нужна
|
||||||
|
для победы в logit-выборе.
|
||||||
|
|
||||||
|
## 6. Ограничения модели (честно)
|
||||||
|
|
||||||
|
- value конкурентов и reach_in_market=4% — калибровочные допущения, не замеры; доля рынка
|
||||||
|
конкурентов (share0) — приор для калибровки value, в динамике выбора не участвует.
|
||||||
|
- Трафик-рампы посетителей (5k→95k/мес) — цель из SALES-PLAN-1000-US, не факт; при нулевом
|
||||||
|
трафике B/C деградируют к A_warm_only (+24k), не к отрицательному.
|
||||||
|
- Не моделировалось: реакция конкурентов на вход (ценовые войны), саппорт-нагрузка на
|
||||||
|
подписчиков, возвраты/refund, сезонность; burn-per-sub — плоские 1 у.е./мес.
|
||||||
|
- Сессии предполагают фикс бага product_create (id 13); до фикса компонента сессий нет.
|
||||||
|
- Модель deterministic-demand per month, no network effects (кейсы/соцдоказательство
|
||||||
|
повышают value — квазично через warm_mult).
|
||||||
|
|
||||||
|
## 7. Артефакты
|
||||||
|
|
||||||
|
- Модель: `scripts/market_sim.py` (детерминированный seed, `--sens` — чувствительность).
|
||||||
|
- Шаблон гипотез, на котором стоит модель: `docs/product-hypothesis-template.md`.
|
||||||
|
- Входные якоря: docs/PRICE-DIRECTIVE-SIMBIOT.md ($100/mo, 100 мест), artifacts/session-productize-runbook.md (сессия $150, витрина), docs/SALES-PLAN-1000-US (воронка), docs/aum.md (метрика ΔAUM).
|
||||||
235
scripts/market_sim.py
Normal file
235
scripts/market_sim.py
Normal file
|
|
@ -0,0 +1,235 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Рыночная симуляция Simbiot: конкуренты + клиенты + сценарии входа (t-261009123019-a32bb3).
|
||||||
|
|
||||||
|
Модель поверх расширенного шаблона гипотез (docs/product-hypothesis-template.md):
|
||||||
|
- конкуренты: цены, качество, доли (logit-выбор клиента);
|
||||||
|
- клиенты: сегменты с WTP, ценностью продукта и оттоком подписки;
|
||||||
|
- Simbiot: вход по сценариям (тёплый контур / presale-воронка / микс + сессии $150);
|
||||||
|
- метрика: E[ΔAUM] = revenue − CAC − burn за 12 мес, Монте-Карло + чувствительность.
|
||||||
|
|
||||||
|
Калибровка (v0.2, после sanity-чека):
|
||||||
|
- подписка $100/mo — РЕКУРРЕНТНО (биллинг на активных, не разово);
|
||||||
|
- logit_scale=100 у.е.: холодная сквозная конверсия ≈0.5–1.2% — верх плана SALES-PLAN-1000-US;
|
||||||
|
- тёплая конверсия ≈20–40% (буст доверия на value) — не 77%;
|
||||||
|
- допущения помечены в PARAMS, чувствительность — отдельным прогоном (--sens).
|
||||||
|
|
||||||
|
Запуск: ./venv/bin/python scripts/market_sim.py [--trials N] [--seed S] [--md] [--sens]
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import copy
|
||||||
|
import math
|
||||||
|
import random
|
||||||
|
import statistics as st
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
BASE = {
|
||||||
|
# Экономика харнеса (замер cli.py aum 09.10: burn 7д=28.55 у.е.)
|
||||||
|
"aum0": 26.7, # у.е., docs/aum/balances.json
|
||||||
|
"burn_month_base": 28.55 / 7 * 30.4, # ≈124 у.е./мес — замер стора (BudgetSpent-оценка)
|
||||||
|
"burn_per_session": 5.0, # допущение: токены 1 сессии агента (60 мин)
|
||||||
|
"burn_per_sub_extra": 1.0, # допущение: доп. тики агента на активного подписчика/мес
|
||||||
|
# Presale (директива m-516173408): $100/mo, без скидок, места total=100
|
||||||
|
"presale_price": 100.0,
|
||||||
|
"seats_total": 100,
|
||||||
|
"monthly_churn": 0.08, # допущение: отток AI-подписок 8%/мес (чувствительность: 5–12%)
|
||||||
|
# Сессии (ранбук session_productize): $150 one_time
|
||||||
|
"session_price": 150.0,
|
||||||
|
# Конкуренты (блок 1 шаблона: cheapest/closest/premium)
|
||||||
|
"competitors": [
|
||||||
|
# name, цена $/мес, value (полезность у.е./мес для среднего клиента), share0 — априорная доля
|
||||||
|
{"name": "DIY (LLM-подписка + ручные промпты)", "price": 25.0, "value": 30.0, "share0": 0.45},
|
||||||
|
{"name": "Upwork/AI-фрилансер (часами)", "price": 100.0, "value": 110.0, "share0": 0.30},
|
||||||
|
{"name": "AI-консультант соло (сессии)", "price": 150.0, "value": 140.0, "share0": 0.15},
|
||||||
|
{"name": "AI-студия (проектно)", "price": 300.0, "value": 260.0, "share0": 0.10},
|
||||||
|
],
|
||||||
|
# Simbiot: базовая value для сегмента s2; сегменты масштабируют (value_mult)
|
||||||
|
"simbiot_value_base": 130.0,
|
||||||
|
"logit_scale": 100.0, # делитель (value−price): калибрует конверсию (см. шапку)
|
||||||
|
# Сегменты (блок 2-3 шаблона): weight, WTP, множитель ценности Simbiot
|
||||||
|
"segments": [
|
||||||
|
{"name": "s1 соло-основатель (рутина, платит $50–200)", "w": 0.35, "wtp": 130.0, "vm": 1.0},
|
||||||
|
{"name": "s2 индиз/автоматизатор (платит $30–80)", "w": 0.40, "wtp": 60.0, "vm": 0.75},
|
||||||
|
{"name": "s3 операционка малого бизнеса (платит $150–500)", "w": 0.25, "wtp": 300.0, "vm": 1.3},
|
||||||
|
],
|
||||||
|
# Сценарии входа: n_warm/мес; visitors/мес (presale-лендинг); paid_share и cac — платный трафик
|
||||||
|
"scenarios": {
|
||||||
|
"A_warm_only": {"n_warm": 15, "visitors": [0] * 12, "paid_share": 0.0, "cac": 0.0,
|
||||||
|
"session_rate": 0.12, "warm_mult": 1.3},
|
||||||
|
"D_no_entry": {"n_warm": 0, "visitors": [0] * 12, "paid_share": 0.0, "cac": 0.0,
|
||||||
|
"session_rate": 0.0, "warm_mult": 1.0},
|
||||||
|
"B_presale_funnel": {"n_warm": 12,
|
||||||
|
"visitors": [5000, 8000, 12000, 18000, 25000, 35000, 45000, 55000, 65000, 75000, 85000, 95000],
|
||||||
|
"paid_share": 0.6, "cac": 22.0, "session_rate": 0.05, "warm_mult": 1.2},
|
||||||
|
"C_mixed": {"n_warm": 15,
|
||||||
|
"visitors": [3000, 6000, 10000, 15000, 22000, 30000, 40000, 50000, 60000, 70000, 80000, 90000],
|
||||||
|
"paid_share": 0.5, "cac": 22.0, "session_rate": 0.12, "warm_mult": 1.3},
|
||||||
|
},
|
||||||
|
"reach_in_market": 0.04, # допущение: 4% посетителей реально «в рынке» (чувствительность ×0.5/×2)
|
||||||
|
"horizon_months": 12,
|
||||||
|
}
|
||||||
|
|
||||||
|
MC_TRIALS = 400
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class TrialResult:
|
||||||
|
revenue_sub: float
|
||||||
|
revenue_sessions: float
|
||||||
|
cac_total: float
|
||||||
|
burn_total: float
|
||||||
|
d_aum: float
|
||||||
|
subs_final: int
|
||||||
|
sessions_total: int
|
||||||
|
payback_month: int # 0 = не окупился за горизонт
|
||||||
|
|
||||||
|
|
||||||
|
def logit_share(options, scale):
|
||||||
|
us = {n: (v - p) / scale for n, v, p in options}
|
||||||
|
mx = max(us.values())
|
||||||
|
exps = {n: math.exp(u - mx) for n, u in us.items()}
|
||||||
|
z = sum(exps.values())
|
||||||
|
return {n: e / z for n, e in exps.items()}
|
||||||
|
|
||||||
|
|
||||||
|
def share_simbiot(seg, p, warm_mult):
|
||||||
|
opts = [(c["name"], c["value"], c["price"]) for c in p["competitors"]]
|
||||||
|
val = p["simbiot_value_base"] * seg["vm"] * warm_mult
|
||||||
|
opts.append(("simbiot", val, p["presale_price"]))
|
||||||
|
return logit_share(opts, p["logit_scale"])["simbiot"]
|
||||||
|
|
||||||
|
|
||||||
|
def run_trial(rng, p, sc):
|
||||||
|
H = p["horizon_months"]
|
||||||
|
subs = 0
|
||||||
|
rev_sub = rev_sess = cac_t = burn_t = 0.0
|
||||||
|
sessions = 0
|
||||||
|
payback = 0
|
||||||
|
cum = 0.0
|
||||||
|
segs, ws = p["segments"], [s["w"] for s in p["segments"]]
|
||||||
|
for m in range(H):
|
||||||
|
# отток
|
||||||
|
subs = int(round(subs * (1 - p["monthly_churn"])))
|
||||||
|
# рекуррентный биллинг активных
|
||||||
|
rev_sub += subs * p["presale_price"]
|
||||||
|
# тёплые лиды
|
||||||
|
for _ in range(sc["n_warm"]):
|
||||||
|
seg = rng.choices(segs, weights=ws)[0]
|
||||||
|
if rng.random() < share_simbiot(seg, p, sc["warm_mult"]) and subs < p["seats_total"]:
|
||||||
|
subs += 1
|
||||||
|
# сессии $150 (разовый вход, часть тёплых)
|
||||||
|
exp_booked = int(round(sc["n_warm"] * sc["session_rate"]))
|
||||||
|
booked = rng.randint(0, exp_booked) if exp_booked > 0 else 0
|
||||||
|
sessions += booked
|
||||||
|
rev_sess += booked * p["session_price"]
|
||||||
|
# presale-воронка
|
||||||
|
vis = sc["visitors"][m]
|
||||||
|
if vis:
|
||||||
|
for _ in range(int(vis * p["reach_in_market"])):
|
||||||
|
seg = rng.choices(segs, weights=ws)[0]
|
||||||
|
if rng.random() < share_simbiot(seg, p, 1.0) and subs < p["seats_total"]:
|
||||||
|
subs += 1
|
||||||
|
if rng.random() < sc["paid_share"]:
|
||||||
|
cac_t += sc["cac"]
|
||||||
|
burn_t += p["burn_month_base"] + subs * p["burn_per_sub_extra"] + booked * p["burn_per_session"]
|
||||||
|
cum = rev_sub + rev_sess - cac_t - burn_t
|
||||||
|
if payback == 0 and cum > 0:
|
||||||
|
payback = m + 1
|
||||||
|
return TrialResult(rev_sub, rev_sess, cac_t, burn_t, cum, subs, sessions, payback)
|
||||||
|
|
||||||
|
|
||||||
|
def run_scenario(name, sc, p, trials, seed):
|
||||||
|
rng = random.Random(seed)
|
||||||
|
rs = [run_trial(rng, p, sc) for _ in range(trials)]
|
||||||
|
d = [r.d_aum for r in rs]
|
||||||
|
aum0 = p["aum0"]
|
||||||
|
base_burn = p["burn_month_base"] * p["horizon_months"] # «не входить»: burn всё равно идёт
|
||||||
|
return {
|
||||||
|
"name": name,
|
||||||
|
"E[rev sub]": st.mean(r.revenue_sub for r in rs),
|
||||||
|
"E[rev sess]": st.mean(r.revenue_sessions for r in rs),
|
||||||
|
"E[CAC]": st.mean(r.cac_total for r in rs),
|
||||||
|
"E[burn]": st.mean(r.burn_total for r in rs),
|
||||||
|
"E[dAUM]": st.mean(d),
|
||||||
|
"P(dAUM>0)": sum(1 for x in d if x > 0) / len(d),
|
||||||
|
"P(инкремент>0 vs без входа)": sum(1 for x in d if x > -base_burn) / len(d),
|
||||||
|
"P(фильтр +1% AUM)": sum(1 for x in d if x >= 0.01 * aum0) / len(d),
|
||||||
|
"subs_final med": st.median(r.subs_final for r in rs),
|
||||||
|
"sessions med": st.median(r.sessions_total for r in rs),
|
||||||
|
"payback med": st.median([r.payback_month for r in rs if r.payback_month > 0] or [0]),
|
||||||
|
"payback>0": sum(1 for r in rs if r.payback_month > 0) / len(rs),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def sens_table(trials, seed):
|
||||||
|
"""Чувствительность E[dAUM] сценария C_mixed к ключевым допущениям."""
|
||||||
|
rows = []
|
||||||
|
variants = [
|
||||||
|
("база", {}),
|
||||||
|
("churn 5%", {"monthly_churn": 0.05}),
|
||||||
|
("churn 12%", {"monthly_churn": 0.12}),
|
||||||
|
("logit_scale 80 (быстрее верят)", {"logit_scale": 80.0}),
|
||||||
|
("logit_scale 130 (скептичнее)", {"logit_scale": 130.0}),
|
||||||
|
("value −15%", {"simbiot_value_base": 130.0 * 0.85}),
|
||||||
|
("value +15%", {"simbiot_value_base": 130.0 * 1.15}),
|
||||||
|
("reach ×0.5 (трафик холоднее)", {"reach_in_market": 0.02}),
|
||||||
|
("reach ×2 (трафик горячее)", {"reach_in_market": 0.08}),
|
||||||
|
]
|
||||||
|
for label, patch in variants:
|
||||||
|
p = copy.deepcopy(BASE)
|
||||||
|
p.update(patch)
|
||||||
|
r = run_scenario("C_mixed", p["scenarios"]["C_mixed"], p, trials, seed)
|
||||||
|
rows.append((label, r["E[dAUM]"], r["P(dAUM>0)"], r["subs_final med"]))
|
||||||
|
return rows
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
ap = argparse.ArgumentParser()
|
||||||
|
ap.add_argument("--trials", type=int, default=MC_TRIALS)
|
||||||
|
ap.add_argument("--seed", type=int, default=42)
|
||||||
|
ap.add_argument("--md", action="store_true")
|
||||||
|
ap.add_argument("--sens", action="store_true")
|
||||||
|
a = ap.parse_args()
|
||||||
|
p = BASE
|
||||||
|
print(f"# Рыночная симуляция Simbiot: {a.trials} trial'ов, seed={a.seed}, горизонт {p['horizon_months']} мес")
|
||||||
|
print(f"# база: AUM={p['aum0']} у.е., burn={p['burn_month_base']:.1f}/мес (~{p['burn_month_base']*12:.0f}/год);")
|
||||||
|
print(f"# presale $100/mo рекуррентно (мест {p['seats_total']}), сессия $150; отток {p['monthly_churn']:.0%}/мес")
|
||||||
|
keys = ["E[rev sub]", "E[rev sess]", "E[CAC]", "E[burn]", "E[dAUM]", "P(dAUM>0)",
|
||||||
|
"P(инкремент>0 vs без входа)", "P(фильтр +1% AUM)", "subs_final med",
|
||||||
|
"sessions med", "payback med", "payback>0"]
|
||||||
|
rows = [run_scenario(n, sc, p, a.trials, a.seed) for n, sc in p["scenarios"].items()]
|
||||||
|
if a.md:
|
||||||
|
print("| сценарий | " + " | ".join(keys) + " |")
|
||||||
|
print("|---|" + "---|" * len(keys))
|
||||||
|
for r in rows:
|
||||||
|
print(f"| {r['name']} | " + " | ".join(
|
||||||
|
(f"{r[k]:.0f}" if abs(r[k]) >= 10 else f"{r[k]:.3f}") if isinstance(r[k], float) else str(r[k])
|
||||||
|
for k in keys) + " |")
|
||||||
|
else:
|
||||||
|
for r in rows:
|
||||||
|
print("\n===", r["name"])
|
||||||
|
for k in keys:
|
||||||
|
v = r[k]
|
||||||
|
print(f" {k:30s} {v:,.1f}" if isinstance(v, float) else f" {k:30s} {v}")
|
||||||
|
if a.sens:
|
||||||
|
print("\n# Чувствительность (C_mixed): E[dAUM], P(>0), подписчики")
|
||||||
|
print("| вариант | E[dAUM] у.е. | P(dAUM>0) | subs med |")
|
||||||
|
print("|---|---|---|---|")
|
||||||
|
for label, e, pp, subs in sens_table(a.trials, a.seed):
|
||||||
|
print(f"| {label} | {e:,.0f} | {pp:.2f} | {subs:.0f} |")
|
||||||
|
# снимок долей (сегмент s1, тёплый буст 1.3)
|
||||||
|
print("\n# Доли выбора, сегмент s1, тёплый контур (буст 1.3):")
|
||||||
|
s1 = p["segments"][0]
|
||||||
|
opts = [(c["name"], c["value"], c["price"]) for c in p["competitors"]]
|
||||||
|
opts.append(("Simbiot $100/mo", p["simbiot_value_base"] * s1["vm"] * 1.3, p["presale_price"]))
|
||||||
|
for n, sh in sorted(logit_share(opts, p["logit_scale"]).items(), key=lambda x: -x[1]):
|
||||||
|
print(f" {sh*100:5.1f}% {n}")
|
||||||
|
print("# то же, холодный (буст 1.0):")
|
||||||
|
opts[-1] = ("Simbiot $100/mo", p["simbiot_value_base"] * s1["vm"], p["presale_price"])
|
||||||
|
for n, sh in sorted(logit_share(opts, p["logit_scale"]).items(), key=lambda x: -x[1]):
|
||||||
|
print(f" {sh*100:5.1f}% {n}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Loading…
Reference in a new issue