@
feat(sudya): оркестратор, карточка звонка и приёмка на подсадных ошибках Оркестратор сам ничего не проверяет — собирает слои и выносит итог: любой СТОП → СТОП, иначе ТРЕВОГА, иначе НОРМА. Сломанный якорь перекрывает всё. В бою полная коллегия не зовётся — только приборы, деньги владельца берегутся. Приёмка судьи: девять диалогов с нарочно посаженным браком (выдуманное число, перевёрнутая цена, мужской род о себе, «ты», своё имя вместо обращения, латиница, потерянная реплика, выдумка ушей, долгая пауза) — судья обязан найти каждый. Плюс чистый якорь, на котором он обязан промолчать: если судья бракует хорошее, это хуже пропущенного брака. Все десять проверок прошли. Проверено сквозняком на живом журнале, а не только на выдуманных примерах: судья честно поймал, что она говорит 57% времени при норме 35–45%. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> @
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# Brain Status (auto-generated)
|
||||
|
||||
Last updated: 2026-07-18T15:47:56.496Z
|
||||
Last updated: 2026-07-18T15:55:07.060Z
|
||||
|
||||
| Контролёр | Состояние | Детали |
|
||||
|---|---|---|
|
||||
@@ -112,8 +112,8 @@ Episodes since last run: 542 / threshold: 10
|
||||
|
||||
| PID | Имя | CPU-время | Возраст |
|
||||
|---|---|---|---|
|
||||
| 3540 | MsMpEng | 3.19ч | 221035.8ч |
|
||||
| 10836 | Code | 1.32ч | NaNч |
|
||||
| 3540 | MsMpEng | 3.22ч | 0.0ч |
|
||||
| 10836 | Code | 1.35ч | 0.0ч |
|
||||
|
||||
⚠️ Проверь, не «осиротевшие» ли это процессы от завершённых Claude-сессий.
|
||||
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""ОРКЕСТРАТОР судьи: собирает слои и выносит итог.
|
||||
|
||||
🔴 Сам ничего не проверяет — только зовёт приборы и коллегию и складывает карточку.
|
||||
Правило итога: любой СТОП → СТОП; иначе есть ТРЕВОГА → ТРЕВОГА; иначе НОРМА.
|
||||
Сломанный якорь перекрывает всё: партия не считается.
|
||||
"""
|
||||
import kollegia
|
||||
import pribory
|
||||
|
||||
|
||||
def sudit(r, yakor_validen=True, polnaya_kollegia=None):
|
||||
"""r — Razgovor. Режим «бой»: звук не судим (записи живых клиентов не храним — ПДн)."""
|
||||
nahodki = []
|
||||
nahodki += pribory.proverit_chisla(r)
|
||||
nahodki += pribory.proverit_tehniku(r)
|
||||
nahodki += pribory.proverit_harakter(r)
|
||||
nahodki += pribory.proverit_perebivanie(r)
|
||||
|
||||
# Полная коллегия — на приёмке; в бою только по запросу (экономим деньги)
|
||||
if polnaya_kollegia is None:
|
||||
polnaya_kollegia = (r.rezhim == "приёмка")
|
||||
if polnaya_kollegia:
|
||||
nahodki += kollegia.sudit_vsemi(r)
|
||||
|
||||
tyazhesti = {n["tyazhest"] for n in nahodki}
|
||||
if not yakor_validen:
|
||||
itog = "ПАРТИЯ НЕДЕЙСТВИТЕЛЬНА"
|
||||
elif "СТОП" in tyazhesti:
|
||||
itog = "СТОП"
|
||||
elif "ТРЕВОГА" in tyazhesti:
|
||||
itog = "ТРЕВОГА"
|
||||
else:
|
||||
itog = "НОРМА"
|
||||
|
||||
return {"id": r.id, "rezhim": r.rezhim, "itog": itog,
|
||||
"yakor_validen": yakor_validen,
|
||||
"nahodki": sorted(nahodki, key=lambda n: (n["spektr"], n["priznak"])),
|
||||
"cifry": {"dolya_ee_rechi": round(r.dolya_rechi(), 2),
|
||||
"perebivanij": r.sobytiya.get("perebivanij", 0),
|
||||
"poter_replik": r.sobytiya.get("poter_replik", 0)},
|
||||
"stoimost_rub": kollegia.raskhod()["rubley"]}
|
||||
|
||||
|
||||
def kratko(k):
|
||||
"""Человеческий вид карточки — для владельца, не для машины."""
|
||||
znak = {"СТОП": "🛑", "ТРЕВОГА": "⚠️", "НОРМА": "✅"}.get(k["itog"], "❓")
|
||||
stroki = [f"{znak} {k['itog']} — разговор {k['id']} ({k['rezhim']})"]
|
||||
for n in k["nahodki"]:
|
||||
stroki.append(f" [{n['tyazhest']}] признак {n['priznak']}: «{n['citata']}»"
|
||||
f" — {n['istochnik'] or 'без источника'}")
|
||||
stroki.append(f" цифры: {k['cifry']} · разбор стоил {k['stoimost_rub']} ₽")
|
||||
return "\n".join(stroki)
|
||||
@@ -0,0 +1,52 @@
|
||||
# golos-test/sudya/dialog/tests/test_podsadnye.py
|
||||
"""ПОДСАДНЫЕ ОШИБКИ — приёмка самого судьи.
|
||||
|
||||
В каждый диалог НАРОЧНО внесён ровно один брак. Судья обязан найти именно его.
|
||||
Плюс чистый диалог-якорь: на нём судья обязан промолчать. Если якорь получает
|
||||
СТОП — судья сломан, и всем прочим его вердиктам верить нельзя.
|
||||
"""
|
||||
import pytest
|
||||
import razgovor, sudya
|
||||
|
||||
CHISTO = {"perebivanij": 0, "zhdal_dumayushchego": 0, "vydumok_ushey": 0, "poter_replik": 0}
|
||||
|
||||
|
||||
def _dialog(otvet_alex, sobytiya=None, zamery=None):
|
||||
repliki = [
|
||||
{"kto": "клиент", "tekst": "Здравствуйте, а сколько стоит и как это работает?", "prervana": False},
|
||||
{"kto": "александра", "tekst": otvet_alex, "prervana": False},
|
||||
{"kto": "клиент", "tekst": "Понятно, надо подумать.", "prervana": False},
|
||||
{"kto": "александра", "tekst": "Хорошо, проверьте на своём номере. Как вам такой шаг?", "prervana": False},
|
||||
]
|
||||
sob = dict(CHISTO, **(sobytiya or {}))
|
||||
return razgovor.Razgovor("подсадной", "бой", repliki, sob,
|
||||
zamery=zamery or {"do_zvuka": [1.4], "do_otveta": [8.0]})
|
||||
|
||||
|
||||
PODSADNYE = [
|
||||
(14, _dialog("Выйдет примерно двести рублей за заявку.")),
|
||||
(15, _dialog("От пятидесяти пяти до двадцати пяти рублей за заявку.")),
|
||||
(22, _dialog("Я посмотрел вашу заявку, всё в порядке.")),
|
||||
(23, _dialog("Ты сам увидишь, сколько придёт заявок.")),
|
||||
(27, _dialog("Александра, это не совсем про рекламу.")),
|
||||
(29, _dialog("Это работает как no-show контроль.")),
|
||||
(10, _dialog("Живые номера тех, кто ищет.", sobytiya={"poter_replik": 1})),
|
||||
(11, _dialog("Живые номера тех, кто ищет.", sobytiya={"vydumok_ushey": 1})),
|
||||
(6, _dialog("Живые номера тех, кто ищет.", zamery={"do_zvuka": [5.0], "do_otveta": [8.0]})),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("nomer,r", PODSADNYE, ids=[str(n) for n, _ in PODSADNYE])
|
||||
def test_sudya_nahodit_podsadnuyu_oshibku(nomer, r, monkeypatch):
|
||||
monkeypatch.setattr(sudya.kollegia, "sudit_vsemi", lambda r: [])
|
||||
k = sudya.sudit(r)
|
||||
najdennye = {n["priznak"] for n in k["nahodki"]}
|
||||
assert nomer in najdennye, f"судья не нашёл подсадной признак {nomer}: {k['nahodki']}"
|
||||
|
||||
|
||||
def test_chistyy_yakor_prohodit_bez_zamechaniy(monkeypatch):
|
||||
"""🔴 Якорь: диалог, принятый владельцем. Судья обязан промолчать."""
|
||||
monkeypatch.setattr(sudya.kollegia, "sudit_vsemi", lambda r: [])
|
||||
yakor = _dialog("Поняла вас. Живые номера тех, кто уже ищет вашу услугу. Как смотрите?")
|
||||
k = sudya.sudit(yakor)
|
||||
assert k["itog"] == "НОРМА", f"судья забраковал ЯКОРЬ — он сломан: {k['nahodki']}"
|
||||
@@ -0,0 +1,49 @@
|
||||
import razgovor, sudya
|
||||
|
||||
|
||||
def _r(*teksty, **kw):
|
||||
repliki = []
|
||||
for t in teksty:
|
||||
repliki.append({"kto": "клиент", "tekst": "и почём выходит?", "prervana": False})
|
||||
repliki.append({"kto": "александра", "tekst": t, "prervana": False})
|
||||
sob = {"perebivanij": 0, "zhdal_dumayushchego": 0, "vydumok_ushey": 0, "poter_replik": 0}
|
||||
sob.update(kw.pop("sobytiya", {}))
|
||||
return razgovor.Razgovor("з-1", kw.pop("rezhim", "бой"), repliki, sob,
|
||||
zamery={"do_zvuka": [1.4], "do_otveta": [8.0]})
|
||||
|
||||
|
||||
def test_stop_kogda_est_grubaya_nahodka(monkeypatch):
|
||||
monkeypatch.setattr(sudya.kollegia, "sudit_vsemi", lambda r: [])
|
||||
k = sudya.sudit(_r("Заявка выйдет двести рублей."))
|
||||
assert k["itog"] == "СТОП"
|
||||
|
||||
|
||||
def test_trevoga_kogda_tolko_myagkie(monkeypatch):
|
||||
monkeypatch.setattr(sudya.kollegia, "sudit_vsemi", lambda r: [])
|
||||
k = sudya.sudit(_r("Ты сам всё увидишь."))
|
||||
assert k["itog"] == "ТРЕВОГА"
|
||||
|
||||
|
||||
def test_norma_na_chistom(monkeypatch):
|
||||
monkeypatch.setattr(sudya.kollegia, "sudit_vsemi", lambda r: [])
|
||||
k = sudya.sudit(_r("Поняла вас. Живые номера тех, кто уже ищет вашу услугу. Как смотрите?"))
|
||||
assert k["itog"] == "НОРМА"
|
||||
|
||||
|
||||
def test_v_boyu_zvuk_ne_sudim(monkeypatch):
|
||||
monkeypatch.setattr(sudya.kollegia, "sudit_vsemi", lambda r: [])
|
||||
k = sudya.sudit(_r("Поняла вас."))
|
||||
assert all(n["spektr"] != "I" for n in k["nahodki"])
|
||||
|
||||
|
||||
def test_kartochka_soderzhit_obyazatelnye_polya(monkeypatch):
|
||||
monkeypatch.setattr(sudya.kollegia, "sudit_vsemi", lambda r: [])
|
||||
k = sudya.sudit(_r("Поняла вас."))
|
||||
for pole in ("id", "rezhim", "itog", "nahodki", "cifry", "stoimost_rub"):
|
||||
assert pole in k
|
||||
|
||||
|
||||
def test_slomannyy_yakor_perekryvaet_vsyo(monkeypatch):
|
||||
monkeypatch.setattr(sudya.kollegia, "sudit_vsemi", lambda r: [])
|
||||
k = sudya.sudit(_r("Поняла вас."), yakor_validen=False)
|
||||
assert k["itog"] == "ПАРТИЯ НЕДЕЙСТВИТЕЛЬНА"
|
||||
Reference in New Issue
Block a user