Разбор гейта (трассы с 15.10): hour — 0% пустых тиков, min — 6% (15% токенов уровня) → модель-гейт не нужен. Пустые мин-тики — зацикливание: «бюджет исчерпан» возвращал S как S, мин брал её снова с нуля (S-задача 33 захвата за 2 ч; 45 из 272 задач ≥ 2 мин-захватов). - requeue_or_escalate: после 2 захватов на уровне (SIMBIOT_TASK_ATTEMPTS_ PER_LEVEL) размер растёт выше классов уровня (S→M, к часу, 300 шагов) — и для guard «бюджет исчерпан», и для смерти воркера; счётчик попыток best effort (сбой → обычный requeue, не ломает путь падения воркера); - kodi_core — несколько голов (общие: хранение/обучение/промоут/откат, nf в модели; SIMBIOT_CORE_<ГОЛОВА>). Голова task_fit: влезет ли S в мин-тик (метки — из истории задач); shadow — прогноз перед первым мин-захватом, on — уверенное «не влезет» сразу S→M. Честно: на 93 задачах 5 фолдов 0.58–0.62 при базе 0.52, уверенных 0% — пока инертна; - рефлексия (как reflexion.py): ≥ 3 расхождений вердикта адресации с меткой человека/чата или ≥ 3 S-задач, не влезших в мин → задача часу с автодоставкой: разобрать и предложить Alex правило (код не трогать); - недельный отчёт ядра (понедельник, без LLM, идемпотентно по неделе); ночью: train_all → reflect → weekly_report; cli.py core reflect|report|--head. Тесты: test_task_escalation 8, test_kodi_core_tasks 13, test_kodi_core_ reflect 10, test_kodi_core 28 PASS; полный набор — без регрессий. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
89 lines
4.5 KiB
Python
89 lines
4.5 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""Ядро Коди: рефлексия и недельный отчёт — hermetic.
|
||
|
||
Запуск: ./venv/bin/python tests/test_kodi_core_reflect.py
|
||
|
||
Проверяет: < порога ошибок — рефлексии нет; ≥ 3 расхождений вердикта с
|
||
меткой человека/чата → задача часу (M, автодоставка, случаи в постановке)
|
||
+ CoreReflection; повтор — только по новым случаям; задачи: ≥ 3 S-задач
|
||
не влезли в мин → своя рефлексия, разобранные не повторяются; недельный
|
||
отчёт: не в понедельник — нет, force — текст с метриками, повтор недели
|
||
— SKIP (идемпотентная доставка).
|
||
"""
|
||
import json
|
||
import os
|
||
import sys
|
||
import tempfile
|
||
|
||
sys.path.insert(0, "/root/simbiot-harness")
|
||
_tmp = tempfile.mkdtemp(prefix="simbiot_core_refl_")
|
||
os.environ["SIMBIOT_DB"] = os.path.join(_tmp, "harness.db")
|
||
os.environ["SIMBIOT_CORE_DIR"] = os.path.join(_tmp, "core")
|
||
os.environ.pop("SIMBIOT_CORE_REFLECT_MIN", None)
|
||
|
||
from harness import es, kodi_core, tg_inbox
|
||
|
||
es.init_db()
|
||
fails = []
|
||
|
||
|
||
def check(name, cond, extra=""):
|
||
print(("PASS" if cond else "FAIL"), name, ("| " + str(extra)) if not cond else "")
|
||
if not cond:
|
||
fails.append(name)
|
||
|
||
|
||
SENT = []
|
||
tg_inbox.notify_chat = lambda text: SENT.append(text) or {"ok": True}
|
||
G = -1004402779881
|
||
N = [0]
|
||
|
||
|
||
def wrong(verdict_addressed: bool, truth: int, src="feedback"):
|
||
N[0] += 1
|
||
mid = f"m-{N[0]}"
|
||
es.append_event("inbox", mid, "MessageReceived", {"update_id": N[0], "chat_id": G,
|
||
"chat_type": "supergroup", "first_name": "Alex", "text": f"реплика {N[0]}"})
|
||
es.append_event("inbox", mid, "MessageTriaged" if verdict_addressed else "MessageIgnored",
|
||
{"addressed": verdict_addressed, "reason": "not_addressed", "rule": "decision", "p": .6})
|
||
es.append_event("core", f"lab:{mid}", "CoreLabel" if src == "human" else "CoreFeedback",
|
||
{"head": "addressing", "msg": mid, "label": truth, "source": src})
|
||
|
||
|
||
def tasks_with(event_type):
|
||
return [json.loads(e["data"]) for e in es.get_events(0, 10**6) if e["event_type"] == event_type]
|
||
|
||
|
||
wrong(True, 0); wrong(False, 1)
|
||
check("2 ошибки < порога — рефлексии нет", kodi_core.reflect(log=lambda *_: None) == [])
|
||
wrong(True, 0, "human")
|
||
made = kodi_core.reflect(log=lambda *_: None)
|
||
sub = {t["id"]: t for t in tasks_with("TaskSubmitted")}
|
||
check("3 ошибки → задача рефлексии", len(made) == 1 and "Рефлексия адресации" in sub[made[0]]["title"], made)
|
||
check("задача — к часу (M) с автодоставкой",
|
||
sub[made[0]]["size"] == "M" and es.run_projections()["tasks"][made[0]].get("await_delivery"))
|
||
check("случаи в постановке", "я ответил" in sub[made[0]]["details"] and "было не боту" in sub[made[0]]["details"])
|
||
check("повтор без новых случаев — нет", kodi_core.reflect(log=lambda *_: None) == [])
|
||
|
||
# задачи: 3 S-задачи не влезли
|
||
for i in range(3):
|
||
tid = f"t-{i}"
|
||
es.append_event("task", tid, "TaskSubmitted", {"id": tid, "title": f"найди плашку {i}", "size": "S"})
|
||
es.append_event("task", tid, "TaskClaimed", {"level": "min"})
|
||
es.append_event("task", tid, "TaskClaimed", {"level": "min"})
|
||
made2 = kodi_core.reflect(log=lambda *_: None)
|
||
sub = {t["id"]: t for t in tasks_with("TaskSubmitted")}
|
||
check("задачи: 3 не влезли → рефлексия задач", len(made2) == 1 and "Рефлексия задач" in sub[made2[0]]["title"], made2)
|
||
check("задачи: разобранные не повторяются", kodi_core.reflect(log=lambda *_: None) == [])
|
||
|
||
# недельный отчёт
|
||
from datetime import datetime, timezone
|
||
if datetime.now(timezone.utc).weekday() != 0:
|
||
check("не понедельник — отчёта нет", kodi_core.weekly_report() is None and not SENT)
|
||
txt = kodi_core.weekly_report(force=True)
|
||
check("отчёт: текст с головами и метриками", txt and "Адресация в группах" in txt and "Задачи" in txt
|
||
and "рефлексий" in txt and SENT, txt)
|
||
check("отчёт: повтор недели — SKIP", kodi_core.weekly_report(force=True) is None and len(SENT) == 1)
|
||
|
||
print(f"\n{'OK' if not fails else 'FAILED'}: {len(fails)} fail(s)")
|
||
sys.exit(1 if fails else 0)
|