Passe de 95/3/2 (lookups/raisonnement/règles) à ~31/49/20. Dataset cible ~16K exemples denses (vs 66K de lookups avant). Modifiés : - 03_convert_cache.py : cache complet 1840 entrées (actuel + backup) - 04_build_dataset.py : subsampling agressif (CIM-10 1.5K, CCAM 1.5K, CoCoA 2K) + sélection intelligente priorisant le raisonnement - 12_generate_pipeline_examples.py : 3 templates (court + long + CPAM), cache actuel, cible ~2800 exemples Créés : - 13_generate_fascicule_reasoning.py : parsing 10 fascicules ATIH, génération Q&A raisonnement via Claude Opus 4.6 (~450 exemples) - 14_generate_negative_examples.py : 1000 exemples négatifs (symptômes/DP, redondances sémantiques, DAS non significatifs) - 15_generate_discrimination.py : 800 exercices de discrimination entre codes siblings CIM-10 via Claude Opus 4.6 - 16_parse_guide_metho.py : extraction Guide Méthodologique MCO 2026, Q&A directes + raisonnement via Claude Opus 4.6 (~500 exemples) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
563 lines
20 KiB
Python
563 lines
20 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Phase 1D — Génération de données synthétiques via API OpenAI (GPT-4o).
|
|
|
|
Envoie des métadonnées anonymisées FAISS à un grand modèle pour générer
|
|
des exemples de raisonnement DIM complet en format ChatML.
|
|
|
|
Types d'exemples générés :
|
|
1. Scénario clinique → raisonnement DIM → code CIM-10
|
|
2. Discrimination entre codes proches
|
|
3. Application des règles PMSI (DP/DAS, CMA, exclusions)
|
|
|
|
Nécessite : OPENAI_API_KEY en variable d'environnement
|
|
|
|
Usage :
|
|
python scripts/06_generate_synthetic.py [--n 500] [--batch 5] [--model gpt-4o] [--dry-run]
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import sys
|
|
import time
|
|
import random
|
|
import argparse
|
|
from pathlib import Path
|
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
|
|
random.seed(42)
|
|
|
|
BASE = Path(__file__).resolve().parent.parent
|
|
T2A = Path("/home/dom/ai/t2a")
|
|
OUT = BASE / "data" / "processed"
|
|
OUT.mkdir(parents=True, exist_ok=True)
|
|
|
|
# --- Prompts ---
|
|
|
|
SYSTEM_PROMPT_SCENARIO = """Tu es un formateur DIM (Département d'Information Médicale) expert en codage PMSI.
|
|
Tu génères des scénarios cliniques réalistes et anonymisés pour former des médecins DIM au codage CIM-10.
|
|
|
|
Pour chaque code CIM-10 fourni, tu dois produire :
|
|
1. Un SCÉNARIO CLINIQUE réaliste (3-5 phrases, anonymisé, comme extrait d'un compte-rendu d'hospitalisation)
|
|
2. Un RAISONNEMENT DIM structuré montrant la démarche de codage
|
|
|
|
Le raisonnement doit suivre ces étapes :
|
|
- analyse_clinique : ce que le texte clinique révèle
|
|
- codes_candidats : 2-3 codes CIM-10 envisageables avec leur libellé
|
|
- discrimination : pourquoi le code retenu est le bon (et pas les autres)
|
|
- regle_pmsi : règle PMSI applicable (DP/DAS, exclusions, conventions dague/astérisque, etc.)
|
|
- code : le code CIM-10 retenu
|
|
- confidence : high/medium/low
|
|
- justification : synthèse en 1 phrase
|
|
|
|
IMPORTANT :
|
|
- Les scénarios doivent être VARIÉS (âges, sexes, contextes différents)
|
|
- Anonymisés (pas de vrais noms/dates)
|
|
- Médicalement cohérents
|
|
- En français médical professionnel
|
|
- Réponse en JSON valide uniquement"""
|
|
|
|
SYSTEM_PROMPT_DISCRIM = """Tu es un formateur DIM expert en codage PMSI.
|
|
Tu crées des exercices de discrimination entre codes CIM-10 proches pour former des médecins DIM.
|
|
|
|
Pour chaque groupe de codes fourni, génère UN scénario clinique où le choix entre les codes est subtil,
|
|
puis montre le raisonnement complet pour arriver au bon code.
|
|
|
|
IMPORTANT : Réponse en JSON valide uniquement."""
|
|
|
|
SYSTEM_PROMPT_RULES = """Tu es un formateur DIM expert en règles PMSI.
|
|
Tu crées des exercices d'application des règles PMSI (codage DP/DAS, CMA, séjours multi-unités, etc.).
|
|
|
|
Pour chaque situation fournie, génère un scénario d'hospitalisation et montre comment les règles PMSI
|
|
s'appliquent au codage.
|
|
|
|
IMPORTANT : Réponse en JSON valide uniquement."""
|
|
|
|
|
|
def load_faiss_metadata():
|
|
"""Charger les métadonnées FAISS."""
|
|
meta_path = T2A / "data" / "rag_index" / "metadata.json"
|
|
with open(meta_path) as f:
|
|
return json.load(f)
|
|
|
|
|
|
def load_cim10_fhir():
|
|
"""Charger les concepts FHIR pour enrichir les prompts."""
|
|
fhir_path = BASE / "data" / "raw" / "smt_cim10_fhir.json"
|
|
if not fhir_path.exists():
|
|
return {}
|
|
with open(fhir_path) as f:
|
|
data = json.load(f)
|
|
by_code = {}
|
|
for c in data.get("concept", []):
|
|
by_code[c["code"]] = c
|
|
return by_code
|
|
|
|
|
|
def load_cocoa_entries():
|
|
"""Charger les entrées CoCoA parsées."""
|
|
cocoa_path = OUT / "cocoa_entries_debug.json"
|
|
if not cocoa_path.exists():
|
|
return {}
|
|
with open(cocoa_path) as f:
|
|
return json.load(f)
|
|
|
|
|
|
def clean_extrait(extrait):
|
|
"""Nettoyer un extrait FAISS (enlever bruit OCR, numéros de page, etc.)."""
|
|
import re
|
|
# Enlever les numéros de page isolés (sur leur propre ligne ou collés)
|
|
extrait = re.sub(r'\n\s*\d{1,4}\s*\n', '\n', extrait)
|
|
extrait = re.sub(r'^\d{1,4}\s*\n', '', extrait)
|
|
# Enlever les transitions de chapitre
|
|
extrait = re.sub(r'Chapitre\s+[IVX]+\b.*', '', extrait)
|
|
# Enlever les lignes de classification
|
|
extrait = re.sub(r'Classification Internationale.*$', '', extrait, flags=re.MULTILINE)
|
|
# Enlever les lignes vides multiples
|
|
extrait = re.sub(r'\n{2,}', '\n', extrait)
|
|
# Tronquer au premier code d'une AUTRE catégorie
|
|
lines = extrait.split('\n')
|
|
first_code = None
|
|
clean_lines = []
|
|
for line in lines:
|
|
stripped = line.strip()
|
|
# Ligne ne contenant qu'un nombre = bruit
|
|
if re.match(r'^\d{1,4}$', stripped):
|
|
continue
|
|
m = re.match(r'^([A-Z]\d{2}(?:\.\d{1,2})?)[*†]?\s', stripped)
|
|
if m:
|
|
code = m.group(1)
|
|
if first_code is None:
|
|
first_code = code
|
|
elif not code.startswith(first_code[:3]):
|
|
break # On est passé à un autre groupe de codes
|
|
clean_lines.append(stripped)
|
|
result = '\n'.join(clean_lines).strip()
|
|
# Limiter la longueur
|
|
if len(result) > 400:
|
|
result = result[:400].rsplit('\n', 1)[0]
|
|
return result
|
|
|
|
|
|
def select_codes_for_scenarios(metadata, n=500):
|
|
"""Sélectionner les codes CIM-10 les plus intéressants pour la génération."""
|
|
# Filtrer les entrées CIM-10 avec des extraits substantiels
|
|
cim10_entries = [m for m in metadata if m.get("document") == "cim10" and len(m.get("extrait", "")) > 20]
|
|
|
|
# Prioriser les codes avec des extraits riches
|
|
cim10_entries.sort(key=lambda x: len(x.get("extrait", "")), reverse=True)
|
|
|
|
# Prendre les codes uniques
|
|
seen = set()
|
|
selected = []
|
|
for m in cim10_entries:
|
|
code = m["code"]
|
|
if code not in seen and "." in code: # Préférer les sous-codes (plus spécifiques)
|
|
seen.add(code)
|
|
selected.append(m)
|
|
if len(selected) >= n:
|
|
break
|
|
|
|
# Si pas assez de sous-codes, ajouter des catégories
|
|
if len(selected) < n:
|
|
for m in cim10_entries:
|
|
code = m["code"]
|
|
if code not in seen:
|
|
seen.add(code)
|
|
selected.append(m)
|
|
if len(selected) >= n:
|
|
break
|
|
|
|
random.shuffle(selected)
|
|
return selected[:n]
|
|
|
|
|
|
def select_discrimination_groups(metadata, cocoa_entries, n=100):
|
|
"""Sélectionner des groupes de codes proches pour la discrimination."""
|
|
# Grouper par catégorie parente (3 premiers caractères)
|
|
by_parent = {}
|
|
for m in metadata:
|
|
if m.get("document") != "cim10":
|
|
continue
|
|
code = m.get("code", "")
|
|
if "." in code:
|
|
parent = code.split(".")[0]
|
|
by_parent.setdefault(parent, []).append(m)
|
|
|
|
# Sélectionner les groupes avec 2-6 sous-codes
|
|
groups = []
|
|
for parent, children in by_parent.items():
|
|
if 2 <= len(children) <= 6:
|
|
groups.append({
|
|
"parent": parent,
|
|
"codes": [{"code": c["code"], "extrait": clean_extrait(c["extrait"])[:150]} for c in children]
|
|
})
|
|
|
|
random.shuffle(groups)
|
|
return groups[:n]
|
|
|
|
|
|
def build_scenario_prompt(codes_batch, fhir_by_code, cocoa_entries):
|
|
"""Construire le prompt pour un batch de codes (scénarios cliniques)."""
|
|
items = []
|
|
for meta in codes_batch:
|
|
code = meta["code"]
|
|
|
|
# Source primaire : FHIR (propre)
|
|
fhir = fhir_by_code.get(code, {})
|
|
display = fhir.get("display", "")
|
|
|
|
# Source secondaire : CoCoA (riche)
|
|
cocoa = cocoa_entries.get(code, {})
|
|
cocoa_desc = cocoa.get("description", "")
|
|
exclusions = cocoa.get("exclusions", [])[:4]
|
|
synonyms = cocoa.get("synonyms", [])[:5]
|
|
comprend = cocoa.get("comprend", [])[:3]
|
|
severity = cocoa.get("severity")
|
|
clinical = " ".join(cocoa.get("clinical_text", []))[:200]
|
|
|
|
# Utiliser la meilleure description disponible
|
|
desc = display or cocoa_desc or clean_extrait(meta["extrait"])[:200]
|
|
|
|
item = f"CODE: {code}\nLIBELLÉ: {desc}"
|
|
if synonyms:
|
|
item += f"\nSYNONYMES: {'; '.join(synonyms)}"
|
|
if comprend:
|
|
item += f"\nCOMPREND: {'; '.join(comprend)}"
|
|
if exclusions:
|
|
item += f"\nEXCLUSIONS: {'; '.join(exclusions)}"
|
|
if severity:
|
|
item += f"\nSÉVÉRITÉ CMA: {severity}"
|
|
if clinical:
|
|
item += f"\nDESCRIPTION CLINIQUE: {clinical}"
|
|
items.append(item)
|
|
|
|
codes_text = "\n---\n".join(items)
|
|
|
|
prompt = f"""Génère un scénario clinique et un raisonnement DIM pour chacun des {len(codes_batch)} codes suivants.
|
|
|
|
{codes_text}
|
|
|
|
Réponds en JSON avec cette structure exacte :
|
|
{{"scenarios": [
|
|
{{
|
|
"code": "le code CIM-10",
|
|
"scenario_clinique": "Texte du scénario clinique réaliste (3-5 phrases)",
|
|
"raisonnement": {{
|
|
"analyse_clinique": "Analyse des éléments cliniques pertinents",
|
|
"codes_candidats": "2-3 codes envisagés avec libellés",
|
|
"discrimination": "Pourquoi ce code et pas les autres",
|
|
"regle_pmsi": "Règle PMSI applicable",
|
|
"code_retenu": "le code",
|
|
"confidence": "high",
|
|
"justification": "Synthèse en 1 phrase"
|
|
}}
|
|
}},
|
|
...
|
|
]}}
|
|
|
|
Le tableau "scenarios" DOIT contenir exactement {len(codes_batch)} objets, un par code."""
|
|
|
|
return prompt
|
|
|
|
|
|
def build_discrimination_prompt(group, fhir_by_code):
|
|
"""Construire le prompt pour un exercice de discrimination."""
|
|
codes_text = "\n".join(
|
|
f"- {c['code']}: {c['extrait']}"
|
|
for c in group["codes"]
|
|
)
|
|
|
|
prompt = f"""Voici un groupe de codes CIM-10 de la catégorie {group['parent']} :
|
|
|
|
{codes_text}
|
|
|
|
Génère UN scénario clinique réaliste où le choix entre ces codes est subtil et demande une réflexion.
|
|
Puis montre le raisonnement DIM complet pour arriver au bon code.
|
|
|
|
Réponds en JSON :
|
|
{{
|
|
"scenario_clinique": "Le scénario (5-8 phrases, réaliste, anonymisé)",
|
|
"codes_en_jeu": ["code1", "code2"],
|
|
"raisonnement": {{
|
|
"analyse_clinique": "...",
|
|
"codes_candidats": "...",
|
|
"discrimination": "Explication détaillée de pourquoi un code est préféré",
|
|
"regle_pmsi": "Règle PMSI applicable",
|
|
"code_retenu": "le code correct",
|
|
"confidence": "high/medium",
|
|
"justification": "..."
|
|
}}
|
|
}}
|
|
|
|
JSON valide uniquement."""
|
|
|
|
return prompt
|
|
|
|
|
|
def call_openai(client, model, system_prompt, user_prompt, temperature=0.7):
|
|
"""Appeler l'API OpenAI."""
|
|
response = client.chat.completions.create(
|
|
model=model,
|
|
messages=[
|
|
{"role": "system", "content": system_prompt},
|
|
{"role": "user", "content": user_prompt},
|
|
],
|
|
temperature=temperature,
|
|
max_tokens=4096,
|
|
response_format={"type": "json_object"},
|
|
)
|
|
content = response.choices[0].message.content
|
|
return json.loads(content)
|
|
|
|
|
|
def _extract_items(result):
|
|
"""Extraire la liste d'items depuis la réponse JSON (gère différents formats)."""
|
|
if isinstance(result, list):
|
|
return result
|
|
if isinstance(result, dict):
|
|
# Chercher récursivement un tableau d'items
|
|
for v in result.values():
|
|
if isinstance(v, list) and v and isinstance(v[0], dict):
|
|
return v
|
|
# Si pas de tableau, c'est peut-être un seul item
|
|
if "scenario_clinique" in result or "raisonnement" in result:
|
|
return [result]
|
|
# Dernier recours : aplatir les valeurs dict
|
|
items = []
|
|
for v in result.values():
|
|
if isinstance(v, dict) and ("scenario_clinique" in v or "raisonnement" in v):
|
|
items.append(v)
|
|
if items:
|
|
return items
|
|
return []
|
|
|
|
|
|
def convert_to_chatml(scenario_data):
|
|
"""Convertir un résultat de génération en format ChatML."""
|
|
if not isinstance(scenario_data, dict):
|
|
return None
|
|
|
|
system_msg = "Tu es un médecin DIM expert en codage CIM-10 pour le PMSI français. Tu codes les diagnostics en suivant une démarche structurée."
|
|
|
|
scenario = scenario_data.get("scenario_clinique", "")
|
|
raisonnement = scenario_data.get("raisonnement", {})
|
|
|
|
# Si pas de scenario_clinique, chercher dans d'autres clés possibles
|
|
if not scenario:
|
|
scenario = scenario_data.get("scenario", scenario_data.get("texte_clinique", ""))
|
|
|
|
# Si le raisonnement est directement dans le dict (pas imbriqué)
|
|
if not raisonnement and "analyse_clinique" in scenario_data:
|
|
raisonnement = {k: v for k, v in scenario_data.items() if k != "scenario_clinique"}
|
|
|
|
if not scenario or not raisonnement:
|
|
return None
|
|
|
|
user_msg = f"Code ce diagnostic en CIM-10.\n\nTEXTE CLINIQUE : {scenario}"
|
|
assistant_msg = json.dumps(raisonnement, ensure_ascii=False)
|
|
|
|
return {
|
|
"messages": [
|
|
{"role": "system", "content": system_msg},
|
|
{"role": "user", "content": user_msg},
|
|
{"role": "assistant", "content": assistant_msg},
|
|
]
|
|
}
|
|
|
|
|
|
def process_scenario_batch(client, model, batch, fhir_by_code, cocoa_entries, batch_idx):
|
|
"""Traiter un batch de codes pour générer des scénarios."""
|
|
prompt = build_scenario_prompt(batch, fhir_by_code, cocoa_entries)
|
|
|
|
try:
|
|
result = call_openai(client, model, SYSTEM_PROMPT_SCENARIO, prompt)
|
|
|
|
# Le résultat peut être un tableau ou un dict avec une clé contenant le tableau
|
|
items = _extract_items(result)
|
|
|
|
examples = []
|
|
for item in items:
|
|
chatml = convert_to_chatml(item)
|
|
if chatml:
|
|
examples.append(chatml)
|
|
else:
|
|
print(f" [Batch {batch_idx}] Item non converti: {list(item.keys()) if isinstance(item, dict) else type(item)}")
|
|
|
|
if len(examples) < len(batch):
|
|
print(f" [Batch {batch_idx}] {len(examples)}/{len(batch)} exemples récupérés")
|
|
|
|
return examples
|
|
|
|
except Exception as e:
|
|
print(f" [Batch {batch_idx}] Erreur: {e}")
|
|
return []
|
|
|
|
|
|
def process_discrimination_batch(client, model, group, fhir_by_code, batch_idx):
|
|
"""Traiter un groupe pour générer un exercice de discrimination."""
|
|
prompt = build_discrimination_prompt(group, fhir_by_code)
|
|
|
|
try:
|
|
result = call_openai(client, model, SYSTEM_PROMPT_DISCRIM, prompt)
|
|
chatml = convert_to_chatml(result)
|
|
return [chatml] if chatml else []
|
|
|
|
except Exception as e:
|
|
print(f" [Discrim {batch_idx}] Erreur: {e}")
|
|
return []
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="Génération de données synthétiques via OpenAI")
|
|
parser.add_argument("--n", type=int, default=500, help="Nombre de scénarios à générer")
|
|
parser.add_argument("--n-discrim", type=int, default=100, help="Nombre d'exercices de discrimination")
|
|
parser.add_argument("--batch", type=int, default=5, help="Codes par batch (scénarios)")
|
|
parser.add_argument("--model", default="gpt-4o", help="Modèle OpenAI")
|
|
parser.add_argument("--workers", type=int, default=3, help="Workers parallèles")
|
|
parser.add_argument("--dry-run", action="store_true", help="Afficher les prompts sans appeler l'API")
|
|
parser.add_argument("--resume", action="store_true", help="Reprendre depuis la dernière exécution")
|
|
args = parser.parse_args()
|
|
|
|
# Vérifier la clé API
|
|
api_key = os.environ.get("OPENAI_API_KEY")
|
|
if not api_key and not args.dry_run:
|
|
print("Erreur: OPENAI_API_KEY non définie.")
|
|
print(" export OPENAI_API_KEY='sk-...'")
|
|
sys.exit(1)
|
|
|
|
# Charger les données
|
|
print("Chargement des données...")
|
|
metadata = load_faiss_metadata()
|
|
fhir_by_code = load_cim10_fhir()
|
|
cocoa_entries = load_cocoa_entries()
|
|
print(f" FAISS: {len(metadata)} entrées")
|
|
print(f" FHIR: {len(fhir_by_code)} concepts")
|
|
print(f" CoCoA: {len(cocoa_entries)} entrées")
|
|
|
|
# Sélectionner les codes
|
|
print(f"\nSélection de {args.n} codes pour les scénarios...")
|
|
selected_codes = select_codes_for_scenarios(metadata, n=args.n)
|
|
print(f" {len(selected_codes)} codes sélectionnés")
|
|
|
|
print(f"Sélection de {args.n_discrim} groupes pour la discrimination...")
|
|
discrim_groups = select_discrimination_groups(metadata, cocoa_entries, n=args.n_discrim)
|
|
print(f" {len(discrim_groups)} groupes sélectionnés")
|
|
|
|
# Découper en batches
|
|
scenario_batches = [
|
|
selected_codes[i:i + args.batch]
|
|
for i in range(0, len(selected_codes), args.batch)
|
|
]
|
|
print(f"\n{len(scenario_batches)} batches de scénarios ({args.batch} codes/batch)")
|
|
print(f"{len(discrim_groups)} exercices de discrimination")
|
|
|
|
# Fichier de sortie (avec reprise possible)
|
|
output_path = OUT / "synthetic_chatml.jsonl"
|
|
existing_count = 0
|
|
if args.resume and output_path.exists():
|
|
with open(output_path) as f:
|
|
existing_count = sum(1 for _ in f)
|
|
print(f"\nReprise: {existing_count} exemples existants")
|
|
|
|
if args.dry_run:
|
|
# Mode dry-run : montrer des exemples de prompts
|
|
print("\n=== DRY RUN ===")
|
|
print("\n--- Exemple de prompt scénario ---")
|
|
prompt = build_scenario_prompt(scenario_batches[0], fhir_by_code, cocoa_entries)
|
|
print(prompt[:2000])
|
|
print("\n--- Exemple de prompt discrimination ---")
|
|
if discrim_groups:
|
|
prompt = build_discrimination_prompt(discrim_groups[0], fhir_by_code)
|
|
print(prompt[:1500])
|
|
return
|
|
|
|
# Initialiser le client OpenAI
|
|
from openai import OpenAI
|
|
client = OpenAI(api_key=api_key)
|
|
|
|
all_examples = []
|
|
total_batches = len(scenario_batches) + len(discrim_groups)
|
|
completed = 0
|
|
errors = 0
|
|
|
|
# Ouvrir le fichier en mode append
|
|
mode = "a" if args.resume else "w"
|
|
with open(output_path, mode) as fh:
|
|
|
|
# Phase 1 : Scénarios cliniques
|
|
print(f"\n{'='*50}")
|
|
print(f"Phase 1 : Génération des scénarios cliniques...")
|
|
print(f"{'='*50}")
|
|
|
|
with ThreadPoolExecutor(max_workers=args.workers) as executor:
|
|
futures = {}
|
|
for i, batch in enumerate(scenario_batches):
|
|
future = executor.submit(
|
|
process_scenario_batch, client, args.model,
|
|
batch, fhir_by_code, cocoa_entries, i
|
|
)
|
|
futures[future] = i
|
|
|
|
for future in as_completed(futures):
|
|
batch_idx = futures[future]
|
|
try:
|
|
examples = future.result()
|
|
for ex in examples:
|
|
fh.write(json.dumps(ex, ensure_ascii=False) + "\n")
|
|
all_examples.append(ex)
|
|
completed += 1
|
|
if completed % 10 == 0:
|
|
print(f" [{completed}/{len(scenario_batches)}] {len(all_examples)} exemples générés...")
|
|
except Exception as e:
|
|
errors += 1
|
|
print(f" [Batch {batch_idx}] Exception: {e}")
|
|
|
|
print(f" Scénarios: {len(all_examples)} exemples")
|
|
|
|
# Phase 2 : Discrimination
|
|
print(f"\n{'='*50}")
|
|
print(f"Phase 2 : Génération des exercices de discrimination...")
|
|
print(f"{'='*50}")
|
|
|
|
discrim_count = 0
|
|
with ThreadPoolExecutor(max_workers=args.workers) as executor:
|
|
futures = {}
|
|
for i, group in enumerate(discrim_groups):
|
|
future = executor.submit(
|
|
process_discrimination_batch, client, args.model,
|
|
group, fhir_by_code, i
|
|
)
|
|
futures[future] = i
|
|
|
|
for future in as_completed(futures):
|
|
batch_idx = futures[future]
|
|
try:
|
|
examples = future.result()
|
|
for ex in examples:
|
|
fh.write(json.dumps(ex, ensure_ascii=False) + "\n")
|
|
all_examples.append(ex)
|
|
discrim_count += 1
|
|
except Exception as e:
|
|
errors += 1
|
|
print(f" [Discrim {batch_idx}] Exception: {e}")
|
|
|
|
print(f" Discrimination: {discrim_count} exemples")
|
|
|
|
# Stats finales
|
|
total = len(all_examples) + existing_count
|
|
print(f"\n{'='*50}")
|
|
print(f"Génération terminée !")
|
|
print(f" Nouveaux exemples : {len(all_examples)}")
|
|
if existing_count:
|
|
print(f" Existants (reprise): {existing_count}")
|
|
print(f" Total : {total}")
|
|
print(f" Erreurs : {errors}")
|
|
print(f" Fichier : {output_path}")
|
|
if output_path.exists():
|
|
print(f" Taille : {output_path.stat().st_size / 1024:.0f} Ko")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|