Files
portal/knowledge/aleksandra/brain/tests/test_research.py
T

79 lines
4.6 KiB
Python

from pathlib import Path
from brain.research import get_brief, ORPHAN_PARENT
from brain.index_store import build_index
from brain.cards import parse_card
def fake_embed(text):
t = text.lower()
if "имплант" in t or "зуб" in t or "стоматолог" in t:
return [0.98, 0.05, 0.0]
if "медиц" in t or "клиник" in t or "маммолог" in t:
return [0.6, 0.6, 0.0]
return [0.0, 0.0, 1.0]
def make_vault(tmp_path):
nishi = tmp_path / "nishi"
(nishi / "медицина").mkdir(parents=True)
(nishi / "медицина.md").write_text(
"---\nnisha: Медицина\nsinonimy: [клиника]\n---\nОбщее: доверие и репутация.",
encoding="utf-8")
(nishi / "медицина" / "стоматология.md").write_text(
"---\nnisha: стоматология / имплантация\nroditel: Медицина\n"
"sinonimy: [зубной, импланты]\n---\n## Специфика\nОстрая боль срочно.\n## Тренды\nДольше выбирают.",
encoding="utf-8")
return nishi
def test_niche_hit_composes_and_no_sonar(tmp_path):
nishi = make_vault(tmp_path)
index = build_index(nishi, embed_fn=fake_embed)
called = {"sonar": 0}
def sonar(niche): called["sonar"] += 1; return "НЕ ДОЛЖНО ВЫЗВАТЬСЯ"
brief, source = get_brief("хочу импланты", nishi_dir=nishi, index=index,
embed_fn=fake_embed, sonar_fn=sonar)
assert called["sonar"] == 0
assert source == "niche"
assert "доверие" in brief and "Острая боль" in brief
def test_domain_hit_returns_parent_common(tmp_path):
nishi = make_vault(tmp_path)
index = build_index(nishi, embed_fn=fake_embed)
def sonar(niche): return "спец по маммологии"
brief, source = get_brief("маммология", nishi_dir=nishi, index=index,
embed_fn=fake_embed, sonar_fn=sonar, sonar_on_domain=False)
assert source == "domain"
assert "доверие" in brief
def test_miss_calls_sonar_writes_orphan_card_and_grows_index(tmp_path):
nishi = make_vault(tmp_path)
index = build_index(nishi, embed_fn=fake_embed)
n_before = len(index)
def sonar(niche): return f"свежая справка про {niche}"
brief, source = get_brief("грузоперевозки фурами", nishi_dir=nishi, index=index,
embed_fn=fake_embed, sonar_fn=sonar)
assert source == "miss"
assert "грузоперевозки" in brief
# карточка завелась на диск и помечена как сиротка (НЕ домен)
created = [p for p in nishi.rglob("*.md") if "груз" in p.read_text(encoding="utf-8").lower()]
assert created, "карточка-сиротка не создана"
assert parse_card(created[0]).roditel == ORPHAN_PARENT
# индекс подрос ровно на новую карточку (без полной пересборки)
assert len(index) == n_before + 1
def test_miss_does_not_overwrite_existing_card(tmp_path):
import copy
nishi = make_vault(tmp_path)
# два НЕЗАВИСИМЫХ снимка одного и того же (устаревшего) индекса — имитация гонки:
# два вызова стартуют с индекса ДО первой записи, оба классифицируют как miss.
# Общий/растущий индекс тут не подходит: с Фикс1 (точный синоним) + Фикс3/4 (точечный
# рост индекса) повторный ОДИНАКОВЫЙ запрос находит только что созданную карточку по
# имени и переиспользует её (это и есть желаемое поведение кеша) — так и должно быть.
base_index = build_index(nishi, embed_fn=fake_embed)
def sonar(niche): return "справка 1"
get_brief("грузоперевозки", nishi_dir=nishi, index=copy.deepcopy(base_index),
embed_fn=fake_embed, sonar_fn=sonar)
def sonar2(niche): return "справка 2"
get_brief("грузоперевозки", nishi_dir=nishi, index=copy.deepcopy(base_index),
embed_fn=fake_embed, sonar_fn=sonar2)
files = [p for p in nishi.rglob("*.md") if "груз" in p.read_text(encoding="utf-8").lower()]
assert len(files) == 2 # вторая не затёрла первую (защита в _write_card сработала)