Spaces:
Sleeping
Sleeping
Upload 4 files
Browse files- logic/care_gap_engine.py +39 -0
- logic/exporter.py +29 -0
- logic/financial_model.py +26 -0
- logic/nlp_report.py +68 -0
logic/care_gap_engine.py
ADDED
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# File: logic/care_gap_engine.py
|
2 |
+
|
3 |
+
import pandas as pd
|
4 |
+
from datetime import datetime
|
5 |
+
|
6 |
+
def evaluate_care_gaps(df: pd.DataFrame, config):
|
7 |
+
today = datetime.today()
|
8 |
+
rules = config["care_gap_rules"]
|
9 |
+
results = []
|
10 |
+
|
11 |
+
for _, row in df.iterrows():
|
12 |
+
gaps = []
|
13 |
+
|
14 |
+
if row['gender'] == 'F' and rules['Breast Cancer Screening']['min_age'] <= row['age'] <= rules['Breast Cancer Screening']['max_age']:
|
15 |
+
if not row['last_mammogram'] or (today - pd.to_datetime(row['last_mammogram'], errors='coerce')).days > rules['Breast Cancer Screening']['interval_days']:
|
16 |
+
gaps.append("Breast Cancer Screening")
|
17 |
+
|
18 |
+
if rules['Colorectal Cancer Screening']['min_age'] <= row['age'] <= rules['Colorectal Cancer Screening']['max_age']:
|
19 |
+
if not row['last_colonoscopy'] or (today - pd.to_datetime(row['last_colonoscopy'], errors='coerce')).days > rules['Colorectal Cancer Screening']['interval_days']:
|
20 |
+
gaps.append("Colorectal Cancer Screening")
|
21 |
+
|
22 |
+
if row.get('systolic_bp', '') != "" and pd.to_numeric(row['systolic_bp'], errors='coerce') > rules['Blood Pressure Control']['bp_threshold']:
|
23 |
+
gaps.append("Blood Pressure Control")
|
24 |
+
|
25 |
+
if row.get('hba1c_value', '') != "" and pd.to_numeric(row['hba1c_value'], errors='coerce') >= 9:
|
26 |
+
gaps.append("Diabetes: Poor HbA1c Control")
|
27 |
+
|
28 |
+
if row.get('FollowUp_Scheduled', '').strip().lower() == 'no' or row.get('Primary_Care_Established', '').strip().lower() == 'no':
|
29 |
+
gaps.append("Follow-Up Care")
|
30 |
+
|
31 |
+
if row.get('Previous_Readmissions', '').isdigit() and int(row['Previous_Readmissions']) >= 3:
|
32 |
+
gaps.append("Readmission Risk")
|
33 |
+
|
34 |
+
results.append({
|
35 |
+
'patient_id': row['patient_id'],
|
36 |
+
'care_gaps': gaps
|
37 |
+
})
|
38 |
+
|
39 |
+
return pd.DataFrame(results)
|
logic/exporter.py
ADDED
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# File: logic/exporter.py
|
2 |
+
|
3 |
+
from docx import Document
|
4 |
+
import os
|
5 |
+
import io
|
6 |
+
import zipfile
|
7 |
+
|
8 |
+
def export_to_docx_and_zip(report_data, summaries, output_dir="assets/sample_reports"):
|
9 |
+
zip_buffer = io.BytesIO()
|
10 |
+
with zipfile.ZipFile(zip_buffer, "w") as zipf:
|
11 |
+
for _, row in report_data.iterrows():
|
12 |
+
pid = row['patient_id']
|
13 |
+
doc = Document()
|
14 |
+
doc.add_heading(f'Patient Summary: {pid}', 0)
|
15 |
+
doc.add_paragraph(f"Risk Score: {row['risk_score']}")
|
16 |
+
doc.add_paragraph(f"Projected Revenue Gain: ${row['projected_gain']}")
|
17 |
+
doc.add_paragraph("Care Gaps:")
|
18 |
+
for gap in row['care_gaps']:
|
19 |
+
doc.add_paragraph(f"• {gap}", style='List Bullet')
|
20 |
+
doc.add_paragraph("\nSummary:")
|
21 |
+
doc.add_paragraph(summaries[pid])
|
22 |
+
|
23 |
+
doc_path = f"Patient_{pid}_Report.docx"
|
24 |
+
temp_stream = io.BytesIO()
|
25 |
+
doc.save(temp_stream)
|
26 |
+
zipf.writestr(doc_path, temp_stream.getvalue())
|
27 |
+
|
28 |
+
zip_buffer.seek(0)
|
29 |
+
return zip_buffer, report_data['patient_id'].tolist()
|
logic/financial_model.py
ADDED
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# File: logic/financial_model.py
|
2 |
+
|
3 |
+
def estimate_financial_recovery(care_gaps_df, patient_data, base_rate, sdoh_keys):
|
4 |
+
results = []
|
5 |
+
hcc_weights = {
|
6 |
+
'HCC18': 0.4, 'HCC85': 0.5, 'HCC19': 0.3
|
7 |
+
}
|
8 |
+
|
9 |
+
for _, row in care_gaps_df.iterrows():
|
10 |
+
patient = patient_data[patient_data['patient_id'] == row['patient_id']].iloc[0]
|
11 |
+
hccs = [h.strip() for h in str(patient.get('hcc_codes', '')).split(';') if h.strip() in hcc_weights]
|
12 |
+
risk_score = sum(hcc_weights[h] for h in hccs)
|
13 |
+
|
14 |
+
sdoh_count = sum(1 for key in sdoh_keys if str(patient.get(key, '')).strip().lower() == 'yes')
|
15 |
+
sdoh_modifier = 1 + 0.05 * sdoh_count # Each SDOH adds 5% to the opportunity
|
16 |
+
|
17 |
+
care_gap_count = len(row['care_gaps'])
|
18 |
+
expected_gain = base_rate * (risk_score * 0.02 * care_gap_count) * sdoh_modifier
|
19 |
+
|
20 |
+
results.append({
|
21 |
+
'patient_id': row['patient_id'],
|
22 |
+
'risk_score': round(risk_score, 2),
|
23 |
+
'projected_gain': round(expected_gain, 2)
|
24 |
+
})
|
25 |
+
|
26 |
+
return pd.DataFrame(results)
|
logic/nlp_report.py
ADDED
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# File: logic/nlp_report.py
|
2 |
+
|
3 |
+
from huggingface_hub import hf_hub_download
|
4 |
+
from llama_cpp import Llama
|
5 |
+
import logging
|
6 |
+
|
7 |
+
MODEL_REPO = "bartowski/deepcogito_cogito-v1-preview-llama-8B-GGUF"
|
8 |
+
MODEL_FILENAME = "deepcogito_cogito-v1-preview-llama-8B-Q8_0.gguf"
|
9 |
+
|
10 |
+
model_path = hf_hub_download(
|
11 |
+
repo_id=MODEL_REPO,
|
12 |
+
filename=MODEL_FILENAME,
|
13 |
+
cache_dir="models"
|
14 |
+
)
|
15 |
+
|
16 |
+
llm = Llama(
|
17 |
+
model_path=model_path,
|
18 |
+
n_ctx=2048,
|
19 |
+
chat_format="chatml",
|
20 |
+
verbose=False
|
21 |
+
)
|
22 |
+
|
23 |
+
def generate_patient_summary(patient_row, tone="executive", override_prompt=None):
|
24 |
+
try:
|
25 |
+
gaps = patient_row['care_gaps']
|
26 |
+
gain = patient_row['projected_gain']
|
27 |
+
score = patient_row['risk_score']
|
28 |
+
|
29 |
+
social_flags = []
|
30 |
+
if str(patient_row.get("Lives_Alone", "")).strip().lower() == "yes":
|
31 |
+
social_flags.append("lives alone")
|
32 |
+
if str(patient_row.get("Housing_Instability", "")).strip().lower() == "yes":
|
33 |
+
social_flags.append("experiencing housing instability")
|
34 |
+
if str(patient_row.get("Transportation_Access", "")).strip().lower() == "yes":
|
35 |
+
social_flags.append("has limited transportation access")
|
36 |
+
if str(patient_row.get("Food_Insecurity", "")).strip().lower() == "yes":
|
37 |
+
social_flags.append("faces food insecurity")
|
38 |
+
if str(patient_row.get("Primary_Care_Established", "")).strip().lower() == "no":
|
39 |
+
social_flags.append("does not have a primary care provider")
|
40 |
+
if str(patient_row.get("FollowUp_Scheduled", "")).strip().lower() == "no":
|
41 |
+
social_flags.append("has no follow-up scheduled")
|
42 |
+
|
43 |
+
social_context = "; ".join(social_flags) if social_flags else "no major social risk factors identified"
|
44 |
+
|
45 |
+
base_prompt = override_prompt or f"""
|
46 |
+
You are a healthcare analyst creating summaries for value-based care programs.
|
47 |
+
Tone: {tone}
|
48 |
+
|
49 |
+
Patient ID: {patient_row['patient_id']}
|
50 |
+
Risk Score: {score}
|
51 |
+
Projected Revenue Gain: ${gain}
|
52 |
+
Care Gaps Identified: {', '.join(gaps)}
|
53 |
+
Social Risk Factors: {social_context}
|
54 |
+
|
55 |
+
Write a concise and insightful clinical summary, including key gaps and social considerations. Offer targeted recommendations.
|
56 |
+
"""
|
57 |
+
|
58 |
+
response = llm.create_chat_completion(
|
59 |
+
messages=[{"role": "user", "content": base_prompt}]
|
60 |
+
)
|
61 |
+
|
62 |
+
if "choices" in response and response["choices"]:
|
63 |
+
return response["choices"][0]["message"]["content"]
|
64 |
+
else:
|
65 |
+
return "[Error] No summary returned."
|
66 |
+
except Exception as e:
|
67 |
+
logging.error(f"LLM error: {e}")
|
68 |
+
return "[Error generating summary]"
|