28 Commits

Author SHA1 Message Date
816c50e467 Config Dashbprard 2026-02-23 18:19:50 +01:00
118e3e63b3 refinement 2026-02-23 15:06:54 +01:00
99862629b8 update gitignore 2026-02-23 00:43:33 +01:00
9cc80cd3e6 Audit code 2026-02-23 00:42:41 +01:00
424d38ad1c certainty Delta show 2026-02-18 17:12:31 +01:00
f1d22b28ad updated plot certainty 2026-02-13 09:22:53 +01:00
8e4a43c557 add certainty 2026-02-12 13:39:36 +01:00
2f507bcf20 Adjsuting and cleaning 2026-02-08 01:59:38 +01:00
f4bf37f71c show directional errors
Directional Errors of each functional system.
2026-02-08 01:27:48 +01:00
bc63d1ee72 added new confusion matrix 2026-02-04 18:01:11 +01:00
c2ccb8cd11 update gitignore 2026-02-04 15:29:56 +01:00
b2e9ccd2b6 adding some visualizations 2026-01-26 02:02:19 +01:00
2f1bd2bfd0 save 2026-01-20 14:47:53 +01:00
c145b66cdf optimize dashboard 2026-01-20 13:30:48 +01:00
0da8440496 Dashboard 2026-01-20 13:28:02 +01:00
cc830f00e8 Hitogram Plot 2026-01-20 12:49:47 +01:00
ce3baff6cc optimize with new column names 2026-01-19 02:29:38 +01:00
a1a8abfb8e beautiful plot 2026-01-19 01:26:14 +01:00
8f34f06578 ugly plot 2026-01-19 01:04:00 +01:00
eabde3fcb1 optimize 2026-01-19 00:52:55 +01:00
2a715233ee seaborn styled table 2026-01-19 00:43:29 +01:00
a415632552 updated git ignore and new files 2026-01-19 00:39:13 +01:00
16aa6c206e gitignore update 2026-01-19 00:26:27 +01:00
c11a81548a recall the failed call 2026-01-18 23:35:34 +01:00
e453cf379c Adjusting import 2026-01-18 22:37:29 +01:00
454273a6cb backing up Edss total 2026-01-18 22:32:24 +01:00
2cab5fd9b3 exx 2026-01-18 22:06:53 +01:00
90436584f8 Experiment branch commit 2026-01-18 22:04:27 +01:00
8 changed files with 6763 additions and 5 deletions

24
.gitignore vendored
View File

