"""Simbiot Harness — North Star metric: долгосрочная AUM (t-260929161138). Формализует главную цель (фиксация Alex, m-516173142): максимизация суммарных измеримых ресурсов кооператива на длинной дистанции. Состав метрики: - AUM_t — балансы кооператива в у.е. (USD-эквивалент). Источник уровня день: docs/aum/balances.json (ручной ввод Alex, схема {"accounts":[{"name":..., "amount_usd":...}]}); пока файла нет — AUM = None (не замерен), но драйверы считаются уже сейчас. - burn_t — расход на LLM-инфраструктуру (9router, живая БД usageHistory /root/.9router/db/data.sqlite, env SIMBIOT_ROUTER_DB переопределяет; на тестах БД роутера не требуется — ошибка чтения → пустой замер). - ΔAUM(t) = revenue_t − burn_t; фильтр решений: «даёт ≥ +1% к AUM в горизонте год+?» (README, раздел «Главная цель»). Петля оптимизации (пилот v0.1, песочница): симуляция сценариев (бутстрап исторических суточных burn 9router) + генетический отбор конфигураций расхода по fitness = E[ΔAUM горизонта]. Результат — АДВОКАТ-отчёт (рекомендация дню/Alex'у), НЕ авто-применение. Модель продуктивности — честный плейсхолдер (см. docs/aum.md): бюджет дня покупает токены по ставкам моделей (glm-5.3-flash / glm-5.3 из дайджеста расходов 25.09–02.10), выручка = P·√(полезность токенов), полезность flash-токенов слегка ниже (q_flash=0.95). P калибруется так, чтобы бейзлайн «как сейчас» давал −5% в день (лёгкое давление к оптимизации). В v0.2 P заменяется реальными выручочными ставками проектов кооператива. """ import json import os import random import sqlite3 import statistics from datetime import datetime, timezone # ────────────────────────── источники данных ────────────────────────── ROUTER_DB = os.environ.get( "SIMBIOT_ROUTER_DB", "/root/.9router/db/data.sqlite") STORE_DB = os.environ.get( "SIMBIOT_DB", os.path.join(os.path.dirname(os.path.abspath(__file__)), "harness.db")) HARNESS_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) BALANCES_JSON = os.path.join(HARNESS_ROOT, "docs", "aum", "balances.json") COOP_JSON = os.environ.get("SIMBIOT_COOP_JSON", "/root/coop_data.json") RATE_FLASH = 0.001022 # у.е./токен glm-5.3-flash (дайджест 25.09–02.10) RATE_FULL = 0.001058 # у.е./токен glm-5.3 Q_FLASH = 0.95 # полезность flash-токена относительно полного BASELINE_NET = 0.95 # выручка бейзлайна = 95% расхода (−5%/день) HORIZON_DAYS = 365 def _fmt(v): return None if v is None else round(float(v), 2) # ────────────────────────── чтение данных ────────────────────────── def read_balances(): """Балансы кооператива из docs/aum/balances.json. total=None если нет файла.""" try: path = os.environ.get("SIMBIOT_BALANCES_JSON", BALANCES_JSON) with open(path) as f: data = json.load(f) accs = [a for a in data.get("accounts", []) if a.get("amount_usd") is not None] return {"total": _fmt(sum(float(a["amount_usd"]) for a in accs)) if accs else None, "accounts": accs, "src": "balances.json"} except Exception: return {"total": None, "accounts": [], "src": "balances.json (нет файла)"} def read_router_spend(days: int = 7): """Суточный расход 9router за N дней: {date: {cost, reqs}}. Ошибка → {}.""" try: db = sqlite3.connect(f"file:{ROUTER_DB}?mode=ro", timeout=5, uri=True) rows = db.execute( "SELECT substr(timestamp,1,10) d, ROUND(SUM(cost),4) c, COUNT(*) r " "FROM usageHistory WHERE timestamp >= datetime('now', ?) " "GROUP BY d ORDER BY d", (f"-{int(days)} days",)).fetchall() db.close() return {d: {"cost": c, "reqs": r} for d, c, r in rows} except Exception: return {} # Блендед-ставка токен→у.е., откалибрована на окне 25.09–02.10 # (docs/digests/9router-spend-2026-09-25_10-02.md): 2491.47 у.е. / # 2.642 млрд токенов флота ≈ 0.000943 у.е./токен. Порядок величины, не счёт. BURN_RATE_PER_TOKEN = float(os.environ.get( "SIMBIOT_BURN_RATE_PER_TOKEN", "0.000943")) def read_store_spend(days: int = 7): """Fallback-расход из событий BudgetSpent стора харнеса (только СВОИ тики). Используется, когда живая БД 9router недоступна (миграция хоста унесла /root/.9router): метрика burn не должна молча зануляться — иначе policy.aum_target врёт «burn=0» в контексте каждого тика. Оценка блендед-ставкой; в событиях нет разбивки по моделям. """ try: db = sqlite3.connect(f"file:{STORE_DB}?mode=ro", timeout=5, uri=True) rows = db.execute( "SELECT substr(timestamp,1,10) d, COUNT(*) r, " "SUM(COALESCE(json_extract(data,'$.prompt_tokens'),0)" "+COALESCE(json_extract(data,'$.completion_tokens'),0)) t " "FROM events WHERE event_type='BudgetSpent' " "AND timestamp >= datetime('now', ?) " "GROUP BY d ORDER BY d", (f"-{int(days)} days",)).fetchall() db.close() return {d: {"cost": round(t * BURN_RATE_PER_TOKEN, 4), "reqs": r} for d, r, t in rows} except Exception: return {} def read_coop_structure(): """Структура кооператива (проекты/выручочные доли) — множитель метрики.""" try: with open(COOP_JSON) as f: data = json.load(f) return {"title": (data.get("mission") or {}).get("title"), "projects": [{"name": p.get("name"), "revenue_pct": p.get("revenue_pct")} for p in data.get("projects", [])]} except Exception: return {"title": None, "projects": []} # ────────────────────────── замер ────────────────────────── def measure(): """Снапшот метрики: AUM (если замерен) + burn 24ч/7д + структура кооператива.""" bal = read_balances() spend = read_router_spend(days=7) spend_src = "9router-db" if not spend: # БД роутера недоступна → не молчим, а считаем из стора spend = read_store_spend(days=7) spend_src = "store-BudgetSpent-оценка" if spend else "недоступен" days_iter = list(spend.values()) return { "ts": datetime.now(timezone.utc).isoformat(), "aum": bal["total"], "balances_src": bal["src"], "burn_24h": _fmt(days_iter[-1]["cost"]) if days_iter else 0.0, "burn_7d": _fmt(sum(v["cost"] for v in days_iter)), "spend_days": spend, "burn_src": spend_src, "coop": read_coop_structure(), } # ────────────────────────── симуляция сценариев ────────────────────────── def _model_params(config, productivity): volume = min(3.0, max(0.05, float(config.get("volume_mult", 1.0)))) flash = min(1.0, max(0.0, float(config.get("flash_share", 0.84)))) rate = flash * RATE_FLASH + (1 - flash) * RATE_FULL quality = flash * Q_FLASH + (1 - flash) * 1.0 return volume, rate, quality, productivity def calibrate(burn_hist: list, base_config: dict) -> float: """P бейзлайна: выручка = BASELINE_NET × средний burn (давление к оптимизации).""" mean_b = statistics.mean(burn_hist) or 100.0 _, rate, quality, _ = _model_params(base_config, 0.0) utility = (mean_b / rate) * quality return BASELINE_NET * mean_b / max(utility ** 0.5, 1e-9) def simulate(config: dict, burn_hist: list, productivity: float, days: int = HORIZON_DAYS, seed: int = 42, trials: int = 120) -> dict: """E[ΔAUM в горизонте days]: бюджет дня B=volume·burn_t покупает токены B/rate; полезность токенов×quality → выручка P·√польз; Δ = выручка − B.""" volume, rate, quality, P = _model_params(config, productivity) rng = random.Random(seed) finals = [] for _ in range(trials): aum = 0.0 for _d in range(days): B = volume * rng.choice(burn_hist) utility = (B / rate) * quality aum += P * (utility ** 0.5) - B finals.append(aum) return {"mean": statistics.mean(finals), "p10": statistics.quantiles(finals, n=10)[0]} def score(config, burn_hist, productivity, seed=42): return simulate(config, burn_hist, productivity, seed=seed)["mean"] # ────────────────────────── генетический отбор ────────────────────────── def evolve(burn_hist: list, pop_size: int = 12, generations: int = 6, seed: int = 7) -> dict: """Генетический отбор по fitness=E[ΔAUM]; песочница — отчёт-адвокат.""" rng = random.Random(seed) base_cfg = {"volume_mult": 1.0, "flash_share": 0.84} # «как сейчас» P = calibrate(burn_hist, base_cfg) base_fit = score(base_cfg, burn_hist, P, seed=seed) def mutate(c): return {"volume_mult": round(min(3.0, max(0.05, c["volume_mult"] + rng.gauss(0, 0.2))), 3), "flash_share": round(min(1.0, max(0.0, c["flash_share"] + rng.gauss(0, 0.12))), 3)} def crossover(a, b): return {"volume_mult": round((a["volume_mult"] + b["volume_mult"]) / 2, 3), "flash_share": round((a["flash_share"] + b["flash_share"]) / 2, 3)} pop = [{"volume_mult": round(rng.uniform(0.3, 1.8), 3), "flash_share": round(rng.uniform(0.2, 1.0), 3)} for _ in range(pop_size)] best, best_fit = base_cfg, base_fit history = [] for g in range(generations): scored = sorted(((score(c, burn_hist, P, seed=seed + g), c) for c in pop), reverse=True) if scored[0][0] > best_fit: best_fit, best = scored[0][0], dict(scored[0][1]) history.append(round(scored[0][0], 2)) elite = [c for _, c in scored[: max(2, pop_size // 3)]] pop = list(elite) while len(pop) < pop_size: pop.append(mutate(crossover(rng.choice(elite), rng.choice(elite)))) gap = (best_fit - base_fit) / abs(base_fit) * 100 if base_fit else 0.0 return { "baseline": base_cfg, "baseline_fitness": round(base_fit, 2), "best": best, "best_fitness": round(best_fit, 2), "improves": bool(best_fit > base_fit + 1e-9), "improvement_pct": round(gap, 2), "generations": generations, "pop_size": pop_size, "productivity_P": round(P, 4), "burn_mean_day": round(statistics.mean(burn_hist), 2), "fitness_history": history, "verdict": ("конфигурация лучше статус-кво — вынести дню/Alex'у " "(адвокат, не авто-применение)") if best_fit > base_fit + 1e-9 else "статус-кво остаётся", } def run_pilot(m: dict) -> dict: """Пилот на свежем замере: история burn → симуляции → отбор → вывод.""" hist = [v["cost"] for v in m.get("spend_days", {}).values()] or [100.0] return {"ts": m["ts"], "aum": m["aum"], "burn_7d": m.get("burn_7d"), "evolution": evolve(hist), "note": "вход — суточный burn 9router; выход — рекомендации у.е. расхода"} def build_policy_aum_summary(m: dict, pilot: dict = None) -> str: """Однострочник для policy/контекста всех уровней.""" aum = m.get("aum") part = (f"AUM={aum} у.е." if aum is not None else "AUM пока не замерен (когда будет баланс — занеси в " "docs/aum/balances.json)") ev = (pilot or {}).get("evolution") or {} tail = f"; последний отбор: {ev.get('verdict', 'не запускался')}" if pilot else "" src = m.get("burn_src") or "9router-db" burn = (f"≈{m.get('burn_7d', 0)} (оценка: BudgetSpent стора × блендед-ставка; " f"живая БД 9router недоступна)" if src.startswith("store") else f"{m.get('burn_7d', 0)}") return (f"ЦЕЛЬ: максимизация долгосрочной AUM (sum всех измеримых ресурсов " f"на длинной дистанции). {part}; burn 9router 7д=" f"{burn} у.е. (прямой минус к AUM). Фильтр любого " f"решения и задачи: «добавляет ≥ +1% к AUM в горизонте год+?»; " f"короткий доход, съедающий долгосрочную базу — ниже приоритетом." f"{tail}")