import gradio as gr import joblib import pandas as pd import numpy as np import shap import matplotlib.pyplot as plt from fpdf import FPDF import tempfile # --- Загрузка моделей --- try: model = joblib.load('allostatic_model.pkl') imputer = joblib.load('imputer.pkl') explainer = joblib.load('shap_explainer.pkl') expected_features = imputer.feature_names_in_.tolist() except Exception as e: print(f"Ошибка загрузки: {e}") RU_NAMES = { 'RIDAGEYR': 'Возраст', 'RIAGENDR': 'Пол', 'BMXBMI': 'ИМТ (BMI)', 'BPXSY1': 'Сист. давление', 'LBXNEPCT': 'Нейтрофилы %', 'LBDLYMNO': 'Лимфоциты %', 'LBXSATSI': 'АСТ (U/L)', 'LBXPLTSI': 'Тромбоциты', 'LBXSCR': 'Креатинин', 'LBXGH': 'Гликированный Hb', 'RXDCOUNT': 'Кол-во лекарств', 'NLR': 'Индекс NLR', 'APRI': 'Индекс APRI', 'eGFR': 'eGFR (фильтрация)', 'has_symptoms_for_stage4': 'Симптомы', 'simplified_AL_Index': 'Аллостатический индекс' } def calculate_metrics_dict(d): g_num = 1 if d.get('RIAGENDR') == "Male" else 2 age = float(d.get('RIDAGEYR', 50)) creat = float(d.get('LBXSCR', 0.9)) nlr = d.get('LBXNEPCT', 60) / d.get('LBDLYMNO', 30) if d.get('LBDLYMNO', 0) > 0 else 0 apri = (d.get('LBXSATSI', 25) / 40) / d.get('LBXPLTSI', 250) * 100 if d.get('LBXPLTSI', 0) > 0 else 0 kappa = 0.7 if g_num == 2 else 0.9 alpha = -0.241 if g_num == 2 else -0.302 egfr = 142 * (min(creat/kappa, 1)**alpha) * (max(creat/kappa, 1)**-1.2) * (0.9938**age) * (1.012 if g_num == 2 else 1.0) symptoms = 1 if any([d.get('PFQ', False), d.get('MEM', False), d.get('CONC', False), d.get('WALK', False)]) else 0 al_idx = sum([nlr > 2.5, d.get('LBXGH', 5.4) > 5.7, egfr < 90, d.get('RXDCOUNT', 0) >= 3, apri > 0.5, d.get('BMXBMI', 25) > 30, d.get('BPXSY1', 120) > 140]) mapping = { 'RIDAGEYR': age, 'RIAGENDR': g_num, 'BMXBMI': d.get('BMXBMI', 25), 'BPXSY1': d.get('BPXSY1', 120), 'LBXNEPCT': d.get('LBXNEPCT', 60), 'LBDLYMNO': d.get('LBDLYMNO', 30), 'LBXSATSI': d.get('LBXSATSI', 25), 'LBXPLTSI': d.get('LBXPLTSI', 250), 'LBXSCR': creat, 'LBXGH': d.get('LBXGH', 5.4), 'RXDCOUNT': d.get('RXDCOUNT', 0), 'NLR': nlr, 'APRI': apri, 'eGFR': egfr, 'has_symptoms_for_stage4': symptoms, 'simplified_AL_Index': al_idx, 'PFQ049': 1 if d.get('PFQ') else 0, 'PFQ051': 1 if d.get('PFQ') else 0, 'PFQ061Q': 1 if d.get('PFQ') else 0, 'PFQ090': 1 if d.get('PFQ') else 0 } return mapping, al_idx, egfr def main_analysis(age, gender, bmi, sys_bp, neutro, lympho, ast, plt_val, creat, hba1c, rxd, pfq, mem, conc, walk): input_data = { 'RIDAGEYR': age, 'RIAGENDR': gender, 'BMXBMI': bmi, 'BPXSY1': sys_bp, 'LBXNEPCT': neutro, 'LBDLYMNO': lympho, 'LBXSATSI': ast, 'LBXPLTSI': plt_val, 'LBXSCR': creat, 'LBXGH': hba1c, 'RXDCOUNT': rxd, 'PFQ': pfq, 'MEM': mem, 'CONC': conc, 'WALK': walk } mapping, al_idx, egfr = calculate_metrics_dict(input_data) input_df = pd.DataFrame([{f: mapping.get(f, 0) for f in expected_features}])[expected_features] input_imputed = imputer.transform(input_df) raw_stage = int(model.predict(input_imputed)[0]) stage = 1 if al_idx <= 1 and raw_stage >= 5 else raw_stage # SHAP plt.close('all') plt.figure(figsize=(10, 5)) s_vals = explainer.shap_values(input_imputed) s_array = np.array(s_vals) c_idx = min(stage, s_array.shape[0]-1) if s_array.ndim == 3 else 0 final_shap = s_array[c_idx, 0, :] if s_array.ndim == 3 else (s_array[0] if s_array.ndim > 1 else s_array) shap.bar_plot(final_shap, feature_names=[RU_NAMES.get(f, f) for f in expected_features], show=False) plt.tight_layout() plot_path = tempfile.NamedTemporaryFile(delete=False, suffix=".png").name plt.savefig(plot_path) # PDF pdf = FPDF() pdf.add_page() pdf.set_font("Arial", 'B', size=16) pdf.cell(200, 10, txt="Allostatic Risk Report", ln=True, align='C') pdf.set_font("Arial", size=12) pdf.ln(10) pdf.cell(200, 10, txt=f"Stage: {stage}", ln=True) pdf.cell(200, 10, txt=f"AL Index: {al_idx}", ln=True) pdf.cell(200, 10, txt=f"eGFR: {egfr:.2f}", ln=True) pdf.image(plot_path, x=10, y=70, w=180) pdf_path = tempfile.NamedTemporaryFile(delete=False, suffix=".pdf").name pdf.output(pdf_path) # Цветовая индикация color = "green" if stage == 1 else "orange" if stage < 4 else "red" res_label = f"СТАДИЯ {stage}" return res_label, f"Индекс нагрузки: {al_idx} | eGFR: {egfr:.1f}", plot_path, pdf_path with gr.Blocks(theme=gr.themes.Soft()) as demo: gr.Markdown("# 🏥 ProPharm Dashboard") with gr.Row(): with gr.Column(scale=1): gr.Markdown("### 🎚️ Параметры пациента") with gr.Group(): age = gr.Slider(18, 95, label="Возраст", value=50) gender = gr.Radio(["Male", "Female"], label="Пол", value="Male") bmi = gr.Slider(10, 60, label="ИМТ (BMI)", value=24.5, step=0.1) sys_bp = gr.Slider(80, 220, label="Систолическое АД", value=120) with gr.Group(): gr.Markdown("🧪 **Лаборатория**") neutro = gr.Slider(10, 90, label="Нейтрофилы %", value=60) lympho = gr.Slider(5, 70, label="Лимфоциты %", value=30) ast = gr.Slider(5, 150, label="АСТ (U/L)", value=25) plt_val = gr.Slider(50, 600, label="Тромбоциты", value=250) creat = gr.Slider(0.3, 5.0, label="Креатинин (mg/dL)", value=0.9, step=0.1) hba1c = gr.Slider(3, 15, label="Гликированный Hb %", value=5.4, step=0.1) rxd = gr.Slider(0, 15, label="Кол-во лекарств", value=0) with gr.Group(): gr.Markdown("🧠 **Гериатрический статус**") with gr.Row(): pfq = gr.Checkbox(label="Физ. огр.") mem = gr.Checkbox(label="Память") conc = gr.Checkbox(label="Конц.") walk = gr.Checkbox(label="Ходьба") btn = gr.Button("🚀 ПРОВЕСТИ АНАЛИЗ", variant="primary") with gr.Column(scale=1): gr.Markdown("### 📊 Заключение") out_stage = gr.Label(label="Результат") out_ind = gr.Textbox(label="Ключевые показатели") out_plot = gr.Image(label="Вклад факторов (SHAP)") out_pdf = gr.File(label="📥 Отчет для печати (PDF)") btn.click(main_analysis, [age, gender, bmi, sys_bp, neutro, lympho, ast, plt_val, creat, hba1c, rxd, pfq, mem, conc, walk], [out_stage, out_ind, out_plot, out_pdf]) demo.launch()