# -*- coding: utf-8 -*- """t-260929161138: north-star метрика AUM (hermetic, без LLM/TG/9router). Запуск: ./venv/bin/python tests/test_aum.py Проверяет: замер не падает без БД роутера и balances.json (AUM=None), детерминизм пилота (seed), отбор находит конфигурацию не хуже бейзлайна, событие AumMeasured мержит policy.aum_target + копит aum_history (кап 120), build_context инжектит «ЦЕЛЬ (AUM)» всем уровням, CLI aum/aum-pilot/--record существуют, AumMeasured идемпотентен по aggregate_id. """ import io import json import os import sys import contextlib # ── изоляция: своя БД в /tmp, роутер и balances заведомо отсутствуют ── _DST = "/tmp/simbiot-test-aum.db" for p in (_DST, _DST + "-wal", _DST + "-shm"): if os.path.exists(p): os.unlink(p) os.environ["SIMBIOT_DB"] = _DST os.environ["SIMBIOT_ROUTER_DB"] = "/nonexistent-router.sqlite" BAL_TMP = "/tmp/simbiot-test-balances.json" if os.path.exists(BAL_TMP): os.unlink(BAL_TMP) os.environ["SIMBIOT_BALANCES_JSON"] = BAL_TMP sys.path.insert(0, "/root/simbiot-harness") from harness import aum, es # noqa: E402 from harness.levels import LEVELS # noqa: E402 from harness.scheduler import build_context # noqa: E402 fails = [] def check(name, cond, extra=""): print(("PASS" if cond else "FAIL"), name, ("| " + extra) if not cond else "") if not cond: fails.append(name) # ── 1. замер hermetic: не падает, AUM=None, burn=0 ── m = aum.measure() check("measure hermetic ok", set(m) >= {"ts", "aum", "burn_24h", "burn_7d", "spend_days"}) check("aum None без файла", m["aum"] is None) check("burn 0 без роутера", m["burn_7d"] == 0 and m["spend_days"] == {}) # ── 2. balances с данными → total ── with open(BAL_TMP, "w") as f: json.dump({"accounts": [{"name": "a", "amount_usd": 12.5}, {"name": "b", "amount_usd": 7.5}]}, f) m2 = aum.measure() check("balances→total", m2["aum"] == 20.0) # ── 3. детерминизм эволюции ── h = [60., 300., 900., 500., 150., 200.] e1, e2 = aum.evolve(h), aum.evolve(h) check("эволюция детерминирована", e1["best"] == e2["best"] and e1["best_fitness"] == e2["best_fitness"]) check("отбор не хуже бейзлайна", e1["best_fitness"] >= e1["baseline_fitness"] - 1e-6) check("fitness_history по поколениям", len(e1["fitness_history"]) == e1["generations"]) # ── 4. симуляция: больше burn без отдачи — всегда минус (плейсхолдер честен) ── s_small = aum.simulate({"volume_mult": 0.2, "flash_share": 0.84}, h, aum.calibrate(h, {"volume_mult": 1.0, "flash_share": 0.84})) s_big = aum.simulate({"volume_mult": 2.5, "flash_share": 0.84}, h, aum.calibrate(h, {"volume_mult": 1.0, "flash_share": 0.84})) check("меньше объёма burn → лучше при плейсхолдер-модели", s_small["mean"] > s_big["mean"]) # ── 5. события AumMeasured: policy мержится, история копится, кап 120 ── # (повторный aggregate_id легален — каждая версия = новый снапшот; UNIQUE в es # защищает только тики cycle. Дневная частота обеспечивается идемпотентностью тика.) es.init_db() es.append_event("harness", "aum", "AumMeasured", { "ts": "2026-10-02T00:00:00+00:00", "aum": 20.0, "burn_24h": 1.5, "burn_7d": 10.0, "policy": {"aum_target": "ЦЕЛЬ: AUM↑ (тест)"}} ) model = es.run_projections() check("policy.aum_target записан", "AUM" in (model["policy"].get("aum_target") or "")) check("aum_history ведётся", len(model.get("aum_history", [])) == 1) es.append_event("harness", "aum", "AumMeasured", { "ts": "2026-10-02T06:00:00+00:00", "aum": 21.0, "burn_24h": 1.0, "burn_7d": 11.0, "policy": {"aum_target": "ЦЕЛЬ: AUM↑ (тест2)"}}) model = es.run_projections() check("повторный снапшот апендится в историю", len(model.get("aum_history", [])) == 2) check("aum_target перезаписан свежим", "тест2" in (model["policy"].get("aum_target") or "")) for i in range(118): es.append_event("harness", "aum", "AumMeasured", { "ts": f"2026-10-03T{i:02d}:00:00+00:00", "aum": float(i), "burn_24h": float(i), "burn_7d": float(i), "policy": {"aum_target": f"x{i}"}}) model = es.run_projections() check("кап истории 120", len(model.get("aum_history", [])) == 120) # ── 6. build_context инжектит цель всем LLM-уровням ── ok_ctx = True for name in ("min", "hour", "day"): ctx = build_context(LEVELS[name], model, batch=[]) if "ЦЕЛЬ (AUM)" not in ctx: ok_ctx = False print(f" ({name}: цель не в контексте)") check("ЦЕЛЬ (AUM) в контексте min/hour/day", ok_ctx) # ── 7. summary содержит фильтр +1% и burn ── summ = aum.build_policy_aum_summary(m2, {"evolution": e1}) check("summary: фильтр +1% и burn", "+1%" in summ and "burn" in summ) # ── 8. CLI: команды существуют и работают hermetic ── env = dict(os.environ, SIMBIOT_DB=_DST, SIMBIOT_ROUTER_DB="/nonexistent-router.sqlite", SIMBIOT_BALANCES_JSON=BAL_TMP) import subprocess r = subprocess.run(["/root/simbiot-harness/venv/bin/python", "/root/simbiot-harness/cli.py", "aum"], capture_output=True, text=True, timeout=60, env=env) check("cli aum ok", r.returncode == 0 and "AUM" in r.stdout, r.stderr[:200]) r = subprocess.run(["/root/simbiot-harness/venv/bin/python", "/root/simbiot-harness/cli.py", "aum", "--record"], capture_output=True, text=True, timeout=60, env=env) check("cli aum --record ok -> стор читает цель", r.returncode == 0 and "OK: AumMeasured" in r.stdout) model = es.run_projections() check("aum_target в сторе после --record", "AUM" in (model["policy"].get("aum_target") or "")) r = subprocess.run(["/root/simbiot-harness/venv/bin/python", "/root/simbiot-harness/cli.py", "aum-pilot"], capture_output=True, text=True, timeout=120, env=env) check("cli aum-pilot ok", r.returncode == 0 and "вердикт" in r.stdout, r.stderr[:200]) print() print("ИТОГ:", "ВСЕ ОК" if not fails else f"ПРОВАЛЫ: {fails}") sys.exit(1 if fails else 0)