feat: 8 optimisations vitesse + qualité pipeline CIM-10

1. Parallélisation intra-dossier (RAG + DP selector en parallèle)
2. Cache embeddings FAISS (_embed_cached avec LRU)
3. Lazy loading edsnlp (déjà singleton, vérifié)
4. Prompt DP amélioré avec règles PMSI/ATIH
5. Validation croisée Bio↔DAS (cohérence biologie/diagnostics)
6. Resélection DP après vetos/exclusions (reselect_dp_after_vetos)
7. Pré-filtrage R-codes (déjà implémenté dans exclusion_rules)
8. Cache embeddings texte (intégré dans rag_search)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
dom
2026-03-07 22:18:07 +01:00
parent e6bd7406a4
commit 63f61f196b
5 changed files with 268 additions and 30 deletions

View File

@@ -31,6 +31,11 @@ _embed_failed = False # Sentinelle pour éviter les retries infinis
_reranker_model = None
_reranker_lock = threading.Lock()
# Cache d'embeddings : évite de recalculer les vecteurs pour les mêmes textes
_embedding_cache: dict[str, "numpy.ndarray"] = {}
_embedding_cache_lock = threading.Lock()
_EMBEDDING_CACHE_MAX = 5000
# Score minimum de similarité FAISS pour retenir un résultat
_MIN_SCORE = 0.3
# Seuil rehaussé pour le contexte CPAM (filtrage plus agressif du bruit)
@@ -132,6 +137,41 @@ def _rerank(query: str, results: list[dict], top_k: int) -> list[dict]:
return results[:top_k]
def _embed_cached(texts: list[str]) -> "numpy.ndarray":
"""Calcule les embeddings avec cache. Retourne un array (N, dim)."""
import numpy as np
model = _get_embed_model()
results = [None] * len(texts)
to_compute: list[tuple[int, str]] = []
with _embedding_cache_lock:
for i, t in enumerate(texts):
cached = _embedding_cache.get(t)
if cached is not None:
results[i] = cached
else:
to_compute.append((i, t))
if to_compute:
new_texts = [t for _, t in to_compute]
new_vecs = model.encode(new_texts, normalize_embeddings=True, batch_size=64)
new_vecs = np.array(new_vecs, dtype=np.float32)
with _embedding_cache_lock:
for j, (i, t) in enumerate(to_compute):
vec = new_vecs[j]
results[i] = vec
_embedding_cache[t] = vec
# Eviction simple si trop d'entrées
if len(_embedding_cache) > _EMBEDDING_CACHE_MAX:
keys = list(_embedding_cache.keys())
for k in keys[:len(keys) // 5]:
del _embedding_cache[k]
return np.array(results, dtype=np.float32)
def search_similar(query: str, top_k: int = 10) -> list[dict]:
"""Recherche les passages les plus similaires dans l'index FAISS.
@@ -154,9 +194,7 @@ def search_similar(query: str, top_k: int = 10) -> list[dict]:
faiss_index, metadata = result
model = _get_embed_model()
query_vec = model.encode([query], normalize_embeddings=True)
query_vec = np.array(query_vec, dtype=np.float32)
query_vec = _embed_cached([query])
# Chercher plus de résultats que top_k pour pouvoir filtrer ensuite
fetch_k = min(top_k * 2, faiss_index.ntotal)
@@ -218,9 +256,7 @@ def search_similar_ccam(query: str, top_k: int = 8) -> list[dict]:
faiss_index, metadata = result
model = _get_embed_model()
query_vec = model.encode([query], normalize_embeddings=True)
query_vec = np.array(query_vec, dtype=np.float32)
query_vec = _embed_cached([query])
fetch_k = min(top_k * 2, faiss_index.ntotal)
scores, indices = faiss_index.search(query_vec, fetch_k)
@@ -268,9 +304,7 @@ def search_similar_cpam(query: str, top_k: int = 8) -> list[dict]:
logger.warning("Index FAISS non disponible")
return []
model = _get_embed_model()
query_vec = model.encode([query], normalize_embeddings=True)
query_vec = np.array(query_vec, dtype=np.float32)
query_vec = _embed_cached([query])
def _search_one(result_tuple, fetch_mult: int) -> list[dict]:
if result_tuple is None: