"""Юнит-экономика клиента 1-1: LTV, fully-loaded CAC, payback.

Первая редакция аудита считала CAC по одной строке Финтабло («Маркетинг», 58 тыс./мес)
и получила LTV/CAC = 22–25×. В этой цифре не было людей — а они составляют 78% всех
затрат на привлечение. Плюс маржа бралась 56,5% (вычтен только педагог), хотя с каждого
платежа уходят ещё НДС, эквайринг, комиссия площадки и возвраты. Здесь и то, и другое.

    python cac.py

Разбор результатов и выводы — в Obsidian:
projects/mars/operations/{mars} {research} финансовый аудит – 2026-08-01.md, §3.8

Грабли, о которых помнит код:
  * Знаменатель — первые абонементы ТЕКУЩЕГО года. В аудите изначально стояли 22/мес:
    это среднее 2025, в 2026 их 31. Ошибка в полтора раза прямо в CAC.
  * Маржа — ПЕРЕМЕННАЯ (48,6%), а не contribution по направлению (56,5%). Здание,
    бэк-офис и админы не вычитаются сознательно: они не растут от нового клиента
    (в пиковый час занято 6,7 кабинета из 17). Вычесть всё — получить ноль у компании,
    стоящей на нуле, и не суметь принять решение о привлечении.
  * ФОТ маркетинга сидит в CAC — в марже его быть не должно, иначе двойной счёт.
  * ФОТ берётся по ТЕКУЩИМ ставкам, а не по среднему факту за год: в факте сидят
    месяцы без выплат (май, июнь) и разовые доплаты, и он занижает будущие затраты.
  * SHARE — экспертная оценка со слов, а не учёт времени. Самый шаткий вход расчёта:
    именно он двигает CAC сильнее всего. Пересматривать раз в квартал.
  * Хор/ивенты/групповые появились в CRM только в августе 2025 — 2026. Допвыручка
    на длинном хвосте ещё не наблюдалась, оценка +5–7% консервативна снизу.
"""
import collections
import json
import statistics as st
import sys

import cohorts as C

# ── переменные затраты с каждого рубля выручки (Финтабло, факт янв–июль 2026) ──
TEACHER_SHARE = 0.440    # ставка педагога 1-1: 8 899 / 20 185 по разнесению уроков
VAT = 0.050              # НДС 5% без вычетов, с 01.07.2026
ACQUIRING = 0.0104       # торговый эквайринг 2,79% на ~40% оборота картами (в учёте невидим)
PLATFORM = 0.0092        # комиссия «Рабочих рук», 271 783 ₽ / 7 мес
REFUNDS = 0.0045         # возвраты всех видов, 133 350 ₽ / 7 мес
MARGIN = 1 - TEACHER_SHARE - VAT - ACQUIRING - PLATFORM - REFUNDS

TEACHER_TRIAL = 900      # ставка педагога за пробный урок
SHARE = 0.70             # доля времени маркетинга на привлечение новых

# ФОТ маркетинговой функции по текущим ставкам, ₽/мес (Наташа, 01.08.2026)
FOT_RATE = {'Соня Селютина': 85_000, 'Катя Кревер': 74_000, 'Эля Хабриева': 85_000,
            'Галя Макарова': 85_000, 'Наташа Блинова': 85_000}
# то же по факту выплат янв–июль (Финтабло, «Зарплаты бэк-офис»)
FOT_FACT = {'Соня Селютина': 512_000, 'Катя Кревер': 482_000, 'Эля Хабриева': 147_000,
            'Галя Макарова': 432_000, 'Наташа Блинова': 512_000}
MONTHS_FACT = 7

BUDGET_FACT = 405_933 / MONTHS_FACT              # реклама, дизайн, типография, внешние ивенты
BUDGET_NOW = 4_000 + 4_000 + 42_500 + 17_000     # то же по текущему уровню трат

# pay_item_id того, что клиент 1-1 докупает сверх абонементов (в ACTIVE их нет)
EXTRA = {19: 'Хор (курс)', 25: 'Хор (разовый)', 23: 'Квартирник', 24: 'Концерт',
         37: 'Прочие внутренние ивенты', 36: 'Групповой абонемент',
         35: 'Групповое пробное', 30: 'Cover session'}


def load():
    d = json.load(open(C.CACHE))
    return d, *C.build(d['pays'])


def denominator(d, coh, year='2026'):
    """Первых абонементов и пробных в месяц за год — знаменатель CAC."""
    per = collections.Counter()
    for v in coh.values():
        per[v['date'].strftime('%Y-%m')] += 1
    mm = sorted(k for k in per if k.startswith(year))
    abon = sum(per[m] for m in mm) / len(mm)

    pr = [float(p['income'] or 0) for p in d['pays']
          if p['document_date'] and (p['pay_item_id'] or 0) in C.PROB
          and C._date(p['document_date']).strftime('%Y') == year]
    return abon, len(pr) / len(mm), st.mean(pr), {m: per[m] for m in mm}


