# %% 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!") ##