@@ -1,6 +1,20 @@
# Ignore all contents of these directories # 1. Broad Ignores
/Data/ /Data/*
/attach/ /attach/*
/results/ /results/*
/enarcelona/ /enarcelona/*
.env .env
__pycache__/
*.pyc
*.csv
=======
/reference/
*.svg
>>>>>>> Stashed changes
# 2. Ignore virtual environments COMPLETELY
# This must come BEFORE the unignore rule
env*/
# 3. The "Unignore" rule (Whitelisting)
# We only unignore .py files that aren't already blocked by the rules above
!**/*.py

5
app.py
View File

@@ -214,3 +214,8 @@ if __name__ == "__main__":
print(f"Results saved to {output_json}") print(f"Results saved to {output_json}")
## ##
# %% name
eXXXXXXXX
##

2371
audit.py Normal file

File diff suppressed because it is too large Load Diff

600
certainty.py Normal file
View File

@@ -0,0 +1,600 @@
# %% 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.010.0)
# - Alle 8 EDSS-Unterkategorien (mit jeweils eigener Maximalpunktzahl)
#2. Schätze für jede Entscheidung die Sicherheit als Ganzzahl von 0100 % 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 (0100), 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.010.0)
- Alle 8 EDSS-Unterkategorien (mit jeweils eigener Maximalpunktzahl)
2. Schätze für jede Entscheidung die Sicherheit als Ganzzahl von 0100 % 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 (0100), 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!")
##

1540
certainty_show.py Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -263,3 +263,120 @@ plt.legend(frameon=False, loc='upper center', bbox_to_anchor=(0.5, -0.05))
plt.tight_layout() plt.tight_layout()
plt.show() plt.show()
## ##
# %% name
import matplotlib.pyplot as plt
# Data
data = {
'Visit': [9, 8, 7, 6, 5, 4, 3, 2, 1],
'patient_count': [2, 3, 3, 6, 13, 17, 28, 24, 32]
}
# Create figure and axis
fig, ax = plt.subplots(figsize=(10, 6))
# Plot the bar chart
bars = ax.bar(data['Visit'], data['patient_count'], color='darkblue', label='Patients by Visit Count')
# Add labels and title
ax.set_xlabel('Visit Number (from last to first)', fontsize=12)
ax.set_ylabel('Number of Patients', fontsize=12)
ax.set_title('Patient Visits by Visit Number', fontsize=14)
# Invert x-axis to show Visit 9 on the left (descending order) if desired, but keep natural order (19 left to right)
# For descending order (9→1 from left to right), we'd need to reverse:
# Visit = data['Visit'][::-1], patient_count = data['patient_count'][::-1]
# But standard practice is ascending (1 to 9), so we'll sort accordingly:
# Let's sort by Visit to ensure left-to-right: 1,2,...,9
# Actually, your current Visit list is [9,8,...,1], which is descending.
# Let's sort by Visit for intuitive left-to-right increasing order:
sorted_indices = sorted(range(len(data['Visit'])), key=lambda i: data['Visit'][i])
visit_sorted = [data['Visit'][i] for i in sorted_indices]
count_sorted = [data['patient_count'][i] for i in sorted_indices]
# Re-plot with sorted x-axis:
ax.clear()
bars = ax.bar(visit_sorted, count_sorted, color='darkblue', label='Patients by Visit Count')
# Re-apply labels, etc.
ax.set_xlabel('Number of Visits', fontsize=12)
ax.set_ylabel('Number of Unique Patients', fontsize=12)
#ax.set_title('Number of Patients by Visit Number', fontsize=14)
# Add legend
ax.legend()
# Improve layout and grid
ax.grid(axis='y', linestyle='--', alpha=0.7)
plt.xticks(visit_sorted) # Ensure all integer visit numbers are shown
# Show the plot
plt.tight_layout()
plt.show()
##
# %% Patientjourney Bubble chart
import matplotlib.pyplot as plt
import numpy as np
import matplotlib as mpl
mpl.rcParams["font.family"] = "DejaVu Sans" # or "Arial", "Calibri", "Times New Roman", ...
mpl.rcParams["font.size"] = 12 # default size for text
mpl.rcParams["axes.titlesize"] = 14
mpl.rcParams["axes.titleweight"] = "bold"
# Data (your counts)
visits = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9])
patient_count = np.array([32, 24, 28, 17, 13, 6, 3, 3, 2])
# "Remaining" = patients with >= that many visits (cumulative from the right)
remaining = np.array([patient_count[i:].sum() for i in range(len(patient_count))])
# --- Plot ---
fig, ax = plt.subplots(figsize=(12, 3))
y = 0.0 # all bubbles on one horizontal line
# Horizontal line
ax.hlines(y, visits.min() - 0.4, visits.max() + 0.4, color="#1f77b4", linewidth=3)
# Bubble sizes (scale as needed)
# (Matplotlib scatter uses area in points^2)
sizes = patient_count * 35 # tweak this multiplier if you want bigger/smaller bubbles
ax.scatter(visits, np.full_like(visits, y), s=sizes, color="#1f77b4", zorder=3)
# Title
#ax.set_title("Patient Journey by Visit Count", fontsize=14, pad=18)
# Top labels: "1 visits", "2 visits", ...
for x in visits:
label = f"{x} visit" if x == 1 else f"{x} visits"
ax.text(x, y + 0.18, label, ha="center", va="bottom", fontsize=10)
# Bottom labels: "X patients" and "Y remaining"
for x, pc, rem in zip(visits, patient_count, remaining):
ax.text(x, y - 0.20, f"{pc} patients", ha="center", va="top", fontsize=9)
ax.text(x, y - 0.32, f"{rem} remaining", ha="center", va="top", fontsize=9)
# Cosmetics: remove axes, keep spacing nice
ax.set_xlim(visits.min() - 0.6, visits.max() + 0.6)
ax.set_ylim(-0.5, 0.35)
ax.set_xticks([])
ax.set_yticks([])
for spine in ax.spines.values():
spine.set_visible(False)
plt.tight_layout()
plt.show()
plt.savefig("patient_journey.svg", format="svg", bbox_inches="tight")
##

1962
show_plots.py Normal file

File diff suppressed because it is too large Load Diff

149
total_app.py Normal file
View File

@@ -0,0 +1,149 @@
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, max_retries=3):
prompt = f'''Du bist ein medizinischer Assistent, der spezialisiert darauf ist, EDSS-Scores (Expanded Disability Status Scale) sowie alle Unterkategorien 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.
4. **Unterkategorien**:
- Extrahiere alle folgenden Unterkategorien aus dem Bericht:
- VISUAL OPTIC FUNCTIONS (max. 6.0)
- BRAINSTEM FUNCTIONS (max. 6.0)
- PYRAMIDAL FUNCTIONS (max. 6.0)
- CEREBELLAR FUNCTIONS (max. 6.0)
- SENSORY FUNCTIONS (max. 6.0)
- BOWEL AND BLADDER FUNCTIONS (max. 6.0)
- CEREBRAL FUNCTIONS (max. 6.0)
- AMBULATION (max. 10.0)
- Jede Unterkategorie sollte eine Zahl zwischen 0.0 und der jeweiligen Obergrenze enthalten, wenn sie klassifizierbar ist
- Wenn eine Unterkategorie nicht klassifizierbar ist, setze den Wert auf null
### Einschränkungen:
- Erfinde keine Fakten, aber nutze klinische Herleitungen aus dem Bericht, um den EDSS und die Unterkategorien zu bestimmen.
- Priorisiere die Vergabe eines EDSS-Wertes gegenüber der Markierung als nicht klassifizierbar.
- Halte dich strikt an die JSON-Struktur.
- Die Unterkategorien müssen immer enthalten sein, auch wenn sie null sind.
EDSS-Bewertungsrichtlinien:
{EDSS_INSTRUCTIONS}
Patientenbericht:
{patient_text}
'''
start_time = time.time()
for attempt in range(max_retries + 1):
try:
response = client.chat.completions.create(
messages=[
{
"role": "system",
"content": "You extract EDSS scores and all subcategories. You prioritize providing values 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"}
)
content = response.choices[0].message.content
if content is None or content.strip() == "":
raise ValueError("API returned empty or None response content")
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"Attempt {attempt + 1} failed: {e}")
if attempt < max_retries:
time.sleep(2 ** attempt) # Exponential backoff
continue
else:
print("All retries exhausted.")
return {
"success": False,
"error": str(e),
"inference_time_sec": -1
}
# === BUILD PATIENT TEXT ===
def build_patient_text(row):
# Handle potential NaN or None values in the row
summary = str(row.get("T_Zusammenfassung", "")) if pd.notna(row.get("T_Zusammenfassung")) else ""
diagnoses = str(row.get("Diagnosen", "")) if pd.notna(row.get("Diagnosen")) else ""
clinical = str(row.get("T_KlinBef", "")) if pd.notna(row.get("T_KlinBef")) else ""
findings = str(row.get("T_Befunde", "")) if pd.notna(row.get("T_Befunde")) else ""
return "\n".join([summary, diagnoses, clinical, findings]).strip()
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, ensure_ascii=False))
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_total.json")
with open(output_json, 'w', encoding='utf-8') as f:
json.dump(results, f, indent=2, ensure_ascii=False)
print(f"Results saved to {output_json}")