feat: pass LLM hybride pour DAS + interface admin référentiels RAG

Chantier 1 — Extraction DAS par LLM :
- Nouveau prompt expert DIM dans rag_search.py (extract_das_llm)
- Phase 4 dans cim10_extractor.py : détection DAS supplémentaires avant enrichissement RAG
- Cache persistant (clé hash du texte), validation CIM-10, déduplication
- Activé uniquement avec use_rag=True (--no-rag le désactive)

Chantier 2 — Admin référentiels :
- Config : REFERENTIELS_DIR, UPLOAD_MAX_SIZE_MB, ALLOWED_EXTENSIONS
- Chunking générique (PDF/CSV/Excel/TXT) + ajout incrémental FAISS dans rag_index.py
- ReferentielManager CRUD dans viewer/referentiels.py
- 5 routes Flask (listing, upload, indexation, suppression, rebuild)
- Template admin avec tableau interactif + lien sidebar

Fix : if cache → if cache is not None (OllamaCache vide évaluait à False)

410 tests passent (27 nouveaux, 0 régression).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
dom
2026-02-12 23:12:39 +01:00
parent bf92a0ce3e
commit f44216b95b
10 changed files with 1197 additions and 6 deletions

View File

@@ -11,8 +11,11 @@ import requests
from flask import Flask, abort, render_template, request, jsonify
from markupsafe import Markup
from ..config import STRUCTURED_DIR, OLLAMA_URL, CCAM_DICT_PATH, DossierMedical
from werkzeug.utils import secure_filename
from ..config import STRUCTURED_DIR, OLLAMA_URL, CCAM_DICT_PATH, DossierMedical, ALLOWED_EXTENSIONS, UPLOAD_MAX_SIZE_MB
from .. import config as cfg
from .referentiels import ReferentielManager
logger = logging.getLogger(__name__)
@@ -271,12 +274,12 @@ def create_app() -> Flask:
def reprocess(filepath: str):
"""Relance le traitement d'un dossier."""
from ..main import process_pdf, write_outputs
dossier = load_dossier(filepath)
source_file = dossier.source_file
if not source_file:
return jsonify({"error": "Fichier source introuvable"}), 400
# Chercher le PDF source dans input/
input_dir = Path(__file__).parent.parent.parent / "input"
pdf_path = None
@@ -284,10 +287,10 @@ def create_app() -> Flask:
if p.is_file():
pdf_path = p
break
if not pdf_path:
return jsonify({"error": f"PDF source '{source_file}' introuvable"}), 404
try:
anonymized_text, new_dossier, report = process_pdf(pdf_path)
stem = pdf_path.stem.replace(" ", "_")
@@ -300,4 +303,64 @@ def create_app() -> Flask:
logger.exception("Erreur lors du retraitement")
return jsonify({"error": str(e)}), 500
# ------------------------------------------------------------------
# Routes admin référentiels
# ------------------------------------------------------------------
ref_manager = ReferentielManager()
@app.route("/admin/referentiels")
def admin_referentiels():
refs = ref_manager.list_all()
return render_template("admin_referentiels.html", referentiels=refs, max_size=UPLOAD_MAX_SIZE_MB)
@app.route("/admin/referentiels/upload", methods=["POST"])
def upload_referentiel():
if "file" not in request.files:
return jsonify({"error": "Aucun fichier envoyé"}), 400
f = request.files["file"]
if not f.filename:
return jsonify({"error": "Nom de fichier vide"}), 400
filename = secure_filename(f.filename)
try:
file_data = f.read()
ref = ref_manager.add_file(filename, file_data)
return jsonify({"ok": True, "referentiel": ref})
except ValueError as e:
return jsonify({"error": str(e)}), 400
@app.route("/admin/referentiels/<ref_id>/index", methods=["POST"])
def index_referentiel(ref_id: str):
try:
count = ref_manager.index_referentiel(ref_id)
return jsonify({"ok": True, "chunks": count})
except ValueError as e:
return jsonify({"error": str(e)}), 404
except Exception as e:
logger.exception("Erreur lors de l'indexation du référentiel %s", ref_id)
return jsonify({"error": str(e)}), 500
@app.route("/admin/referentiels/<ref_id>", methods=["DELETE"])
def delete_referentiel(ref_id: str):
if ref_manager.remove(ref_id):
return jsonify({"ok": True})
return jsonify({"error": "Référentiel introuvable"}), 404
@app.route("/admin/referentiels/rebuild-index", methods=["POST"])
def rebuild_index():
try:
from ..medical.rag_index import build_index
build_index(force=True)
# Réindexer tous les référentiels actifs
reindexed = 0
for ref in ref_manager.list_all():
if ref["status"] == "indexed":
ref_manager.index_referentiel(ref["id"])
reindexed += 1
return jsonify({"ok": True, "reindexed": reindexed})
except Exception as e:
logger.exception("Erreur lors du rebuild de l'index")
return jsonify({"error": str(e)}), 500
return app