def cumulative(by, coh, k, since=2024):
    """Накопленная выручка на клиента когорты за первые k месяцев жизни: 1-1 и сверх."""
    sel = {c: v for c, v in coh.items() if v['date'].year >= since}
    cut = max(v['m'] for v in coh.values())
    a = b = 0.0
    n = 0
    for cid, v in sel.items():
        if v['m'] + k - 1 > cut:
            continue
        n += 1
        for dt, it, s in by[cid]:
            if C._mkey(dt) >= v['m'] + k:
                continue
            if it in C.ACTIVE:
                a += s
            elif it in EXTRA:
                b += s
    return (a / n, b / n, n) if n else (0, 0, 0)


def main():
    d, by, coh, cut_m = load()
    abon, prob, price, per_month = denominator(d, coh)
    trial_profit = prob * (price - TEACHER_TRIAL)

    print(f"Переменная маржа: {MARGIN:.1%}  "
          f"(педагог {TEACHER_SHARE:.1%} + НДС {VAT:.1%} + эквайринг {ACQUIRING:.2%} "
          f"+ площадка {PLATFORM:.2%} + возвраты {REFUNDS:.2%})")
    print(f"Первых абонементов: {abon:.1f}/мес   пробных: {prob:.1f}/мес по {price:,.0f} ₽")
    print('  помесячно: ' + '  '.join(f'{m[5:]}:{n}' for m, n in per_month.items()))
    print(f"Прибыль с пробных: {trial_profit:,.0f} ₽/мес ({trial_profit/abon:,.0f} ₽ на клиента)\n")

    # ── LTV: 5,46–6,2 платежа × средний чек, плюс докупки
    CHECK = 13_014
    ltv_rev = (5.46 * CHECK, 6.2 * CHECK)
    _, extra12, _ = cumulative(by, coh, 12, since=2025)
    a12, _, _ = cumulative(by, coh, 12, since=2025)
    uplift = 1 + extra12 / a12
    LTV = tuple(x * MARGIN * uplift for x in ltv_rev)
    print(f"LTV выручка {ltv_rev[0]:,.0f}–{ltv_rev[1]:,.0f} ₽ × маржа {MARGIN:.1%} "
          f"× докупки {uplift:.2f} → **LTV {LTV[0]:,.0f}–{LTV[1]:,.0f} ₽**\n")

    print(f"{'сценарий':<44}{'затраты':>11}{'CAC':>9}{'LTV/CAC':>13}{'доля людей':>12}")
    fot_fact = sum(FOT_FACT.values()) / MONTHS_FACT
    fot_rate = sum(FOT_RATE.values())
    cacs = {}
    for label, fot, bud, key in (
            ('только денежный бюджет (как было в аудите)', 0, BUDGET_FACT, 'бюджет'),
            ('+ ФОТ маркетинга, факт янв–июль', fot_fact, BUDGET_FACT, 'факт'),
            ('+ ФОТ по текущим ставкам (осень)', fot_rate, BUDGET_NOW, 'осень')):
        spend = fot * SHARE + bud
        net = (spend - trial_profit) / abon
        cacs[key] = net
        lo, hi = (x / net for x in LTV)
        print(f"{label:<44}{spend:>11,.0f}{net:>9,.0f}{f'{lo:.1f}–{hi:.1f}×':>13}"
              f"{fot*SHARE/spend:>11.0%}")

    print('\nPayback — накопленная маржа на клиента по месяцам жизни (когорты 2024+):')
    print(f"{'мес':>4}{'выручка 1-1':>13}{'сверх':>9}{'маржа':>10}   окупает CAC")
    for k in (1, 2, 3, 6, 12, 24):
        a, b, n = cumulative(by, coh, k)
        if n < 30:
            continue
        m = (a + b) * MARGIN
        ok = [name for name, v in (('факт', cacs['факт']), ('осень', cacs['осень'])) if m >= v]
        print(f"{k:>4}{a:>13,.0f}{b:>9,.0f}{m:>10,.0f}   {', '.join(ok) or '—'}   (n={n})")

    print('\nЕсли добавить бюджет (люди уже оплачены — предельный эффект):')
    base = fot_fact * SHARE + BUDGET_FACT
    for add in (100_000, 150_000):
        print(f"\n  +{add:,} ₽/мес — окупается с {add/LTV[0]:.1f} клиентов/мес")
        need = (base + add) / (base / abon) - abon
        print(f"    чтобы средний CAC не вырос: +{need:.0f} абонементов ({need/abon:+.0%} к потоку)")
        for x in (0, 5, 10, 15):
            n = abon + x
            net = (base + add - trial_profit) / n
            lo, hi = (v / net for v in LTV)
            print(f"      +{x:>2} клиентов → CAC {net:>7,.0f} ₽   LTV/CAC {lo:.1f}–{hi:.1f}×")

    print('\nПотолок — сколько можно платить за клиента:')
    for target in (3, 4, 6):
        lo, hi = (v / target for v in LTV)
        print(f"  при LTV/CAC = {target}× → {lo:,.0f}–{hi:,.0f} ₽")


if __name__ == '__main__':
    sys.exit(main())
