601 lines
22 KiB
Python
601 lines
22 KiB
Python
|
||
# %% API call1
|
||
#import time
|
||
#import json
|
||
#import os
|
||
#from datetime import datetime
|
||
#import pandas as pd
|
||
#from openai import OpenAI
|
||
#from dotenv import load_dotenv
|
||
#
|
||
## Load environment variables
|
||
#load_dotenv()
|
||
#
|
||
## === CONFIGURATION ===
|
||
#OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
|
||
#OPENAI_BASE_URL = os.getenv("OPENAI_BASE_URL")
|
||
#MODEL_NAME = "GPT-OSS-120B"
|
||
#HEALTH_URL = f"{OPENAI_BASE_URL}/health" # Placeholder - actual health check would need to be implemented
|
||
#CHAT_URL = f"{OPENAI_BASE_URL}/chat/completions"
|
||
#
|
||
## File paths
|
||
#INPUT_CSV = "/home/shahin/Lab/Doktorarbeit/Barcelona/Data/MS_Briefe_400_with_unique_id_SHA3_explore_cleaned_unique.csv"
|
||
#EDSS_INSTRUCTIONS_PATH = "/home/shahin/Lab/Doktorarbeit/Barcelona/attach/Komplett.txt"
|
||
##GRAMMAR_FILE = "/home/shahin/Lab/Doktorarbeit/Barcelona/attach/just_edss_schema.gbnf"
|
||
#
|
||
## Initialize OpenAI client
|
||
#client = OpenAI(
|
||
# api_key=OPENAI_API_KEY,
|
||
# base_url=OPENAI_BASE_URL
|
||
#)
|
||
#
|
||
## Read EDSS instructions from file
|
||
#with open(EDSS_INSTRUCTIONS_PATH, 'r') as f:
|
||
# EDSS_INSTRUCTIONS = f.read().strip()
|
||
## === RUN INFERENCE 2 ===
|
||
#def run_inference(patient_text):
|
||
# prompt = f'''
|
||
# Du bist ein medizinischer Assistent, der spezialisiert darauf ist, EDSS-Scores (Expanded Disability Status Scale) aus klinischen Berichten zu extrahieren.
|
||
#### Regeln für die Ausgabe:
|
||
#1. **Reason**: Erstelle eine prägnante Zusammenfassung (max. 400 Zeichen) der Befunde auf **DEUTSCH**, die zur Einstufung führen.
|
||
#2. **klassifizierbar**:
|
||
# - Setze dies auf **true**, wenn ein EDSS-Wert identifiziert, berechnet oder basierend auf den klinischen Hinweisen plausibel geschätzt werden kann.
|
||
# - Setze dies auf **false**, NUR wenn die Daten absolut unzureichend oder so widersprüchlich sind, dass keinerlei Einstufung möglich ist.
|
||
#3. **EDSS**:
|
||
# - Dieses Feld ist **VERPFLICHTEND**, wenn "klassifizierbar" auf true steht.
|
||
# - Es muss eine Zahl zwischen 0.0 und 10.0 sein.
|
||
# - Versuche stets, den EDSS-Wert so präzise wie möglich zu bestimmen, auch wenn die Datenlage dünn ist (nutze verfügbare Informationen zu Gehstrecke und Funktionssystemen).
|
||
# - Dieses Feld **DARF NICHT ERSCHEINEN**, wenn "klassifizierbar" auf false steht.
|
||
#
|
||
#### Einschränkungen:
|
||
#- Erfinde keine Fakten, aber nutze klinische Herleitungen aus dem Bericht, um den EDSS zu bestimmen.
|
||
#- Priorisiere die Vergabe eines EDSS-Wertes gegenüber der Markierung als nicht klassifizierbar.
|
||
#- Halte dich strikt an die JSON-Struktur.
|
||
#
|
||
#EDSS-Bewertungsrichtlinien:
|
||
#{EDSS_INSTRUCTIONS}
|
||
#
|
||
#Patientenbericht:
|
||
#{patient_text}
|
||
#'''
|
||
# start_time = time.time()
|
||
#
|
||
# try:
|
||
# # Make API call using OpenAI client
|
||
# response = client.chat.completions.create(
|
||
# messages=[
|
||
# {
|
||
# "role": "system",
|
||
# "content": "You extract EDSS scores. You prioritize providing a score even if data is partial, by using clinical inference."
|
||
# },
|
||
# {
|
||
# "role": "user",
|
||
# "content": prompt
|
||
# }
|
||
# ],
|
||
# model=MODEL_NAME,
|
||
# max_tokens=2048,
|
||
# temperature=0.0,
|
||
# response_format={"type": "json_object"}
|
||
# )
|
||
#
|
||
# # Extract content from response
|
||
# content = response.choices[0].message.content
|
||
#
|
||
# # Parse the JSON response
|
||
# parsed = json.loads(content)
|
||
#
|
||
# inference_time = time.time() - start_time
|
||
#
|
||
# return {
|
||
# "success": True,
|
||
# "result": parsed,
|
||
# "inference_time_sec": inference_time
|
||
# }
|
||
#
|
||
# except Exception as e:
|
||
# print(f"Inference error: {e}")
|
||
# return {
|
||
# "success": False,
|
||
# "error": str(e),
|
||
# "inference_time_sec": -1
|
||
# }
|
||
## === BUILD PATIENT TEXT ===
|
||
#def build_patient_text(row):
|
||
# return (
|
||
# str(row["T_Zusammenfassung"]) + "\n" +
|
||
# str(row["Diagnosen"]) + "\n" +
|
||
# str(row["T_KlinBef"]) + "\n" +
|
||
# str(row["T_Befunde"]) + "\n"
|
||
# )
|
||
#
|
||
#if __name__ == "__main__":
|
||
# # Read CSV file ONLY inside main block
|
||
# df = pd.read_csv(INPUT_CSV, sep=';')
|
||
# results = []
|
||
#
|
||
# # Process each row
|
||
# for idx, row in df.iterrows():
|
||
# print(f"Processing row {idx + 1}/{len(df)}")
|
||
# try:
|
||
# patient_text = build_patient_text(row)
|
||
# result = run_inference(patient_text)
|
||
#
|
||
# # Add unique_id and MedDatum to result for tracking
|
||
# result["unique_id"] = row.get("unique_id", f"row_{idx}")
|
||
# result["MedDatum"] = row.get("MedDatum", None)
|
||
#
|
||
# results.append(result)
|
||
# print(json.dumps(result, indent=2))
|
||
# except Exception as e:
|
||
# print(f"Error processing row {idx}: {e}")
|
||
# results.append({
|
||
# "success": False,
|
||
# "error": str(e),
|
||
# "unique_id": row.get("unique_id", f"row_{idx}"),
|
||
# "MedDatum": row.get("MedDatum", None)
|
||
# })
|
||
#
|
||
# # Save results to a JSON file
|
||
# output_json = INPUT_CSV.replace(".csv", "_results_Nisch.json")
|
||
# with open(output_json, 'w') as f:
|
||
# json.dump(results, f, indent=2)
|
||
# print(f"Results saved to {output_json}")
|
||
##
|
||
|
||
|
||
|
||
# %% API call1 - Enhanced with certainty scoring
|
||
#import time
|
||
#import json
|
||
#import os
|
||
#from datetime import datetime
|
||
#import pandas as pd
|
||
#from openai import OpenAI
|
||
#from dotenv import load_dotenv
|
||
#
|
||
## Load environment variables
|
||
#load_dotenv()
|
||
#
|
||
## === CONFIGURATION ===
|
||
#OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
|
||
#OPENAI_BASE_URL = os.getenv("OPENAI_BASE_URL")
|
||
#MODEL_NAME = "GPT-OSS-120B"
|
||
#
|
||
## File paths
|
||
#INPUT_CSV = "/home/shahin/Lab/Doktorarbeit/Barcelona/Data/Test.csv"
|
||
#EDSS_INSTRUCTIONS_PATH = "/home/shahin/Lab/Doktorarbeit/Barcelona/attach/Komplett.txt"
|
||
#
|
||
## Initialize OpenAI client
|
||
#client = OpenAI(
|
||
# api_key=OPENAI_API_KEY,
|
||
# base_url=OPENAI_BASE_URL
|
||
#)
|
||
#
|
||
## Read EDSS instructions from file
|
||
#with open(EDSS_INSTRUCTIONS_PATH, 'r') as f:
|
||
# EDSS_INSTRUCTIONS = f.read().strip()
|
||
#
|
||
## === PROMPT WITH CERTAINTY REQUEST ===
|
||
#def build_prompt(patient_text):
|
||
# return f'''Du bist ein medizinischer Assistent, der spezialisiert darauf ist, EDSS-Scores (Expanded Disability Status Scale), alle Unterkategorien und die Bewertungssicherheit aus klinischen Berichten zu extrahieren.
|
||
#
|
||
#### Deine Aufgabe:
|
||
#1. Analysiere den Patientenbericht und extrahiere:
|
||
# - Den Gesamt-EDSS-Score (0.0–10.0)
|
||
# - Alle 8 EDSS-Unterkategorien (mit jeweils eigener Maximalpunktzahl)
|
||
#2. Schätze für jede Entscheidung die Sicherheit als Ganzzahl von 0–100 % ein.
|
||
#
|
||
#### Struktur der JSON-Ausgabe (VERPFLICHTEND):
|
||
#Gib NUR gültiges JSON zurück — kein Markdown, kein Text davor/dahinter.
|
||
#
|
||
#{{
|
||
# "reason": "Kernaussage zur EDSS-Begründung (max. 400 Zeichen, auf Deutsch).",
|
||
# "klassifizierbar": true/false,
|
||
# "EDSS": null ODER Zahl zwischen 0.0 und 10.0 (nur wenn klassifizierbar=true)",
|
||
# "certainty_percent": 0 ODER Zahl zwischen 0 und 100 (Ganzzahl)",
|
||
# "subcategories": {{
|
||
# "VISUAL_OPTIC_FUNCTIONS": null ODER Zahl zwischen 0.0 und 6.0,
|
||
# "BRAINSTEM_FUNCTIONS": null ODER Zahl zwischen 0.0 und 6.0,
|
||
# "PYRAMIDAL_FUNCTIONS": null ODER Zahl zwischen 0.0 und 6.0,
|
||
# "CEREBELLAR_FUNCTIONS": null ODER Zahl zwischen 0.0 und 6.0,
|
||
# "SENSORY_FUNCTIONS": null ODER Zahl zwischen 0.0 und 6.0,
|
||
# "BOWEL_AND_BLADDER_FUNCTIONS": null ODER Zahl zwischen 0.0 und 6.0,
|
||
# "CEREBRAL_FUNCTIONS": null ODER Zahl zwischen 0.0 und 6.0,
|
||
# "AMBULATION": null ODER Zahl zwischen 0.0 und 10.0
|
||
# }}
|
||
#}}
|
||
#
|
||
#### Regeln:
|
||
#- **reason**: Kurze, prägnante Begründung (auf Deutsch, max. 400 Zeichen), warum du den EDSS-Wert und die Unterkategorien so bewertest.
|
||
#- **klassifizierbar**:
|
||
# - `true`, wenn EDSS und mindestens die wichtigsten Unterkategorien *eindeutig ableitbar* oder *plausibel inferierbar* sind.
|
||
# - `false`, **nur**, wenn keine relevanten Daten vorliegen, oder diese so widersprüchlich/inkonsistent sind, dass keine vernünftige Einschätzung möglich ist.
|
||
#- **EDSS**:
|
||
# - **VERPFLICHTEND**, wenn `klassifizierbar=true`.
|
||
# - Zahl zwischen 0.0 und 10.0 (z.B. 3.0, 5.5). Darf **nicht** erscheinen, wenn `klassifizierbar=false`.
|
||
#- **certainty_percent**:
|
||
# - **Immer present** — Ganzzahl (0–100), basierend auf:
|
||
# - Klarheit und Vollständigkeit der Berichtsangaben,
|
||
# - Stichhaltigkeit der Schlussfolgerung (inkl. Inferenz),
|
||
# - Konsistenz zwischen den Unterkategorien.
|
||
#- **subcategories**:
|
||
# - **Immer present** — **alle 8 Unterkategorien** müssen enthalten sein.
|
||
# - Jeder Wert ist entweder:
|
||
# - `null` (wenn keine ausreichende Information vorliegt), **oder**
|
||
# - eine Zahl ≤ jeweiliger Obergrenze (z.B. Ambulation ≤ 10.0).
|
||
# - Wenn die Unterkategorie plausibel inferiert werden kann (auch indirekt), gib einen sinnvollen Wert ab.
|
||
# - Beispiel: Wenn „Gang mit Krückstock auf ebenem Boden bis 200 m“ steht, setze `AMBULATION: 5.5`.
|
||
#
|
||
#### EDSS-Bewertungsrichtlinien:
|
||
#{EDSS_INSTRUCTIONS}
|
||
#
|
||
#Patientenbericht:
|
||
#{patient_text}
|
||
#'''
|
||
#
|
||
## === INFERENCE FUNCTION ===
|
||
#def run_inference(patient_text):
|
||
# prompt = build_prompt(patient_text)
|
||
#
|
||
# start_time = time.time()
|
||
#
|
||
# try:
|
||
# response = client.chat.completions.create(
|
||
# messages=[
|
||
# {"role": "system", "content": "Du gibst EXKLUSIV gültiges JSON zurück — keine weiteren Erklärungen."}
|
||
# ] + [
|
||
# {"role": "user", "content": prompt}
|
||
# ],
|
||
# model=MODEL_NAME,
|
||
# max_tokens=2048,
|
||
# temperature=0.1, # Slightly higher for more natural certainty estimation (still low for reliability)
|
||
# response_format={"type": "json_object"}
|
||
# )
|
||
#
|
||
# content = response.choices[0].message.content
|
||
#
|
||
# # Parse and validate JSON
|
||
# try:
|
||
# parsed = json.loads(content)
|
||
# except json.JSONDecodeError as e:
|
||
# print(f"⚠️ JSON parsing failed: {e}")
|
||
# print("Raw response:", content[:500])
|
||
# raise ValueError("Model did not return valid JSON")
|
||
#
|
||
# # Enforce required keys
|
||
# if "certainty_percent" not in parsed:
|
||
# print("⚠️ Missing 'certainty_percent' in output! Force-adding fallback.")
|
||
# parsed["certainty_percent"] = 0 # fallback
|
||
# elif not isinstance(parsed["certainty_percent"], (int, float)):
|
||
# parsed["certainty_percent"] = int(parsed["certainty_percent"])
|
||
#
|
||
# # Clamp certainty to [0, 100]
|
||
# pct = parsed["certainty_percent"]
|
||
# parsed["certainty_percent"] =max(0, min(100, int(pct)))
|
||
#
|
||
# # Enforce EDSS rules: if not classifiable → remove EDSS
|
||
# if not parsed.get("klassifizierbar", False):
|
||
# if "EDSS" in parsed:
|
||
# del parsed["EDSS"] # per spec, must not appear if not classifiable
|
||
# else:
|
||
# if "EDSS" not in parsed:
|
||
# print("⚠️ 'klassifizierbar' is true but EDSS missing — adding fallback.")
|
||
# parsed["EDSS"] = 7.0 # last-resort fallback
|
||
#
|
||
# inference_time = time.time() - start_time
|
||
#
|
||
# return {
|
||
# "success": True,
|
||
# "result": parsed,
|
||
# "inference_time_sec": inference_time
|
||
# }
|
||
#
|
||
# except Exception as e:
|
||
# print(f"❌ Inference error: {e}")
|
||
# return {
|
||
# "success": False,
|
||
# "error": str(e),
|
||
# "inference_time_sec": -1,
|
||
# "result": None # no structured output
|
||
# }
|
||
#
|
||
## === BUILD PATIENT TEXT ===
|
||
#def build_patient_text(row):
|
||
# return (
|
||
# str(row.get("T_Zusammenfassung", "")) + "\n" +
|
||
# str(row.get("Diagnosen", "")) + "\n" +
|
||
# str(row.get("T_KlinBef", "")) + "\n" +
|
||
# str(row.get("T_Befunde", ""))
|
||
# )
|
||
#
|
||
#if __name__ == "__main__":
|
||
# # Load data
|
||
# df = pd.read_csv(INPUT_CSV, sep=';')
|
||
# results = []
|
||
#
|
||
# # Optional: limit for testing
|
||
# # df = df.head(3)
|
||
#
|
||
# print(f"Processing {len(df)} rows...")
|
||
# for idx, row in df.iterrows():
|
||
# print(f"\n— Row {idx + 1}/{len(df)} —")
|
||
# try:
|
||
# patient_text = build_patient_text(row)
|
||
# result = run_inference(patient_text)
|
||
#
|
||
# # Attach metadata
|
||
# result["unique_id"] = row.get("unique_id", f"row_{idx}")
|
||
# result["MedDatum"] = row.get("MedDatum", None)
|
||
#
|
||
# results.append(result)
|
||
#
|
||
# # Print summary
|
||
# if result["success"]:
|
||
# res = result["result"]
|
||
# edss = res.get("EDSS", "N/A") if res.get("klassifizierbar") else "N/A"
|
||
# print(f"✅ Result → EDSS={edss}, certainty={res.get('certainty_percent', 'N/A')}%")
|
||
# print(f" Reason: {res.get('reason', 'N/A')[:100]}…")
|
||
# else:
|
||
# print(f"❌ Failed: {result.get('error', 'Unknown error')[:100]}")
|
||
#
|
||
# except Exception as e:
|
||
# print(f"⚠️ Error processing row {idx}: {e}")
|
||
# results.append({
|
||
# "success": False,
|
||
# "error": str(e),
|
||
# "unique_id": row.get("unique_id", f"row_{idx}"),
|
||
# "MedDatum": row.get("MedDatum", None),
|
||
# "result": None
|
||
# })
|
||
#
|
||
# # Save results
|
||
# output_json = INPUT_CSV.replace(".csv", "_results_Nisch_certainty.json")
|
||
# with open(output_json, 'w', encoding='utf-8') as f:
|
||
# json.dump(results, f, indent=2, ensure_ascii=False)
|
||
# print(f"\n✅ Saved results to: {output_json}")
|
||
#
|
||
##
|
||
|
||
|
||
# %% API call - Multi-iteration EDSS + certainty extraction
|
||
|
||
import time
|
||
import json
|
||
import os
|
||
from datetime import datetime
|
||
import pandas as pd
|
||
from openai import OpenAI
|
||
from dotenv import load_dotenv
|
||
|
||
# Load environment variables
|
||
load_dotenv()
|
||
|
||
# === CONFIGURATION ===
|
||
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
|
||
OPENAI_BASE_URL = os.getenv("OPENAI_BASE_URL")
|
||
MODEL_NAME = "GPT-OSS-120B"
|
||
|
||
# File paths
|
||
INPUT_CSV = "/home/shahin/Lab/Doktorarbeit/Barcelona/Data/MS_Briefe_400_with_unique_id_SHA3_explore_cleaned_unique.csv"
|
||
EDSS_INSTRUCTIONS_PATH = "/home/shahin/Lab/Doktorarbeit/Barcelona/attach/Komplett.txt"
|
||
|
||
# Iteration settings
|
||
NUM_ITERATIONS = 20
|
||
STOP_ON_FIRST_ERROR = False # Set to True for debugging
|
||
|
||
# Initialize OpenAI client
|
||
client = OpenAI(
|
||
api_key=OPENAI_API_KEY,
|
||
base_url=OPENAI_BASE_URL
|
||
)
|
||
|
||
# Read EDSS instructions from file
|
||
with open(EDSS_INSTRUCTIONS_PATH, 'r') as f:
|
||
EDSS_INSTRUCTIONS = f.read().strip()
|
||
|
||
# === PROMPT (unchanged from before) ===
|
||
def build_prompt(patient_text):
|
||
return f'''Du bist ein medizinischer Assistent, der spezialisiert darauf ist, EDSS-Scores (Expanded Disability Status Scale), alle Unterkategorien und die Bewertungssicherheit aus klinischen Berichten zu extrahieren.
|
||
|
||
### Deine Aufgabe:
|
||
1. Analysiere den Patientenbericht und extrahiere:
|
||
- Den Gesamt-EDSS-Score (0.0–10.0)
|
||
- Alle 8 EDSS-Unterkategorien (mit jeweils eigener Maximalpunktzahl)
|
||
2. Schätze für jede Entscheidung die Sicherheit als Ganzzahl von 0–100 % ein.
|
||
|
||
### Struktur der JSON-Ausgabe (VERPFLICHTEND):
|
||
Gib NUR gültiges JSON zurück — kein Markdown, kein Text davor/dahinter.
|
||
|
||
{{
|
||
"reason": "Kernaussage zur EDSS-Begründung (max. 400 Zeichen, auf Deutsch).",
|
||
"klassifizierbar": true/false,
|
||
"EDSS": null ODER Zahl zwischen 0.0 und 10.0 (nur wenn klassifizierbar=true)",
|
||
"certainty_percent": 0 ODER Zahl zwischen 0 und 100 (Ganzzahl)",
|
||
"subcategories": {{
|
||
"VISUAL_OPTIC_FUNCTIONS": null ODER Zahl zwischen 0.0 und 6.0,
|
||
"BRAINSTEM_FUNCTIONS": null ODER Zahl zwischen 0.0 und 6.0,
|
||
"PYRAMIDAL_FUNCTIONS": null ODER Zahl zwischen 0.0 und 6.0,
|
||
"CEREBELLAR_FUNCTIONS": null ODER Zahl zwischen 0.0 und 6.0,
|
||
"SENSORY_FUNCTIONS": null ODER Zahl zwischen 0.0 und 6.0,
|
||
"BOWEL_AND_BLADDER_FUNCTIONS": null ODER Zahl zwischen 0.0 und 6.0,
|
||
"CEREBRAL_FUNCTIONS": null ODER Zahl zwischen 0.0 und 6.0,
|
||
"AMBULATION": null ODER Zahl zwischen 0.0 und 10.0
|
||
}}
|
||
}}
|
||
|
||
### Regeln:
|
||
- **reason**: Kurze, prägnante Begründung (auf Deutsch, max. 400 Zeichen), warum du den EDSS-Wert und die Unterkategorien so bewertest.
|
||
- **klassifizierbar**:
|
||
- `true`, wenn EDSS und mindestens die wichtigsten Unterkategorien *eindeutig ableitbar* oder *plausibel inferierbar* sind.
|
||
- `false`, **nur**, wenn keine relevanten Daten vorliegen, oder diese so widersprüchlich/inkonsistent sind, dass keine vernünftige Einschätzung möglich ist.
|
||
- **EDSS**:
|
||
- **VERPFLICHTEND**, wenn `klassifizierbar=true`.
|
||
- Zahl zwischen 0.0 und 10.0 (z.B. 3.0, 5.5). Darf **nicht** erscheinen, wenn `klassifizierbar=false`.
|
||
- **certainty_percent**:
|
||
- **Immer present** — Ganzzahl (0–100), basierend auf:
|
||
- Klarheit und Vollständigkeit der Berichtsangaben,
|
||
- Stichhaltigkeit der Schlussfolgerung (inkl. Inferenz),
|
||
- Konsistenz zwischen den Unterkategorien.
|
||
- **subcategories**:
|
||
- **Immer present** — **alle 8 Unterkategorien** müssen enthalten sein.
|
||
- Jeder Wert ist entweder:
|
||
- `null` (wenn keine ausreichende Information vorliegt), **oder**
|
||
- eine Zahl ≤ jeweiliger Obergrenze (z.B. Ambulation ≤ 10.0).
|
||
- Wenn die Unterkategorie plausibel inferiert werden kann (auch indirekt), gib einen sinnvollen Wert ab.
|
||
- Beispiel: Wenn „Gang mit Krückstock auf ebenem Boden bis 200 m“ steht, setze `AMBULATION: 5.5`.
|
||
|
||
### EDSS-Bewertungsrichtlinien:
|
||
{EDSS_INSTRUCTIONS}
|
||
|
||
Patientenbericht:
|
||
{patient_text}
|
||
'''
|
||
|
||
# === INFERENCE FUNCTION (unchanged) ===
|
||
def run_inference(patient_text):
|
||
prompt = build_prompt(patient_text)
|
||
|
||
start_time = time.time()
|
||
|
||
try:
|
||
response = client.chat.completions.create(
|
||
messages=[
|
||
{"role": "system", "content": "Du gibst EXKLUSIV gültiges JSON zurück — keine weiteren Erklärungen."}
|
||
] + [
|
||
{"role": "user", "content": prompt}
|
||
],
|
||
model=MODEL_NAME,
|
||
max_tokens=2048,
|
||
temperature=0.1,
|
||
response_format={"type": "json_object"}
|
||
)
|
||
|
||
content = response.choices[0].message.content
|
||
|
||
# Parse and validate JSON
|
||
try:
|
||
parsed = json.loads(content)
|
||
except json.JSONDecodeError as e:
|
||
print(f"⚠️ JSON parsing failed: {e}")
|
||
print("Raw response:", content[:500])
|
||
raise ValueError("Model did not return valid JSON")
|
||
|
||
# Enforce required keys
|
||
if "certainty_percent" not in parsed:
|
||
print("⚠️ Missing 'certainty_percent' in output! Force-adding fallback.")
|
||
parsed["certainty_percent"] = 0
|
||
elif not isinstance(parsed["certainty_percent"], (int, float)):
|
||
parsed["certainty_percent"] = int(parsed["certainty_percent"])
|
||
|
||
# Clamp certainty to [0, 100]
|
||
pct = parsed["certainty_percent"]
|
||
parsed["certainty_percent"] = max(0, min(100, int(pct)))
|
||
|
||
# Enforce EDSS rules
|
||
if not parsed.get("klassifizierbar", False):
|
||
if "EDSS" in parsed:
|
||
del parsed["EDSS"]
|
||
else:
|
||
if "EDSS" not in parsed:
|
||
print("⚠️ 'klassifizierbar' is true but EDSS missing — adding fallback.")
|
||
parsed["EDSS"] = 7.0
|
||
|
||
inference_time = time.time() - start_time
|
||
|
||
return {
|
||
"success": True,
|
||
"result": parsed,
|
||
"inference_time_sec": inference_time
|
||
}
|
||
|
||
except Exception as e:
|
||
print(f"❌ Inference error: {e}")
|
||
return {
|
||
"success": False,
|
||
"error": str(e),
|
||
"inference_time_sec": -1,
|
||
"result": None
|
||
}
|
||
|
||
# === BUILD PATIENT TEXT ===
|
||
def build_patient_text(row):
|
||
return (
|
||
str(row.get("T_Zusammenfassung", "")) + "\n" +
|
||
str(row.get("Diagnosen", "")) + "\n" +
|
||
str(row.get("T_KlinBef", "")) + "\n" +
|
||
str(row.get("T_Befunde", ""))
|
||
)
|
||
|
||
# === MAIN LOOP (NEW: MULTI-ITERATION) ===
|
||
if __name__ == "__main__":
|
||
# Load data ONCE (to avoid repeated I/O overhead)
|
||
df = pd.read_csv(INPUT_CSV, sep=';')
|
||
total_rows = len(df)
|
||
print(f"Loaded {total_rows} patient records.")
|
||
|
||
for iteration in range(1, NUM_ITERATIONS + 1):
|
||
print(f"\n{'='*60}")
|
||
print(f"🔄 ITERATION {iteration}/{NUM_ITERATIONS}")
|
||
print(f"{'='*60}")
|
||
|
||
iteration_results = []
|
||
start_iter = time.time()
|
||
|
||
for idx, row in df.iterrows():
|
||
print(f"\rRow {idx+1}/{total_rows} | Iter {iteration}", end='', flush=True)
|
||
try:
|
||
patient_text = build_patient_text(row)
|
||
result = run_inference(patient_text)
|
||
|
||
# Attach metadata
|
||
if result["success"]:
|
||
res = result["result"].copy() # avoid mutation
|
||
res["iteration"] = iteration
|
||
res["unique_id"] = row.get("unique_id", f"row_{idx}")
|
||
res["MedDatum"] = row.get("MedDatum", None)
|
||
result["result"] = res
|
||
|
||
else:
|
||
result["iteration"] = iteration
|
||
result["unique_id"] = row.get("unique_id", f"row_{idx}")
|
||
result["MedDatum"] = row.get("MedDatum", None)
|
||
|
||
iteration_results.append(result)
|
||
|
||
if result["success"]:
|
||
res = result["result"]
|
||
edss = res.get("EDSS", "N/A") if res.get("klassifizierbar") else "N/A"
|
||
print(f" ✅ EDSS={edss}, cert={res.get('certainty_percent', '?')}%")
|
||
else:
|
||
print(f" ❌ {result.get('error', 'Unknown')}")
|
||
|
||
except Exception as e:
|
||
print(f"\n⚠️ Row {idx} failed: {e}")
|
||
iteration_results.append({
|
||
"success": False,
|
||
"error": str(e),
|
||
"iteration": iteration,
|
||
"unique_id": row.get("unique_id", f"row_{idx}"),
|
||
"MedDatum": row.get("MedDatum", None),
|
||
"result": None
|
||
})
|
||
if STOP_ON_FIRST_ERROR:
|
||
break
|
||
|
||
# Save per-iteration results
|
||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||
output_path = INPUT_CSV.replace(".csv", f"_results_iter_{iteration}_{timestamp}.json")
|
||
with open(output_path, 'w', encoding='utf-8') as f:
|
||
json.dump(iteration_results, f, indent=2, ensure_ascii=False)
|
||
print(f"\n✅ Iteration {iteration} complete. Saved to: {output_path}")
|
||
|
||
elapsed = time.time() - start_iter
|
||
print(f"⏱️ Iteration {iteration} took {elapsed:.1f}s ({elapsed/total_rows:.1f}s/row)")
|
||
|
||
print(f"\n🎉 All {NUM_ITERATIONS} iterations completed!")
|
||
|
||
|
||
|
||
##
|