94 lines
4.2 KiB
Python
94 lines
4.2 KiB
Python
"""Главная точка для движка: по произнесённой нише вернуть готовую справку.
|
||
Лесенка: niche → берём карточку + родителя; domain → общее домена (+опц. Sonar на специфику);
|
||
miss → Sonar + заводим карточку. Sonar и эмбеддер инъектируются (тестируемо).
|
||
Индекс ПРЕДПОСЧИТАН и передаётся снаружи — на живом звонке эмбеддится только произнесённая
|
||
ниша (1 вызов), а не весь индекс каждый раз. Новую карточку добавляем в индекс точечно."""
|
||
import re
|
||
from pathlib import Path
|
||
from brain.cards import parse_card, assemble_brief, Card
|
||
from brain.index_store import save_index
|
||
from brain.lookup import find_niche
|
||
|
||
ORPHAN_PARENT = "(не разобрано)" # метка сиротки (miss без домена), чтобы её НЕ считали доменом
|
||
|
||
|
||
def _load_cards(nishi_dir):
|
||
cards, by_name = [], {}
|
||
for md in sorted(Path(nishi_dir).rglob("*.md")):
|
||
c = parse_card(md)
|
||
cards.append(c)
|
||
by_name[c.nisha] = c
|
||
return cards, by_name
|
||
|
||
|
||
def _slug(s):
|
||
s = re.sub(r"[^\wа-яё\- ]+", "", s.lower()).strip()
|
||
return re.sub(r"\s+", "-", s)[:60] or "nisha"
|
||
|
||
|
||
def _write_card(nishi_dir, niche, brief, roditel=None, obnovleno="—"):
|
||
folder = Path(nishi_dir)
|
||
fm = [f"nisha: {niche}"]
|
||
if roditel:
|
||
folder = folder / _slug(roditel)
|
||
fm.append(f"roditel: {roditel}")
|
||
else:
|
||
fm.append(f"roditel: {ORPHAN_PARENT}") # miss-сиротка помечена, не считается доменом
|
||
folder.mkdir(parents=True, exist_ok=True)
|
||
base = _slug(niche)
|
||
path = folder / f"{base}.md"
|
||
n = 2
|
||
while path.exists(): # не затираем существующую карточку
|
||
path = folder / f"{base}-{n}.md"
|
||
n += 1
|
||
fm += ["istochnik: perplexity-sonar", f"obnovleno: {obnovleno}",
|
||
f"trendy_obnovleno: {obnovleno}"]
|
||
path.write_text("---\n" + "\n".join(fm) + "\n---\n" + brief.strip() + "\n",
|
||
encoding="utf-8")
|
||
return path
|
||
|
||
|
||
def _index_append(index, path, embed_fn, index_path=None):
|
||
"""Добавить одну новую карточку в индекс (эмбеддим ТОЛЬКО её). Опц. сохранить индекс на диск."""
|
||
card = parse_card(path)
|
||
key_text = card.nisha + " " + " ".join(card.sinonimy)
|
||
index.append({
|
||
"nisha": card.nisha,
|
||
"roditel": card.roditel,
|
||
"sinonimy": card.sinonimy,
|
||
"path": str(path),
|
||
"vector": embed_fn(key_text),
|
||
})
|
||
if index_path:
|
||
save_index(index, index_path)
|
||
return index
|
||
|
||
|
||
def get_brief(spoken, nishi_dir, index, embed_fn, sonar_fn,
|
||
today="—", sonar_on_domain=False, index_path=None):
|
||
"""index — ПРЕДПОСЧИТАННЫЙ индекс (список dict), грузится снаружи один раз (load_index).
|
||
Здесь эмбеддится только `spoken` (внутри find_niche). Возвращает (brief, source)."""
|
||
_, by_name = _load_cards(nishi_dir)
|
||
res = find_niche(spoken, index, embed_fn=embed_fn)
|
||
|
||
if res.kind == "niche":
|
||
card = by_name.get(res.entry["nisha"])
|
||
return assemble_brief(card, by_name), "niche"
|
||
|
||
if res.kind == "domain":
|
||
domain_card = by_name.get(res.entry["nisha"])
|
||
common = assemble_brief(domain_card, by_name)
|
||
if sonar_on_domain:
|
||
specifics = sonar_fn(spoken)
|
||
newpath = _write_card(nishi_dir, spoken, specifics, roditel=domain_card.nisha,
|
||
obnovleno=today)
|
||
_index_append(index, newpath, embed_fn, index_path)
|
||
return (common + "\n\n" + specifics).strip(), "domain"
|
||
return common, "domain"
|
||
|
||
# miss: совсем новое → Sonar + новая карточка-сиротка (помечена ORPHAN_PARENT)
|
||
brief = sonar_fn(spoken)
|
||
newpath = _write_card(nishi_dir, spoken, brief, roditel=None, obnovleno=today)
|
||
_index_append(index, newpath, embed_fn, index_path)
|
||
return brief, "miss"
|