feat(день2): экран верификации с предложениями + обучение, детектор переплаты

- очередь верификации: предложенный кандидат (топ эмбеддинг), подтвердить/другое/пропустить
- POST /match пополняет синонимы (learned_synonym) — обучающаяся нормализация
- сравнение: медиана + разброс цен + отклонение клиники (переплата/выгода)
- эмбеддер: дедуп названий + ретрай при 429 (квота Gemini)
- развёрнуто на med.secondbrain.tools

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-27 10:47:57 +05:00
parent 9c0ae2c0bf
commit 1ff5d93cd8
6 changed files with 181 additions and 35 deletions
+12 -2
View File
@@ -43,6 +43,8 @@ class GeminiEmbedder:
RETRIEVAL_QUERY: так несвязанные названия расходятся сильнее, чем при
SEMANTIC_SIMILARITY, и порог отсечения работает надёжнее.
"""
import time
from google.genai import types
items = [t if t and t.strip() else " " for t in texts]
@@ -50,8 +52,16 @@ class GeminiEmbedder:
vectors: list[list[float]] = []
for start in range(0, len(items), self.batch):
chunk = items[start : start + self.batch]
response = self._client.models.embed_content(model=self.model, contents=chunk, config=config)
vectors.extend(embedding.values for embedding in response.embeddings)
for attempt in range(8):
try:
response = self._client.models.embed_content(model=self.model, contents=chunk, config=config)
vectors.extend(embedding.values for embedding in response.embeddings)
break
except Exception as exc: # пауза и повтор при превышении квоты (429)
if ("RESOURCE_EXHAUSTED" in str(exc) or "429" in str(exc)) and attempt < 7:
time.sleep(20)
continue
raise
array = np.array(vectors, dtype=np.float32)
if normalize_embeddings: