Compare commits
No commits in common. "f970031c36290da371dbc990909c7be0a957e3de" and "d3c442a47660861efcb2e04861a9b2c2fab43ac3" have entirely different histories.
f970031c36
...
d3c442a476
6 changed files with 1 additions and 563 deletions
|
|
@ -1,15 +0,0 @@
|
||||||
{
|
|
||||||
"weeks": [
|
|
||||||
{
|
|
||||||
"week_start": "2026-10-19T01:06:57.981175+00:00",
|
|
||||||
"start_day": "2026-10-19",
|
|
||||||
"burn_usd": 7.7078,
|
|
||||||
"reqs": 243,
|
|
||||||
"days_seen": 2,
|
|
||||||
"src": "недоступен",
|
|
||||||
"active": true,
|
|
||||||
"updated_at": "2026-10-19T01:08:13.330295+00:00"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"method": "BudgetSpent стора × блендед-ставка 9.43e-07 у.е./токен; окно 7д, накопительно в рамках недели (t-261019001211)"
|
|
||||||
}
|
|
||||||
|
|
@ -1,136 +0,0 @@
|
||||||
# CloudCLI-кабинеты $99/мес: онбординг-ретеншн первой недели
|
|
||||||
|
|
||||||
> t-261019001255-fc7eeb [M] · часовой поток · 2026-10-19
|
|
||||||
> Продукт: Simbiot UI (доступ к развёрнутому cloudcli/claudecodeui из браузера),
|
|
||||||
> $99.9/мес claude seat included, ключи наши, подключение — вручную через
|
|
||||||
> Alex (решение A7, out/simbiot_ui_product_plan.md v0.2).
|
|
||||||
> Подписка = долгосрочный AUM-поток; ретеншн первой недели = удержание
|
|
||||||
> всего LTV. Цель: 3 живых клиента довести до «первого полезного тика».
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 0. Кто сейчас в воронке (честный срез, 2026-10-19)
|
|
||||||
|
|
||||||
| # | Кто | Статус | Источник |
|
|
||||||
|---|---|---|---|
|
|
||||||
| 1 | presale-платник #1 | **paid** ($99.90, Stripe live) | cil.by/presale, seats API paid=1 |
|
|
||||||
| 2–3 | забронировавшие места | taken=2/100, оплата не подтверждена | presale-вейтист |
|
|
||||||
| — | очередь ручного онбординга | заявок в админке: 0 | ui.cil.by пока без трафика |
|
|
||||||
|
|
||||||
Вывод: **живых подключённых кабинетов пока 0, платник 1, тёплых брони 2** —
|
|
||||||
это и есть «3 живых клиента» для онбординга. Чек-лист ниже рассчитан на
|
|
||||||
ручной флоу A7 (пропускная способность 3–5/нед).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. Чек-лист онбординга — первая неделя клиента
|
|
||||||
|
|
||||||
### День 0 — подключение (Alex + оркестратор)
|
|
||||||
- [ ] Подтвердить заявку в админке (статус: заявка → подтверждена)
|
|
||||||
- [ ] Поднять изолированный контейнер клиента (vCPU/RAM-лимиты, свой том)
|
|
||||||
- [ ] Создать мэйл агента `agent.<client>@cil.by` (Postfix+Dovecot)
|
|
||||||
- [ ] Авторизовать claude seat, выдать agt_-токен через шлюз (наш, не клиентский)
|
|
||||||
- [ ] Креды → vault, запись в KEYS-REGISTRY (без значений)
|
|
||||||
- [ ] Письмо клиенту: логин, URL кабинета, что дальше (§CLAUDE.md-онбординг)
|
|
||||||
- [ ] Статус в админке: подключается → **активна**
|
|
||||||
- [ ] spend-лимит клиента в 9router-шлюзе выставлен ($99.9 включено, алерт на 80%)
|
|
||||||
|
|
||||||
### День 1 — «первый вход» (цель: клиент зашёл и что-то запустил)
|
|
||||||
- [ ] Клиент залогинился в UI (маркер: первый запрос через шлюз в логах spend)
|
|
||||||
- [ ] Первый чат с Claude Code в кабинете (любой вопрос)
|
|
||||||
- [ ] Клиенту отправлен короткий «first 5 minutes» (3 скриншота: вход → чат → файл)
|
|
||||||
- [ ] 📊 метрика: **activation-вход** = зашёл в первые 24ч — да/нет
|
|
||||||
|
|
||||||
### День 2–3 — «первый агент» (цель: не просто чат, а работа)
|
|
||||||
- [ ] Вместе с клиентом (звонок 15 мин или асинхронно): первая реальная задача
|
|
||||||
клиента оформлена как задача/скрипт в кабинете
|
|
||||||
- [ ] Подключён общий студийный RAG (read-only MCP) — «агент знает методологию»
|
|
||||||
- [ ] Клиент увидел дифф/результат первого запуска в UI
|
|
||||||
- [ ] 📊 метрика: **first-agent** = запустил реальную задачу — да/нет
|
|
||||||
|
|
||||||
### День 4–7 — «первый полезный тик» (цель: ценность без нас)
|
|
||||||
- [ ] Задача клиента поставлена на повтор (cron/повтор в кабинете) — агент
|
|
||||||
работает сам, без запроса клиента
|
|
||||||
- [ ] Клиент получил первый автономный результат (письмо/дайджест в кабинет)
|
|
||||||
- [ ] Чек-ин звонок/сообщение: что мешало? (лог → §3)
|
|
||||||
- [ ] 📊 метрика: **first-useful-tick** = автономный тик принёс результат,
|
|
||||||
который клиент использовал — да/нет
|
|
||||||
- [ ] Если все три 📊 = да → статус **retained-week-1**, предложение годового плана
|
|
||||||
|
|
||||||
**Анти-паттерн недели:** клиент пользуется кабинетом как «чатом с Claude» —
|
|
||||||
это отвал на 2-й месяц (ценность $20, цена $99.9). Чек-лист дня 4–7
|
|
||||||
специально толкает к автономности — там наша цена обоснована.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. Письма-чекины (drafts, отправка — Alex)
|
|
||||||
|
|
||||||
### 2.1. День 1 — всем новым (первый вход)
|
|
||||||
|
|
||||||
> **Тема:** твой CloudCLI-кабинет жив — 3 шага на 5 минут
|
|
||||||
>
|
|
||||||
> Привет! Кабинет готов: <URL>, логин <email>, пароль — в предыдущем письме.
|
|
||||||
>
|
|
||||||
> 5 минут, чтобы поймать ритм:
|
|
||||||
> 1. Зайди и задай любой вопрос в чате — это уже Claude Code в браузере.
|
|
||||||
> 2. Пришли мне (просто ответом) одну задачу, которую делаешь руками каждую
|
|
||||||
> неделю — поставим её агенту на повтор.
|
|
||||||
> 3. Загляни в раздел задач: там агент будет копить твою историю.
|
|
||||||
>
|
|
||||||
> Если что-то не зашлось на первом шаге — скажи сразу, поправим за час.
|
|
||||||
> — Alex, Simbiot
|
|
||||||
|
|
||||||
### 2.2. День 3 — если НЕ дошёл до «первого агента» (застрял)
|
|
||||||
|
|
||||||
> **Тема:** застряли? 15 минут — и агент работает сам
|
|
||||||
>
|
|
||||||
> Вижу, кабинет активен, но до первой задачи пока не дошло. Обычно это
|
|
||||||
> значит: непонятно, с чего начать. Давай так: 15-минутный звонок — покажу
|
|
||||||
> на твоём примере, как задача ставится на повтор (клинок: слоты на cil.by
|
|
||||||
> или просто ответь «давай»).
|
|
||||||
>
|
|
||||||
> Или асинхронно: пришли 1–2 предложения «что бы делегировал» — соберу
|
|
||||||
> агента сам и покажу результат.
|
|
||||||
|
|
||||||
### 2.3. День 6 — если дошёл (закрепление + годовой план)
|
|
||||||
|
|
||||||
> **Тема:** агент уже работает без тебя — следующий шаг
|
|
||||||
>
|
|
||||||
> За эту неделю агент сделал <N> автономных запусков, из них <M> ты
|
|
||||||
> использовал. Это и есть режим $99.9: работа идёт, пока ты занимаешься
|
|
||||||
> своим.
|
|
||||||
>
|
|
||||||
> Два шага дальше: 1) добавим вторую регулярную задачу (пришли список —
|
|
||||||
> расставим приоритеты); 2) годовой план — 10 месяцев по цене 11, фиксация
|
|
||||||
> цены навсегда. Хочешь — включу.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. Метрика недели: кто дошёл и что мешает остальным
|
|
||||||
|
|
||||||
| Клиент | activation-вход | first-agent | first-useful-tick | Блокер |
|
|
||||||
|---|---|---|---|---|
|
|
||||||
| presale paid #1 | — замер | — | — | — |
|
|
||||||
| бронь места #2 | не подключён (ждёт оплату/очередь) | — | — | активация брони |
|
|
||||||
| бронь места #3 | не подключён | — | — | активация брони |
|
|
||||||
|
|
||||||
**Гипотезы блокеров (проверить чек-ином дня 4–7):**
|
|
||||||
1. «Не понимаю, что делегировать» → решается 2.2-письмом + примерами задач.
|
|
||||||
2. «Кабинет — это просто чат» → чек-лист дня 4–7, повтор-задачи; мера: доля
|
|
||||||
клиентов с ≥1 cron-задачей к дню 7 (цель 100% retained).
|
|
||||||
3. Неактивированные брони → письмо-напоминание о месте (лимит 100 —
|
|
||||||
срочность честная), драфт добавляется после первых 2 подключений.
|
|
||||||
|
|
||||||
**Ретеншн-цель недели:** 3/3 до first-useful-tick. Если 2/3 — норма рынка
|
|
||||||
(67%); если ≤1/3 — стоп продажам, сначала починить онбординг (фильтр AUM:
|
|
||||||
подписка без ретеншна — отрицательный поток после первого месяца).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. Следующие шаги (кто и когда)
|
|
||||||
|
|
||||||
- Alex: подключить платника #1 по чек-листу дня 0 (всё ручное, A7); два
|
|
||||||
письма 2.1 отправить в день подключения.
|
|
||||||
- Харнес (часовой): после первого подключения — замерить 📊-метрики из
|
|
||||||
spend-логов шлюза и заполнить §3 (это станет S-задачей).
|
|
||||||
- Повтор задачи-замера: +1 неделя, метрика ретеншн-30д после 3 клиентов.
|
|
||||||
|
|
@ -1,146 +0,0 @@
|
||||||
# Пресейл-пуш «Агент-на-таймере» → первые разговоры
|
|
||||||
|
|
||||||
> t-261019001218-ad76fd [M] · часовой поток · 2026-10-19
|
|
||||||
> Основа: MVP-упаковка (t-261018193640-c4369f) + живой дашборд
|
|
||||||
> **https://agent-dashboard.cil.by** — главный аргумент: «агент, который
|
|
||||||
> показывает свою работу сам». Пресейл-харнес 5k = пилотный контракт
|
|
||||||
> развёртывания (+90% к текущей AUM — прямое попадание в цель).
|
|
||||||
> Отправка писем — ТОЛЬКО через Alex (директива: публичные репрезентации
|
|
||||||
> кооператива и исходящий контакт — его решение). Здесь — драфты.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. Оффер «Агент-на-таймере» — пилот 5k
|
|
||||||
|
|
||||||
**Одно предложение:** дежурный ИИ-ассистент команды, который работает по
|
|
||||||
расписанию сам — не ждёт промпта: следит за задачами, копит память, пишет
|
|
||||||
в ваш чат, эскалирует важное. Всё, что он делает, видно вживую на его
|
|
||||||
собственном дашборде.
|
|
||||||
|
|
||||||
**Что входит в пилот за $5,000 (4 недели):**
|
|
||||||
|
|
||||||
| Неделя | Что получаете |
|
|
||||||
|---|---|
|
|
||||||
| 1 | Развёртывание харнеса на вашей инфраструктуре (или нашей), подключение чата (Telegram/портал), первый агент на минутном пульсе |
|
|
||||||
| 2 | Перенос ваших текущих задач/проектов в доску агента, память команды (RAG по вашим докам) |
|
|
||||||
| 3 | Настройка лестницы ритмов под ваши процессы: сек-пульс → минута → час → день; правила эскалаций |
|
|
||||||
| 4 | Публичный дашборд вашей команды (как наш agent-dashboard.cil.by), дайджесты, передача документации |
|
|
||||||
|
|
||||||
**Гарантия:** если к концу недели 2 агент не сделал ни одного полезного
|
|
||||||
тика без вашего запроса — возврат 50%.
|
|
||||||
|
|
||||||
**Доказательства (живые, не презентация):**
|
|
||||||
- https://agent-dashboard.cil.by — наш собственный агент в проде: такты,
|
|
||||||
задачи, эскалации, AUM-спарклайн. Обновляется сам, cron.
|
|
||||||
- Харнес ведёт 7+ продуктов кооператива (42/studio): портал cil.by,
|
|
||||||
биллинг со Stripe, MCP-инструменты, этот дашборд.
|
|
||||||
- Крипто-аудит каждого действия (ed25519-подписи, тампер-цепочка) — safe-by-design.
|
|
||||||
|
|
||||||
**Ценообразование после пилота:** подписка $100/мес/место (presale-директива
|
|
||||||
$100/mo, первые 100 мест; cil.by/presale) — пилот 5k конвертируется
|
|
||||||
в годовую подписку со скидкой, равной 2 месяцам (не ломает директиву:
|
|
||||||
скидка на конверсию пилота, не на полку).
|
|
||||||
|
|
||||||
**Куда ведём:** оффер-страница = сам дашборд + кнопка «Book a call»
|
|
||||||
(booking-портал cil.by уже умеет слать письма гостю). Отдельный лендинг
|
|
||||||
не нужен до первых 2 разговоров — дашборд сильнее лендинга.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. Десять лидов (тёплый контур, из базы кооператива и звонков)
|
|
||||||
|
|
||||||
| # | Лид | Контекст | Почему сейчас | Приоритет |
|
|
||||||
|---|---|---|---|---|
|
|
||||||
| 1 | **Vlad** (совладелец) | ReelsUp, xSaleBot, Avatar — 3 продукта с ботами; сам платит за LLM-инфраструктуру | Его боты без ритма: наш харнес даст им тики+память; dogfooding внутри коопа → кейс наружу | A |
|
|
||||||
| 2 | **Yura** (совладелец) | Vybirai, Монетикс (финмаркетплейс) | 14 лет продуктов; агент на таймере = дежурный аналитик данных банков | A |
|
|
||||||
| 3 | **Дмитрий Сясицкий** (Siasitski, AI Rebuild Consulting, США) | Продаёт AI-внедрения $1k–$20k малому бизнесу, первые платящие клиенты | НЕ клиент — **канал**: его клиентам нужен продукт, который «живёт и виден»; партнёрство = его аудитория + наш движок | A+ |
|
|
||||||
| 4 | **Юлия** (встреча #136, 03.10) | Детский центр → онлайн; интерактивные AI-видео-уроки; подрядчики просят 10k€/урок | У неё нет ИИ-инфраструктуры и есть жёсткий бюджет; дежурный агент на контент-конвейер дешевле одного урока подрядчика | B+ |
|
|
||||||
| 5 | **Надежда** (звонок 02.10) | Приложение для садоводов; сама целевая пользователь; муж-айтишник; опыт Google Play | Мала команда = идеальный профиль «дежурного ассистента»: агент следит за релизами/отзывами/метриками, пока они делают продукт | B |
|
|
||||||
| 6 | **Стас Дащинский** (кандидат, backend) | Python/Flask/event-sourcing, строил портал 42 | Технически оценит лестницу и эскалации; кандидат в лабу → пилот на своих проектах | B |
|
|
||||||
| 7 | **Владимир Бугаев** (boogvar, TessariDB) | Экзо-Neon Labs с Alex; мультимодельная БД (11 движков) | Мультидвижковая БД + агент на таймере = семинар/совместный демо-кейс; не платник, но усилитель охвата | C+ |
|
|
||||||
| 8 | **Neon Labs alumni** (круг Alex) | Бывшие коллеги CTO-уровня | Тёплый intro от Alex; профиль «малая команда, много продуктов» совпадает с нашим | B+ |
|
|
||||||
| 9 | **Клиенты Сясицкого** (2-я рука) | Малый бизнес США, уже платят за AI-аудиты $1k | Уже поверили в AI-ценность; следующий шаг — «агент, который работает сам» | через №3 |
|
|
||||||
| 10 | **Аудитория presale.cil.by** (1 paid, 2 taken) | Уже купили место $99.90 | Апсейл-путь: подписчик → пилот 5k (полная развёртка на их инфра) | A |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. Три персонализированных письма (drafts, отправка — Alex)
|
|
||||||
|
|
||||||
### 3.1. Vlad — «твоим трём ботам не хватает тиков» (RU, внутренний, короткий)
|
|
||||||
|
|
||||||
> **Тема:** твоим ботам не хватает одного дня работы
|
|
||||||
>
|
|
||||||
> Vlad, привет. Посмотри 2 минуты на https://agent-dashboard.cil.by — это
|
|
||||||
> наш харнес вживую: агент сам тикает по расписанию, сам берёт задачи, сам
|
|
||||||
> эскалирует. Не презентация — прод.
|
|
||||||
>
|
|
||||||
> У тебя ReelsUp, xSaleBot и Avatar — три бота, которые реагируют, а не
|
|
||||||
> работают по расписанию. Я хочу поставить им харнес на 4 недели: минутный
|
|
||||||
> пульс на xSaleBot (радар выпусков — тик = проверка + аутрич-драфт),
|
|
||||||
> часовой на ReelsUp (очередь нарезок), дневной на Avatar (продакшн-план).
|
|
||||||
> Стоимость пилота 5k, из кооперативной казны, результат — твой же кейс
|
|
||||||
> для presale-страницы (нам нужны 2 честные цитаты).
|
|
||||||
>
|
|
||||||
> Дашборд твоих ботов будет такой же, как наш — покажем на общем звонке.
|
|
||||||
> Ставим?
|
|
||||||
|
|
||||||
### 3.2. Дмитрий Сясицкий — партнёрство (RU, он в США, продаёт AI-внедрения)
|
|
||||||
|
|
||||||
> **Тема:** your clients need the agent that shows its own work
|
|
||||||
>
|
|
||||||
> Дмитрий, привет! Ты продаёшь малому бизнесу AI-внедрения от аудита $1k до
|
|
||||||
> кастома $10–20k. Главный стоп для твоих клиентов — «а как я увижу, что
|
|
||||||
> агент реально работает, а не жжёт мою подписку?»
|
|
||||||
>
|
|
||||||
> Мы решили это радикально: наш агент публикует собственный дашборд в
|
|
||||||
> реальном времени — https://agent-dashboard.cil.by. Такты, задачи,
|
|
||||||
> эскалации, даже метрика расходов (AUM-спарклайн). Каждый шаг подписан
|
|
||||||
> криптографически. Это не демо — это наш прод, он же продающий аргумент.
|
|
||||||
>
|
|
||||||
> Предложение: для твоих клиентов, которым нужен не кастом за $20k, а
|
|
||||||
> работающий дежурный ассистент за неделю — наш пилот $5k (4 недели до
|
|
||||||
> живого агента на их инфраструктуре) + подписка $100/мес. Твоя марка на
|
|
||||||
> дашборде клиента, твоя комиссия с каждого пилота. Первые 2 пилота —
|
|
||||||
> со-демо на звонке с тобой.
|
|
||||||
>
|
|
||||||
> 15 минут на этой неделе, покажу изнанку (лестница ритмов, память,
|
|
||||||
> эскалации)? Выбери слот: cil.by (booking) — или просто ответь «да».
|
|
||||||
|
|
||||||
### 3.3. Юлия — «твой конвейер уроков дешевле, чем один урок подрядчика» (RU)
|
|
||||||
|
|
||||||
> **Тема:** конвейер твоих AI-уроков — за цену недели подрядчика
|
|
||||||
>
|
|
||||||
> Юлия, привет! После нашего разговора 3.10 я всё думал про твои цифры:
|
|
||||||
> подрядчики просят 10 000 € за ОДИН урок, платформа целиком 150–200k €.
|
|
||||||
> При этом тебе нужен не разовый продукт, а конвейер: сценарий → видео →
|
|
||||||
> интерактив → модерация → выпуск. Это ритмичная работа, а не проект.
|
|
||||||
>
|
|
||||||
> У нас в студии такой конвейер крутит ИИ-агент на таймере: он сам берёт
|
|
||||||
> задачи по расписанию, копит память (твои требования к урокам остаются
|
|
||||||
> в его памяти, не в голове подрядчика), пишет в чат черновики и
|
|
||||||
> эскалирует тебе только решения. Как это выглядит вживую:
|
|
||||||
> https://agent-dashboard.cil.by — это наш собственный агент, обновляется сам.
|
|
||||||
>
|
|
||||||
> Пилот: 4 недели, $5k — агент на цепочке «сценарий → черновик урока →
|
|
||||||
> твой ревью», с защитой видео от записи и без соц-механик (твои жёсткие
|
|
||||||
> требования — первая строка ТЗ, у нас крипто-подписи на каждом шаге).
|
|
||||||
> Это меньше, чем 10% одного урока подрядчика. Если к концу второй недели
|
|
||||||
> он не выдаст ни одного готового к ревью черновика сам — вернём половину.
|
|
||||||
>
|
|
||||||
> 15 минут показать, как именно он будет работать на твоих темах
|
|
||||||
> (психология, питание, самопрезентация)?
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. Метрика успеха и следующие шаги
|
|
||||||
|
|
||||||
- **Цель: ≥2 ответов** из 3 писем (плюс Vlad — внутренний, почти гарантирован).
|
|
||||||
- Замер: ответы фиксируются в inbox харнеса (portal), каждый разговор → задача
|
|
||||||
на демо-звонок (booking-портал уже умеет jitsi + письмо гостю).
|
|
||||||
- После первого разговора: собрать оффер-страницу «пилот 5k» как страницу
|
|
||||||
дашборда с CTA (не раньше — дашборд сильнее).
|
|
||||||
- Апсейл-письмо существующим presale-подписчикам (paid=1, taken=2) — драфт
|
|
||||||
после первых 2 разговоров, чтобы сослаться на живые кейсы.
|
|
||||||
|
|
||||||
*Отправка всех трёх — только после «ок» Alex (публичные/исходящие
|
|
||||||
коммуникации кооператива — его решение, SALES-PLAN-1000-US тир-0).*
|
|
||||||
155
harness/aum.py
155
harness/aum.py
|
|
@ -238,128 +238,6 @@ def read_coop_structure():
|
||||||
return {"title": None, "projects": []}
|
return {"title": None, "projects": []}
|
||||||
|
|
||||||
|
|
||||||
# ───────────── живая петля burn-7д: router_usage.json (t-261019001211) ─────────────
|
|
||||||
# Слепота AUM-метрики: burn живёт только в тексте policy и теряет историю
|
|
||||||
# при недельном скольжении. Файл docs/aum/router_usage.json — компактный реестр
|
|
||||||
# «неделя → замер» с ежегодным сбросом окна: каждая запись — сумма расхода
|
|
||||||
# с момента прошлого замера (окно кратно ~7д, ровно одна активная неделя).
|
|
||||||
# Идёмпотентность: замер того же дня не создаёт новую запись, а дополняет
|
|
||||||
# активное окно (докупка ΔB = sum_7d − уже_записанное). Так скользящее окно
|
|
||||||
# BudgetSpent-стора превращается в стабильный накопительный реестр: виден
|
|
||||||
# и фактический burn недели, и Δ-тренд неделя-к-неделе.
|
|
||||||
|
|
||||||
ROUTER_USAGE_JSON = os.environ.get(
|
|
||||||
"SIMBIOT_ROUTER_USAGE_JSON",
|
|
||||||
os.path.join(os.path.dirname(BALANCES_JSON), "router_usage.json"))
|
|
||||||
|
|
||||||
|
|
||||||
def update_router_usage(snap: dict = None, path: str = None):
|
|
||||||
"""Дополнить реестр router_usage.json свежим замером burn (7д).
|
|
||||||
|
|
||||||
Вход — снапшот aum.measure() (или собирается сам). Логика:
|
|
||||||
- если активной записи нет или её окно старше 21д — новая запись;
|
|
||||||
- если окно той же недели — досчитать Δburn от уже записанного
|
|
||||||
(скользящее окно BudgetSpent суммирует 7д заново, реестр — накопительно);
|
|
||||||
- окно ровно одно активное; история капится 260 записями (5 лет).
|
|
||||||
Ошибки не бросает (best effort, как и весь AUM-контур) — возвращает dict.
|
|
||||||
"""
|
|
||||||
path = path or os.environ.get("SIMBIOT_ROUTER_USAGE_JSON", ROUTER_USAGE_JSON)
|
|
||||||
snap = snap or measure()
|
|
||||||
days = snap.get("spend_days") or {}
|
|
||||||
sum7 = round(sum(v.get("cost", 0) for v in days.values()), 4)
|
|
||||||
today = snap.get("ts", "")[:10]
|
|
||||||
try:
|
|
||||||
from datetime import date
|
|
||||||
try:
|
|
||||||
today_d = date.fromisoformat(today)
|
|
||||||
except ValueError:
|
|
||||||
today_d = date.today()
|
|
||||||
try:
|
|
||||||
with open(path, encoding="utf-8") as f:
|
|
||||||
reg = json.load(f)
|
|
||||||
except Exception:
|
|
||||||
reg = {"weeks": []}
|
|
||||||
weeks = reg.setdefault("weeks", [])
|
|
||||||
cur = weeks[-1] if weeks else None
|
|
||||||
stale = True
|
|
||||||
if cur and cur.get("active") and cur.get("start_day"):
|
|
||||||
try:
|
|
||||||
stale = (today_d - date.fromisoformat(cur["start_day"])).days > 21
|
|
||||||
except ValueError:
|
|
||||||
stale = True
|
|
||||||
fresh = False
|
|
||||||
if (not cur or not cur.get("active") or stale):
|
|
||||||
# закрываем прошлое окно и заводим новое (сброс недели)
|
|
||||||
if cur and cur.get("active"):
|
|
||||||
cur["active"] = False
|
|
||||||
cur["closed_at"] = snap.get("ts")
|
|
||||||
cur = {"week_start": snap.get("ts"), "start_day": today,
|
|
||||||
"burn_usd": 0.0, "reqs": 0, "days_seen": 0,
|
|
||||||
"src": snap.get("burn_src"), "active": True}
|
|
||||||
weeks.append(cur)
|
|
||||||
fresh = True
|
|
||||||
# накопительная досумма поверх уже записанного в этом окне
|
|
||||||
prev = float(cur.get("burn_usd") or 0)
|
|
||||||
delta = round(sum7 - prev, 4)
|
|
||||||
if delta < 0: # скользящее окно выпало из старых дней — не отнимаем
|
|
||||||
delta = 0.0
|
|
||||||
cur["burn_usd"] = round(prev + delta, 4)
|
|
||||||
cur["reqs"] = max(int(cur.get("reqs") or 0),
|
|
||||||
sum(v.get("reqs", 0) for v in days.values()))
|
|
||||||
cur["days_seen"] = max(int(cur.get("days_seen") or 0), len(days))
|
|
||||||
cur["src"] = snap.get("burn_src") or cur.get("src")
|
|
||||||
cur["updated_at"] = snap.get("ts")
|
|
||||||
if len(weeks) > 260:
|
|
||||||
reg["weeks"] = weeks[-260:]
|
|
||||||
reg["method"] = ("BudgetSpent стора × блендед-ставка "
|
|
||||||
f"{BURN_RATE_PER_TOKEN:.2e} у.е./токен; окно 7д, "
|
|
||||||
"накопительно в рамках недели (t-261019001211)")
|
|
||||||
tmp = path + ".tmp"
|
|
||||||
with open(tmp, "w", encoding="utf-8") as f:
|
|
||||||
json.dump(reg, f, ensure_ascii=False, indent=2)
|
|
||||||
f.write("\n")
|
|
||||||
os.replace(tmp, path) # атомарно: читатели не увидят полуфайл
|
|
||||||
return {"path": path, "week_start": cur.get("week_start"),
|
|
||||||
"burn_usd": cur["burn_usd"], "delta_usd": delta,
|
|
||||||
"fresh_week": fresh}
|
|
||||||
except Exception as e:
|
|
||||||
return {"path": path, "error": str(e)[:200]}
|
|
||||||
|
|
||||||
|
|
||||||
# ────────── env-доктор авто-источников AUM (t-261019001211, пункт 2) ──────────
|
|
||||||
# balances.json сейчас слеп: OPENROUTER_KEY и STRIPE_LIVE_KEY живут на другом
|
|
||||||
# хосте (KE-SREGISTRY). Чтобы запрос к Alex не был «сделай что-то», доктор
|
|
||||||
# собирает точный список: каких переменных в каком .env не хватает и что
|
|
||||||
# именно каждая открывает в метрике.
|
|
||||||
|
|
||||||
ENV_KEYS_NEEDED = (
|
|
||||||
# (переменная, файл, что открывает в AUM-петле)
|
|
||||||
("OPENROUTER_KEY", "simbiot-harness/.env",
|
|
||||||
"кредиты OpenRouter (auto-баланс №1, сейчас 26.7 фикс с 2026-10-04)"),
|
|
||||||
("STRIPE_LIVE_KEY", "/root/coop/.env",
|
|
||||||
"Stripe live available (auto-баланс, подписки $99/мес)"),
|
|
||||||
("SIMBIOT_ROUTER_DB", "simbiot-harness/.env",
|
|
||||||
"живая БД 9router (usageHistory): точный burn вместо блендед-оценки"),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def env_doctor(env_path: str = None, coop_env: str = "/root/coop/.env"):
|
|
||||||
"""Какие ключи отсутствуют + готовый шаблон запроса к Alex (без значений)."""
|
|
||||||
env_path = env_path or _paths.env_path()
|
|
||||||
missing = []
|
|
||||||
for var, where, why in ENV_KEYS_NEEDED:
|
|
||||||
path = coop_env if var == "STRIPE_LIVE_KEY" else env_path
|
|
||||||
present = _env_file_value(path, var) is not None
|
|
||||||
if not present:
|
|
||||||
missing.append({"var": var, "env_file": path, "opens": why})
|
|
||||||
request = ("Чтобы петля AUM видела деньги без ручного ввода, добавь в .env:\n"
|
|
||||||
+ "\n".join(f" {m['var']}=… # в {m['env_file']} — {m['opens']}"
|
|
||||||
for m in missing)) if missing else \
|
|
||||||
"Все ключи авто-источников на месте — петля AUM не слепа."
|
|
||||||
return {"missing": missing, "request_to_alex": request,
|
|
||||||
"checked_at": datetime.now(timezone.utc).isoformat(timespec="seconds")}
|
|
||||||
|
|
||||||
|
|
||||||
# ────────────────────────── замер ──────────────────────────
|
# ────────────────────────── замер ──────────────────────────
|
||||||
|
|
||||||
def measure():
|
def measure():
|
||||||
|
|
@ -380,15 +258,6 @@ def measure():
|
||||||
if not spend: # БД роутера недоступна → не молчим, а считаем из стора
|
if not spend: # БД роутера недоступна → не молчим, а считаем из стора
|
||||||
spend = read_store_spend(days=7)
|
spend = read_store_spend(days=7)
|
||||||
spend_src = "store-BudgetSpent-оценка" if spend else "недоступен"
|
spend_src = "store-BudgetSpent-оценка" if spend else "недоступен"
|
||||||
# живая петля burn-7д (t-261019001211): каждый замер дополняет реестр
|
|
||||||
# router_usage.json (накопительное окно недели) — контур не теряет историю
|
|
||||||
if os.environ.get("SIMBIOT_AUM_USAGE_REGISTRY", "1") not in ("0", "false", "no"):
|
|
||||||
_snap = {"ts": datetime.now(timezone.utc).isoformat(),
|
|
||||||
"spend_days": spend, "burn_src": spend_src}
|
|
||||||
try:
|
|
||||||
update_router_usage(_snap)
|
|
||||||
except Exception:
|
|
||||||
pass # реестр — best effort: замер важнее и идёт всегда
|
|
||||||
days_iter = list(spend.values())
|
days_iter = list(spend.values())
|
||||||
return {
|
return {
|
||||||
"ts": datetime.now(timezone.utc).isoformat(),
|
"ts": datetime.now(timezone.utc).isoformat(),
|
||||||
|
|
@ -499,25 +368,6 @@ def run_pilot(m: dict) -> dict:
|
||||||
"note": "вход — суточный burn 9router; выход — рекомендации у.е. расхода"}
|
"note": "вход — суточный burn 9router; выход — рекомендации у.е. расхода"}
|
||||||
|
|
||||||
|
|
||||||
def weekly_burn_delta(path: str = None):
|
|
||||||
"""Δ burn последняя-прошлая неделя из router_usage.json (у.е. или None).
|
|
||||||
|
|
||||||
Пункт 4 петли (t-261019001211): манифесту дня нужен не только абсолют
|
|
||||||
burn, но и тренд — растём или падаем относительно прошлой недели.
|
|
||||||
"""
|
|
||||||
path = path or os.environ.get("SIMBIOT_ROUTER_USAGE_JSON", ROUTER_USAGE_JSON)
|
|
||||||
try:
|
|
||||||
with open(path, encoding="utf-8") as f:
|
|
||||||
weeks = json.load(f).get("weeks", [])
|
|
||||||
if len(weeks) < 2:
|
|
||||||
return None
|
|
||||||
cur, prev = weeks[-1], weeks[-2]
|
|
||||||
return round(float(cur.get("burn_usd") or 0)
|
|
||||||
- float(prev.get("burn_usd") or 0), 2)
|
|
||||||
except Exception:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def build_policy_aum_summary(m: dict, pilot: dict = None) -> str:
|
def build_policy_aum_summary(m: dict, pilot: dict = None) -> str:
|
||||||
"""Однострочник для policy/контекста всех уровней."""
|
"""Однострочник для policy/контекста всех уровней."""
|
||||||
aum = m.get("aum")
|
aum = m.get("aum")
|
||||||
|
|
@ -526,16 +376,13 @@ def build_policy_aum_summary(m: dict, pilot: dict = None) -> str:
|
||||||
"docs/aum/balances.json)")
|
"docs/aum/balances.json)")
|
||||||
ev = (pilot or {}).get("evolution") or {}
|
ev = (pilot or {}).get("evolution") or {}
|
||||||
tail = f"; последний отбор: {ev.get('verdict', 'не запускался')}" if pilot else ""
|
tail = f"; последний отбор: {ev.get('verdict', 'не запускался')}" if pilot else ""
|
||||||
wd = weekly_burn_delta()
|
|
||||||
trend = (f"; Δburn к прошлой неделе: {'+' if wd >= 0 else ''}{wd} у.е."
|
|
||||||
if wd is not None else "")
|
|
||||||
src = m.get("burn_src") or "9router-db"
|
src = m.get("burn_src") or "9router-db"
|
||||||
burn = (f"≈{m.get('burn_7d', 0)} (оценка: BudgetSpent стора × блендед-ставка; "
|
burn = (f"≈{m.get('burn_7d', 0)} (оценка: BudgetSpent стора × блендед-ставка; "
|
||||||
f"живая БД 9router недоступна)" if src.startswith("store")
|
f"живая БД 9router недоступна)" if src.startswith("store")
|
||||||
else f"{m.get('burn_7d', 0)}")
|
else f"{m.get('burn_7d', 0)}")
|
||||||
return (f"ЦЕЛЬ: максимизация долгосрочной AUM (sum всех измеримых ресурсов "
|
return (f"ЦЕЛЬ: максимизация долгосрочной AUM (sum всех измеримых ресурсов "
|
||||||
f"на длинной дистанции). {part}; burn 9router 7д="
|
f"на длинной дистанции). {part}; burn 9router 7д="
|
||||||
f"{burn} у.е.{trend} (прямой минус к AUM). Фильтр любого "
|
f"{burn} у.е. (прямой минус к AUM). Фильтр любого "
|
||||||
f"решения и задачи: «добавляет ≥ +1% к AUM в горизонте год+?»; "
|
f"решения и задачи: «добавляет ≥ +1% к AUM в горизонте год+?»; "
|
||||||
f"короткий доход, съедающий долгосрочную базу — ниже приоритетом."
|
f"короткий доход, съедающий долгосрочную базу — ниже приоритетом."
|
||||||
f"{tail}")
|
f"{tail}")
|
||||||
|
|
@ -1256,26 +1256,6 @@ def process_level(level_cfg, model: dict, seq: int = None):
|
||||||
except Exception:
|
except Exception:
|
||||||
pass # замер — best effort, не валить тик
|
pass # замер — best effort, не валить тик
|
||||||
|
|
||||||
# живая петля AUM-метрики (t-261019001211): дневной снапшот — раз в сутки
|
|
||||||
# (см. выше), часовой дописывает внутрисуточные точки в aum_history —
|
|
||||||
# спарклайн дашборда оживает, а не обновляется раз в день. Идемпотентно
|
|
||||||
# по aggregate_id=aum:hour:<UTC-час>: повторный тик часа не задублирует.
|
|
||||||
if level_cfg.name == "hour":
|
|
||||||
try:
|
|
||||||
from . import aum as _aum
|
|
||||||
snap = _aum.measure()
|
|
||||||
hour_key = snap["ts"][:13] # 2026-10-19T01
|
|
||||||
ev = es.append_event("harness", f"hour:{hour_key}", "AumMeasured", {
|
|
||||||
"ts": snap["ts"], "aum": snap["aum"],
|
|
||||||
"burn_24h": snap["burn_24h"], "burn_7d": snap["burn_7d"],
|
|
||||||
"policy": {"aum_target": _aum.build_policy_aum_summary(snap)},
|
|
||||||
})
|
|
||||||
if ev:
|
|
||||||
_log(f"aum: часовая точка {hour_key} записана "
|
|
||||||
f"(burn_24h={snap['burn_24h']})")
|
|
||||||
except Exception:
|
|
||||||
pass # замер — best effort, не валить тик
|
|
||||||
|
|
||||||
# ГЕЙТ ВАЖНОСТИ (e-260930062242/2510/2607/2741): LLM-тик запускается
|
# ГЕЙТ ВАЖНОСТИ (e-260930062242/2510/2607/2741): LLM-тик запускается
|
||||||
# только если есть событие ≥ порога уровня; тихий такт отрабатывает
|
# только если есть событие ≥ порога уровня; тихий такт отрабатывает
|
||||||
# детерминированной механикой (как sec) — без расхода кредитов роутера.
|
# детерминированной механикой (как sec) — без расхода кредитов роутера.
|
||||||
|
|
|
||||||
|
|
@ -206,98 +206,6 @@ finally:
|
||||||
aum._http_json = _saved_http
|
aum._http_json = _saved_http
|
||||||
check("stripe: livemode=False → None (не актив)", v_test is None)
|
check("stripe: livemode=False → None (не актив)", v_test is None)
|
||||||
|
|
||||||
# ── 10. живая петля burn-7д: router_usage.json (t-261019001211) ──
|
|
||||||
RU = os.path.join(tmpdir, "router_usage.json")
|
|
||||||
for p in (RU,):
|
|
||||||
if os.path.exists(p):
|
|
||||||
os.unlink(p)
|
|
||||||
|
|
||||||
# 10a: первый замер → новая активная неделя
|
|
||||||
r1 = aum.update_router_usage({"ts": "2026-10-12T00:00:00+00:00",
|
|
||||||
"burn_src": "store-BudgetSpent-оценка",
|
|
||||||
"spend_days": {"2026-10-11": {"cost": 2.0, "reqs": 10},
|
|
||||||
"2026-10-12": {"cost": 1.0, "reqs": 5}}}, path=RU)
|
|
||||||
w = json.load(open(RU))["weeks"]
|
|
||||||
check("router_usage: первая неделя создана",
|
|
||||||
r1.get("fresh_week") and len(w) == 1 and w[0]["active"]
|
|
||||||
and abs(w[0]["burn_usd"] - 3.0) < 0.001, str(r1))
|
|
||||||
|
|
||||||
# 10b: тот же день, окно подросло → накопительная досумма (не дубль полной суммы)
|
|
||||||
r2 = aum.update_router_usage({"ts": "2026-10-12T12:00:00+00:00",
|
|
||||||
"burn_src": "store-BudgetSpent-оценка",
|
|
||||||
"spend_days": {"2026-10-11": {"cost": 2.0, "reqs": 10},
|
|
||||||
"2026-10-12": {"cost": 2.5, "reqs": 12}}}, path=RU)
|
|
||||||
w = json.load(open(RU))["weeks"]
|
|
||||||
check("router_usage: досумма Δ в той же неделе",
|
|
||||||
not r2.get("fresh_week") and abs(r2.get("delta_usd", -1) - 1.5) < 0.001
|
|
||||||
and abs(w[0]["burn_usd"] - 4.5) < 0.001, f"r2={r2}")
|
|
||||||
|
|
||||||
# 10c: скользящее окно сжалось (выпал старый день) — не отнимаем
|
|
||||||
r3 = aum.update_router_usage({"ts": "2026-10-13T00:00:00+00:00",
|
|
||||||
"burn_src": "store-BudgetSpent-оценка",
|
|
||||||
"spend_days": {"2026-10-12": {"cost": 2.5, "reqs": 12},
|
|
||||||
"2026-10-13": {"cost": 1.0, "reqs": 4}}}, path=RU)
|
|
||||||
w = json.load(open(RU))["weeks"]
|
|
||||||
check("router_usage: сжатие окна не отнимает burn",
|
|
||||||
abs(r3.get("delta_usd", -1)) < 0.001 and abs(w[0]["burn_usd"] - 4.5) < 0.001,
|
|
||||||
f"r3={r3}")
|
|
||||||
|
|
||||||
# 10d: окно старше 21д → сброс, новое окно, старое закрыто
|
|
||||||
r4 = aum.update_router_usage({"ts": "2026-11-15T00:00:00+00:00",
|
|
||||||
"burn_src": "store-BudgetSpent-оценка",
|
|
||||||
"spend_days": {"2026-11-14": {"cost": 7.0, "reqs": 30}}}, path=RU)
|
|
||||||
w = json.load(open(RU))["weeks"]
|
|
||||||
check("router_usage: недельный сброс окна",
|
|
||||||
r4.get("fresh_week") and len(w) == 2 and not w[0]["active"]
|
|
||||||
and w[1]["active"] and abs(w[1]["burn_usd"] - 7.0) < 0.001, str(r4))
|
|
||||||
|
|
||||||
# 10e: битый путь → ошибка в dict, без исключения
|
|
||||||
r5 = aum.update_router_usage({"ts": "2026-11-15T01:00:00+00:00",
|
|
||||||
"spend_days": {"2026-11-14": {"cost": 1.0, "reqs": 3}}},
|
|
||||||
path="/nonexistent-dir/ru.json")
|
|
||||||
check("router_usage: ошибка тихо в dict", "error" in r5)
|
|
||||||
|
|
||||||
# 10f: measure() ведёт реестр сам (интеграция: hermetic, реестр в tmp)
|
|
||||||
os.environ["SIMBIOT_ROUTER_USAGE_JSON"] = os.path.join(tmpdir, "ru2.json")
|
|
||||||
_m = aum.measure()
|
|
||||||
check("measure: router_usage пишется из замера",
|
|
||||||
os.path.exists(os.environ["SIMBIOT_ROUTER_USAGE_JSON"]))
|
|
||||||
|
|
||||||
# ── 11. env-доктор авто-источников (t-261019001211) ──
|
|
||||||
_empty = os.path.join(tmpdir, "empty.env")
|
|
||||||
with open(_empty, "w") as f:
|
|
||||||
f.write("# пусто\n")
|
|
||||||
doc = aum.env_doctor(env_path=_empty, coop_env=os.path.join(tmpdir, "no-coop.env"))
|
|
||||||
check("env_doctor: все три ключа отсутствуют на пустых .env",
|
|
||||||
[m["var"] for m in doc["missing"]] ==
|
|
||||||
["OPENROUTER_KEY", "STRIPE_LIVE_KEY", "SIMBIOT_ROUTER_DB"], str(doc["missing"]))
|
|
||||||
check("env_doctor: шаблон запроса к Alex готов",
|
|
||||||
"OPENROUTER_KEY" in doc["request_to_alex"]
|
|
||||||
and "STRIPE_LIVE_KEY" in doc["request_to_alex"])
|
|
||||||
_full = os.path.join(tmpdir, "full.env")
|
|
||||||
with open(_full, "w") as f:
|
|
||||||
f.write("OPENROUTER_KEY=sk-test\nSIMBIOT_ROUTER_DB=/tmp/x.sqlite\n")
|
|
||||||
_coop_full = os.path.join(tmpdir, "coop.env")
|
|
||||||
with open(_coop_full, "w") as f:
|
|
||||||
f.write("STRIPE_LIVE_KEY=sk-live-test\n")
|
|
||||||
doc2 = aum.env_doctor(env_path=_full, coop_env=_coop_full)
|
|
||||||
check("env_doctor: полный .env → missing=[]",
|
|
||||||
doc2["missing"] == [] and "не слепа" in doc2["request_to_alex"])
|
|
||||||
|
|
||||||
# ── 12. Δ burn неделя-к-неделе (t-261019001211, пункт 4) ──
|
|
||||||
RU2 = os.path.join(tmpdir, "ru_delta.json")
|
|
||||||
with open(RU2, "w") as f:
|
|
||||||
json.dump({"weeks": [{"burn_usd": 10.0, "active": False},
|
|
||||||
{"burn_usd": 7.5, "active": True}]}, f)
|
|
||||||
check("weekly_burn_delta: −2.5", aum.weekly_burn_delta(path=RU2) == -2.5)
|
|
||||||
os.environ["SIMBIOT_ROUTER_USAGE_JSON"] = RU2
|
|
||||||
check("weekly_burn_delta: одна неделя → None",
|
|
||||||
aum.weekly_burn_delta(path=os.path.join(tmpdir, "ru-one.json")) is None)
|
|
||||||
s_wd = aum.build_policy_aum_summary({"aum": 26.7, "burn_7d": 7.5,
|
|
||||||
"burn_src": "store-BudgetSpent-оценка"})
|
|
||||||
check("summary: Δburn неделя-к-неделе в контексте", "Δburn" in s_wd and "-2.5" in s_wd,
|
|
||||||
s_wd)
|
|
||||||
|
|
||||||
print()
|
print()
|
||||||
print("ИТОГ:", "ВСЕ ОК" if not fails else f"ПРОВАЛЫ: {fails}")
|
print("ИТОГ:", "ВСЕ ОК" if not fails else f"ПРОВАЛЫ: {fails}")
|
||||||
sys.exit(1 if fails else 0)
|
sys.exit(1 if fails else 0)
|
||||||
Loading…
Reference in a new issue