Update app.py
Browse files
app.py
CHANGED
|
@@ -106,6 +106,7 @@ DONOR_FEATURES = ["DONORF", "GRAFTYPE", "HLA_FINAL", "CONDGRPF", "CONDGRP_FINA
|
|
| 106 |
DISEASE_FEATURES = ["NACS2YR", "EXCHTFPR", "VOC2YPR", "VOCFRQPR", "SCATXRSN"]
|
| 107 |
ALL_FEATURES = PATIENT_FEATURES + DONOR_FEATURES + DISEASE_FEATURES
|
| 108 |
|
|
|
|
| 109 |
ICON_OUTCOMES = ["DEAD", "GF", "AGVHD", "CGVHD", "VOCPSHI", "STROKEHI"]
|
| 110 |
OUTCOME_TITLES = {
|
| 111 |
"DEAD": "Death",
|
|
@@ -305,26 +306,7 @@ def lock_sex(baseline_sex):
|
|
| 305 |
|
| 306 |
|
| 307 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 308 |
-
#
|
| 309 |
-
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 310 |
-
|
| 311 |
-
def _nav_button_html(prefix, current_idx, total, label_list):
|
| 312 |
-
"""Generate prev/next nav bar HTML for a carousel."""
|
| 313 |
-
current_label = label_list[current_idx] if label_list else f"{current_idx+1}/{total}"
|
| 314 |
-
prev_disabled = "disabled" if current_idx == 0 else ""
|
| 315 |
-
next_disabled = "disabled" if current_idx >= total - 1 else ""
|
| 316 |
-
return (
|
| 317 |
-
f'<div style="display:flex;align-items:center;justify-content:center;gap:12px;'
|
| 318 |
-
f'padding:6px 0 10px;font-family:\'Segoe UI\',Arial,sans-serif;">'
|
| 319 |
-
f'<span style="color:#888;font-size:11px;">{current_idx+1} / {total}</span>'
|
| 320 |
-
f'<span style="font-size:13px;font-weight:600;color:#1565c0;min-width:200px;text-align:center;">'
|
| 321 |
-
f'{current_label}</span>'
|
| 322 |
-
f'</div>'
|
| 323 |
-
)
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 327 |
-
# HTML RENDERERS
|
| 328 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 329 |
|
| 330 |
def _stick_figure_svg(color, size=16):
|
|
@@ -342,7 +324,8 @@ def _stick_figure_svg(color, size=16):
|
|
| 342 |
)
|
| 343 |
|
| 344 |
|
| 345 |
-
def
|
|
|
|
| 346 |
title = OUTCOME_TITLES.get(outcome, OUTCOME_DESCRIPTIONS.get(outcome, outcome))
|
| 347 |
n_event = round(probability * 100)
|
| 348 |
n_no_event = 100 - n_event
|
|
@@ -379,135 +362,84 @@ def _icon_card_html(probability, outcome, panel_label="", panel_color="#1565c0")
|
|
| 379 |
|
| 380 |
return (
|
| 381 |
f'<div style="background:#fff;border:1px solid #e0e0e0;border-radius:7px;'
|
| 382 |
-
f'padding:
|
| 383 |
-
f'box-shadow:0 2px 4px rgba(0,0,0,0.06);box-sizing:border-box;'
|
| 384 |
-
f'display:flex;flex-direction:column;align-items:center;">'
|
| 385 |
f'{badge}'
|
| 386 |
-
f'<div style="min-height:
|
| 387 |
-
f'font-size:
|
| 388 |
-
f'<div style="font-size:
|
| 389 |
-
f'line-height:1;margin-bottom:
|
| 390 |
-
f'<div style="margin-bottom:
|
| 391 |
f'<div>{legend}</div>'
|
| 392 |
f'</div>'
|
| 393 |
)
|
| 394 |
|
| 395 |
|
| 396 |
-
def
|
| 397 |
-
"""
|
| 398 |
-
|
| 399 |
-
|
| 400 |
-
|
| 401 |
-
|
| 402 |
-
|
| 403 |
-
|
| 404 |
-
|
| 405 |
-
|
| 406 |
-
|
| 407 |
-
|
| 408 |
-
|
| 409 |
-
|
| 410 |
-
|
| 411 |
-
|
| 412 |
-
|
| 413 |
-
uid = abs(hash(title_prefix + str(list(probs_dict.values())[:2]))) % 999999
|
| 414 |
|
| 415 |
footnote = (
|
| 416 |
-
f'<div style="font-size:10px;color:#888;text-align:center;margin-top:
|
| 417 |
f'Each figure = 1 patient out of 100. '
|
| 418 |
f'<span style="color:{EVENT_COLOR};font-weight:600;">■ Red = Event</span> '
|
| 419 |
f'<span style="color:{NO_EVENT_COLOR};font-weight:600;">■ Green = No Event</span>'
|
| 420 |
f'</div>'
|
| 421 |
)
|
| 422 |
|
| 423 |
-
return
|
| 424 |
-
<div
|
| 425 |
-
|
| 426 |
-
|
| 427 |
-
|
| 428 |
-
|
| 429 |
-
|
| 430 |
-
|
| 431 |
-
|
| 432 |
-
<div style="text-align:center;min-width:200px;">
|
| 433 |
-
<div id="ic-label-{uid}" style="font-size:14px;font-weight:700;color:#1565c0;"></div>
|
| 434 |
-
<div id="ic-counter-{uid}" style="font-size:11px;color:#888;margin-top:2px;"></div>
|
| 435 |
-
</div>
|
| 436 |
-
<button id="ic-next-{uid}"
|
| 437 |
-
onclick="icNav_{uid}(1)"
|
| 438 |
-
style="background:#1565c0;color:#fff;border:none;border-radius:6px;
|
| 439 |
-
padding:6px 18px;font-size:18px;cursor:pointer;font-weight:700;
|
| 440 |
-
line-height:1;user-select:none;"
|
| 441 |
-
aria-label="Next outcome">→</button>
|
| 442 |
-
</div>
|
| 443 |
-
<div id="ic-card-{uid}" style="max-width:320px;margin:0 auto;"></div>
|
| 444 |
-
{footnote}
|
| 445 |
-
</div>
|
| 446 |
-
<script>
|
| 447 |
-
(function() {{
|
| 448 |
-
var cards_{uid} = {cards_js};
|
| 449 |
-
var labels_{uid} = {labels_js};
|
| 450 |
-
var idx_{uid} = 0;
|
| 451 |
-
var total_{uid} = cards_{uid}.length;
|
| 452 |
-
|
| 453 |
-
function render_{uid}() {{
|
| 454 |
-
document.getElementById('ic-card-{uid}').innerHTML = cards_{uid}[idx_{uid}];
|
| 455 |
-
document.getElementById('ic-label-{uid}').textContent = labels_{uid}[idx_{uid}];
|
| 456 |
-
document.getElementById('ic-counter-{uid}').textContent = (idx_{uid}+1) + ' / ' + total_{uid};
|
| 457 |
-
document.getElementById('ic-prev-{uid}').style.opacity = idx_{uid} === 0 ? '0.35' : '1';
|
| 458 |
-
document.getElementById('ic-next-{uid}').style.opacity = idx_{uid} === total_{uid}-1 ? '0.35' : '1';
|
| 459 |
-
}}
|
| 460 |
-
|
| 461 |
-
window['icNav_{uid}'] = function(dir) {{
|
| 462 |
-
var next = idx_{uid} + dir;
|
| 463 |
-
if (next >= 0 && next < total_{uid}) {{
|
| 464 |
-
idx_{uid} = next;
|
| 465 |
-
render_{uid}();
|
| 466 |
-
}}
|
| 467 |
-
}};
|
| 468 |
-
|
| 469 |
-
render_{uid}();
|
| 470 |
-
}})();
|
| 471 |
-
</script>
|
| 472 |
-
"""
|
| 473 |
|
| 474 |
|
| 475 |
-
def
|
| 476 |
-
"""
|
| 477 |
-
|
| 478 |
-
|
| 479 |
-
|
| 480 |
-
|
| 481 |
-
|
| 482 |
-
|
| 483 |
-
|
| 484 |
-
|
| 485 |
-
|
| 486 |
-
|
| 487 |
-
|
| 488 |
-
|
| 489 |
-
|
| 490 |
-
|
| 491 |
-
|
| 492 |
-
all_cards_js_rows.append("[" + ", ".join(cards_for_scenario) + "]")
|
| 493 |
-
|
| 494 |
-
all_cards_js = "[" + ", ".join(all_cards_js_rows) + "]"
|
| 495 |
-
labels_js = "[" + ", ".join(f'"{OUTCOME_TITLES.get(o, o)}"' for o in outcomes) + "]"
|
| 496 |
-
scenario_labels_js = "[" + ", ".join(f'"{l}"' for l in labels) + "]"
|
| 497 |
-
scenario_colors_js = "[" + ", ".join(f'"{c}"' for c in colors) + "]"
|
| 498 |
-
|
| 499 |
-
n_scenarios = len(all_probs_list)
|
| 500 |
-
|
| 501 |
-
# Build the row placeholders
|
| 502 |
rows_html = ""
|
| 503 |
-
for
|
|
|
|
|
|
|
| 504 |
rows_html += (
|
| 505 |
-
f'<div style="display:flex;align-items:
|
| 506 |
-
f'<div
|
| 507 |
-
f'
|
| 508 |
-
f'background:{
|
| 509 |
-
f'{
|
| 510 |
-
f'<div id="cic-card-{uid}-{i}" style="flex:1;"></div>'
|
| 511 |
f'</div>'
|
| 512 |
)
|
| 513 |
|
|
@@ -519,58 +451,21 @@ def _build_comparison_icon_carousel_html(all_probs_list, labels, colors):
|
|
| 519 |
f'</div>'
|
| 520 |
)
|
| 521 |
|
| 522 |
-
return
|
| 523 |
-
<div
|
| 524 |
-
|
| 525 |
-
|
| 526 |
-
|
| 527 |
-
|
| 528 |
-
|
| 529 |
-
|
| 530 |
-
|
| 531 |
-
<div id="cic-outlabel-{uid}" style="font-size:14px;font-weight:700;color:#1565c0;"></div>
|
| 532 |
-
<div id="cic-counter-{uid}" style="font-size:11px;color:#888;margin-top:2px;"></div>
|
| 533 |
-
</div>
|
| 534 |
-
<button onclick="cicNav_{uid}(1)"
|
| 535 |
-
style="background:#1565c0;color:#fff;border:none;border-radius:6px;
|
| 536 |
-
padding:6px 18px;font-size:18px;cursor:pointer;font-weight:700;
|
| 537 |
-
line-height:1;user-select:none;"
|
| 538 |
-
aria-label="Next outcome">→</button>
|
| 539 |
-
</div>
|
| 540 |
-
{rows_html}
|
| 541 |
-
{footnote}
|
| 542 |
-
</div>
|
| 543 |
-
<script>
|
| 544 |
-
(function() {{
|
| 545 |
-
var allCards_{uid} = {all_cards_js};
|
| 546 |
-
var outLabels_{uid} = {labels_js};
|
| 547 |
-
var nScen_{uid} = {n_scenarios};
|
| 548 |
-
var total_{uid} = {len(outcomes)};
|
| 549 |
-
var idx_{uid} = 0;
|
| 550 |
-
|
| 551 |
-
function render_{uid}() {{
|
| 552 |
-
document.getElementById('cic-outlabel-{uid}').textContent = outLabels_{uid}[idx_{uid}];
|
| 553 |
-
document.getElementById('cic-counter-{uid}').textContent = (idx_{uid}+1) + ' / ' + total_{uid};
|
| 554 |
-
for (var s = 0; s < nScen_{uid}; s++) {{
|
| 555 |
-
var el = document.getElementById('cic-card-{uid}-' + s);
|
| 556 |
-
if (el) el.innerHTML = allCards_{uid}[s][idx_{uid}];
|
| 557 |
-
}}
|
| 558 |
-
}}
|
| 559 |
-
|
| 560 |
-
window['cicNav_{uid}'] = function(dir) {{
|
| 561 |
-
var next = idx_{uid} + dir;
|
| 562 |
-
if (next >= 0 && next < total_{uid}) {{
|
| 563 |
-
idx_{uid} = next;
|
| 564 |
-
render_{uid}();
|
| 565 |
-
}}
|
| 566 |
-
}};
|
| 567 |
-
|
| 568 |
-
render_{uid}();
|
| 569 |
-
}})();
|
| 570 |
-
</script>
|
| 571 |
-
"""
|
| 572 |
|
| 573 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 574 |
def _delta_color_html(delta, is_survival):
|
| 575 |
if abs(delta) < 0.0005:
|
| 576 |
color = "#888888"
|
|
@@ -685,26 +580,6 @@ def _build_violation_html(violations):
|
|
| 685 |
# SHAP CAROUSEL HELPERS
|
| 686 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 687 |
|
| 688 |
-
def _make_shap_nav_label(idx):
|
| 689 |
-
"""Return display label for SHAP plot at given index."""
|
| 690 |
-
o = SHAP_ORDER[idx]
|
| 691 |
-
return SHAP_LABELS.get(o, o)
|
| 692 |
-
|
| 693 |
-
|
| 694 |
-
def _shap_prev(current_idx, plots_store):
|
| 695 |
-
new_idx = max(0, current_idx - 1)
|
| 696 |
-
plot = plots_store[SHAP_ORDER[new_idx]] if plots_store else None
|
| 697 |
-
label = f"β {_make_shap_nav_label(new_idx)} ({new_idx+1}/{len(SHAP_ORDER)})"
|
| 698 |
-
return new_idx, plot, label
|
| 699 |
-
|
| 700 |
-
|
| 701 |
-
def _shap_next(current_idx, plots_store):
|
| 702 |
-
new_idx = min(len(SHAP_ORDER) - 1, current_idx + 1)
|
| 703 |
-
plot = plots_store[SHAP_ORDER[new_idx]] if plots_store else None
|
| 704 |
-
label = f"{_make_shap_nav_label(new_idx)} βΆ ({new_idx+1}/{len(SHAP_ORDER)})"
|
| 705 |
-
return new_idx, plot, label
|
| 706 |
-
|
| 707 |
-
|
| 708 |
def _shap_counter_html(idx):
|
| 709 |
labels = [SHAP_LABELS.get(o, o) for o in SHAP_ORDER]
|
| 710 |
items = " Β· ".join(
|
|
@@ -742,20 +617,22 @@ def predict_gradio(*values):
|
|
| 742 |
})
|
| 743 |
df = pd.DataFrame(rows)
|
| 744 |
|
| 745 |
-
shap_plots
|
| 746 |
-
icon_html = _build_icon_carousel_html(calibrated_probs, title_prefix="predict")
|
| 747 |
|
| 748 |
-
#
|
| 749 |
-
|
| 750 |
-
|
|
|
|
| 751 |
|
| 752 |
return (
|
| 753 |
df,
|
| 754 |
-
|
| 755 |
-
|
| 756 |
-
|
| 757 |
-
|
| 758 |
-
|
|
|
|
|
|
|
| 759 |
)
|
| 760 |
except Exception as e:
|
| 761 |
print(traceback.format_exc())
|
|
@@ -796,6 +673,13 @@ custom_css = """
|
|
| 796 |
padding: 8px 22px !important; border-radius: 6px !important;
|
| 797 |
}
|
| 798 |
.shap-nav-button:hover { background: linear-gradient(to right, #263238, #455a64) !important; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 799 |
.output-dataframe table td:first-child,
|
| 800 |
.output-dataframe table th:first-child {
|
| 801 |
white-space: normal !important; word-break: break-word !important; min-width: 240px !important;
|
|
@@ -880,18 +764,48 @@ with gr.Blocks(title="HCT Outcome Prediction Model", css=custom_css) as demo:
|
|
| 880 |
wrap=True,
|
| 881 |
)
|
| 882 |
|
| 883 |
-
# ββ Icon Array Carousel (Tab 1) βββββββββββββββββββ
|
| 884 |
gr.Markdown("---")
|
| 885 |
gr.Markdown("## Outcome Probability β Icon Arrays")
|
| 886 |
gr.Markdown("*Use the β β arrows to browse outcomes one at a time.*")
|
| 887 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 888 |
|
| 889 |
# ββ SHAP Carousel (Tab 1) βββββββββββββββββββββββββββββββββββββββββ
|
| 890 |
gr.Markdown("---")
|
| 891 |
gr.Markdown("## SHAP β Feature Importance")
|
| 892 |
gr.Markdown("*Use the β β buttons to browse SHAP plots one at a time.*")
|
| 893 |
|
| 894 |
-
# State: store all plots dict + current index
|
| 895 |
shap_plots_state = gr.State(None)
|
| 896 |
shap_idx_state = gr.State(0)
|
| 897 |
|
|
@@ -902,7 +816,6 @@ with gr.Blocks(title="HCT Outcome Prediction Model", css=custom_css) as demo:
|
|
| 902 |
|
| 903 |
shap_display = gr.Plot(label="SHAP Feature Importance")
|
| 904 |
|
| 905 |
-
# Nav callbacks
|
| 906 |
def _on_shap_prev(idx, plots):
|
| 907 |
new_idx = max(0, idx - 1)
|
| 908 |
plot = plots[SHAP_ORDER[new_idx]] if plots else None
|
|
@@ -929,7 +842,9 @@ with gr.Blocks(title="HCT Outcome Prediction Model", css=custom_css) as demo:
|
|
| 929 |
inputs=inputs_list,
|
| 930 |
outputs=[
|
| 931 |
output_table,
|
| 932 |
-
|
|
|
|
|
|
|
| 933 |
shap_plots_state,
|
| 934 |
shap_idx_state,
|
| 935 |
shap_display,
|
|
@@ -1175,12 +1090,45 @@ with gr.Blocks(title="HCT Outcome Prediction Model", css=custom_css) as demo:
|
|
| 1175 |
wi_table_html = gr.HTML()
|
| 1176 |
|
| 1177 |
gr.Markdown("---")
|
|
|
|
|
|
|
| 1178 |
with gr.Accordion("Outcome Icon Arrays β Baseline vs Scenarios", open=False):
|
| 1179 |
gr.Markdown(
|
| 1180 |
-
"*One row per scenario. Use the β β arrows to browse outcomes one at a time
|
| 1181 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1182 |
)
|
| 1183 |
-
wi_icon_html = gr.HTML()
|
| 1184 |
|
| 1185 |
gr.Markdown("---")
|
| 1186 |
|
|
@@ -1192,9 +1140,9 @@ with gr.Blocks(title="HCT Outcome Prediction Model", css=custom_css) as demo:
|
|
| 1192 |
wi_shap_base_idx = gr.State(0)
|
| 1193 |
|
| 1194 |
with gr.Row():
|
| 1195 |
-
wb_shap_prev
|
| 1196 |
wb_shap_crumb = gr.HTML(value="", scale=4)
|
| 1197 |
-
wb_shap_next
|
| 1198 |
wb_shap_plot = gr.Plot(label="Baseline β SHAP")
|
| 1199 |
|
| 1200 |
def _wb_prev(idx, plots):
|
|
@@ -1242,7 +1190,6 @@ with gr.Blocks(title="HCT Outcome Prediction Model", css=custom_css) as demo:
|
|
| 1242 |
wi_shap_scen_plots.append(s_plot)
|
| 1243 |
wi_shap_scen_crumbs.append(s_crumb)
|
| 1244 |
|
| 1245 |
-
# closure to capture correct store/idx
|
| 1246 |
def _make_prev(st, ix):
|
| 1247 |
def fn(idx, plots):
|
| 1248 |
new_idx = max(0, idx - 1)
|
|
@@ -1293,14 +1240,15 @@ with gr.Blocks(title="HCT Outcome Prediction Model", css=custom_css) as demo:
|
|
| 1293 |
for s in range(MAX_SCENARIOS)
|
| 1294 |
]
|
| 1295 |
|
| 1296 |
-
|
| 1297 |
-
|
| 1298 |
-
|
| 1299 |
-
|
| 1300 |
-
|
| 1301 |
-
|
| 1302 |
-
|
| 1303 |
-
|
|
|
|
| 1304 |
|
| 1305 |
try:
|
| 1306 |
base_dict = _values_to_dict(base_vals)
|
|
@@ -1315,15 +1263,9 @@ with gr.Blocks(title="HCT Outcome Prediction Model", css=custom_css) as demo:
|
|
| 1315 |
all_violations.extend(v)
|
| 1316 |
scenario_dicts_active.append(sd)
|
| 1317 |
|
| 1318 |
-
# Build the output list structure:
|
| 1319 |
-
# violation, table, icon,
|
| 1320 |
-
# base_store, base_idx, base_plot, base_crumb,
|
| 1321 |
-
# [per scenario: store, idx, plot, crumb] Γ MAX_SCENARIOS
|
| 1322 |
-
n_scen_outputs = 4 * MAX_SCENARIOS # store+idx+plot+crumb each
|
| 1323 |
-
|
| 1324 |
if all_violations:
|
| 1325 |
-
out = [_build_violation_html(all_violations), "", ""]
|
| 1326 |
-
out += [None, 0, None, ""]
|
| 1327 |
for _ in range(MAX_SCENARIOS):
|
| 1328 |
out += [None, 0, None, ""]
|
| 1329 |
return tuple(out)
|
|
@@ -1353,20 +1295,19 @@ with gr.Blocks(title="HCT Outcome Prediction Model", css=custom_css) as demo:
|
|
| 1353 |
base_probs, base_ci, scen_probs_list, scen_ci_list, scen_labels, scen_colors
|
| 1354 |
)
|
| 1355 |
|
| 1356 |
-
# Icon carousel:
|
| 1357 |
-
|
| 1358 |
-
|
| 1359 |
-
|
| 1360 |
-
|
| 1361 |
-
)
|
| 1362 |
|
| 1363 |
# SHAP β baseline
|
| 1364 |
base_shap_plots = create_all_shap_plots(base_dict, max_display=10)
|
| 1365 |
first_base_plot = base_shap_plots[SHAP_ORDER[0]]
|
| 1366 |
base_crumb = _shap_counter_html(0)
|
| 1367 |
|
| 1368 |
-
# SHAP β scenarios
|
| 1369 |
-
scen_shap_data = []
|
| 1370 |
for s in range(MAX_SCENARIOS):
|
| 1371 |
if s < n_scen:
|
| 1372 |
sp_plots = create_all_shap_plots(scenario_dicts_active[s], max_display=10)
|
|
@@ -1374,8 +1315,19 @@ with gr.Blocks(title="HCT Outcome Prediction Model", css=custom_css) as demo:
|
|
| 1374 |
else:
|
| 1375 |
scen_shap_data.append((None, None, ""))
|
| 1376 |
|
| 1377 |
-
out = [
|
| 1378 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1379 |
for s in range(MAX_SCENARIOS):
|
| 1380 |
store, plot0, crumb0 = scen_shap_data[s]
|
| 1381 |
out += [store, 0, plot0, crumb0]
|
|
@@ -1388,7 +1340,9 @@ with gr.Blocks(title="HCT Outcome Prediction Model", css=custom_css) as demo:
|
|
| 1388 |
|
| 1389 |
# Build output list
|
| 1390 |
all_run_outputs = (
|
| 1391 |
-
[wi_violation_html, wi_table_html,
|
|
|
|
|
|
|
| 1392 |
+ [wi_shap_base_store, wi_shap_base_idx, wb_shap_plot, wb_shap_crumb]
|
| 1393 |
+ [item for s in range(MAX_SCENARIOS)
|
| 1394 |
for item in (wi_shap_scen_stores[s], wi_shap_scen_idxs[s],
|
|
|
|
| 106 |
DISEASE_FEATURES = ["NACS2YR", "EXCHTFPR", "VOC2YPR", "VOCFRQPR", "SCATXRSN"]
|
| 107 |
ALL_FEATURES = PATIENT_FEATURES + DONOR_FEATURES + DISEASE_FEATURES
|
| 108 |
|
| 109 |
+
# Icon array outcomes in display order
|
| 110 |
ICON_OUTCOMES = ["DEAD", "GF", "AGVHD", "CGVHD", "VOCPSHI", "STROKEHI"]
|
| 111 |
OUTCOME_TITLES = {
|
| 112 |
"DEAD": "Death",
|
|
|
|
| 306 |
|
| 307 |
|
| 308 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 309 |
+
# ICON ARRAY HTML RENDERERS (pure Python, no JS)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 310 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 311 |
|
| 312 |
def _stick_figure_svg(color, size=16):
|
|
|
|
| 324 |
)
|
| 325 |
|
| 326 |
|
| 327 |
+
def _render_single_icon_card(probability, outcome, panel_label="", panel_color="#1565c0"):
|
| 328 |
+
"""Render a single icon array card as HTML (no JS). Used for Python-driven carousel."""
|
| 329 |
title = OUTCOME_TITLES.get(outcome, OUTCOME_DESCRIPTIONS.get(outcome, outcome))
|
| 330 |
n_event = round(probability * 100)
|
| 331 |
n_no_event = 100 - n_event
|
|
|
|
| 362 |
|
| 363 |
return (
|
| 364 |
f'<div style="background:#fff;border:1px solid #e0e0e0;border-radius:7px;'
|
| 365 |
+
f'padding:10px 8px;text-align:center;font-family:\'Segoe UI\',Arial,sans-serif;'
|
| 366 |
+
f'box-shadow:0 2px 4px rgba(0,0,0,0.06);box-sizing:border-box;max-width:340px;'
|
| 367 |
+
f'margin:0 auto;display:flex;flex-direction:column;align-items:center;">'
|
| 368 |
f'{badge}'
|
| 369 |
+
f'<div style="min-height:30px;display:flex;align-items:center;justify-content:center;'
|
| 370 |
+
f'font-size:13px;font-weight:700;color:#222;line-height:1.3;margin-bottom:4px;">{title}</div>'
|
| 371 |
+
f'<div style="font-size:22px;font-weight:800;color:{EVENT_COLOR};'
|
| 372 |
+
f'line-height:1;margin-bottom:6px;">{pct_str}</div>'
|
| 373 |
+
f'<div style="margin-bottom:4px;">{grid_html}</div>'
|
| 374 |
f'<div>{legend}</div>'
|
| 375 |
f'</div>'
|
| 376 |
)
|
| 377 |
|
| 378 |
|
| 379 |
+
def _render_icon_carousel_page(probs_dict, idx):
|
| 380 |
+
"""Render the full carousel HTML for a given outcome index (no JS)."""
|
| 381 |
+
outcome = ICON_OUTCOMES[idx]
|
| 382 |
+
total = len(ICON_OUTCOMES)
|
| 383 |
+
card_html = _render_single_icon_card(probs_dict.get(outcome, 0.0), outcome)
|
| 384 |
+
label = OUTCOME_TITLES.get(outcome, outcome)
|
| 385 |
+
|
| 386 |
+
# Breadcrumb dots
|
| 387 |
+
dots = ""
|
| 388 |
+
for i, o in enumerate(ICON_OUTCOMES):
|
| 389 |
+
active = i == idx
|
| 390 |
+
dots += (
|
| 391 |
+
f'<span style="display:inline-block;width:{"10" if active else "7"}px;'
|
| 392 |
+
f'height:{"10" if active else "7"}px;border-radius:50%;'
|
| 393 |
+
f'background:{"#1565c0" if active else "#ccc"};'
|
| 394 |
+
f'margin:0 3px;vertical-align:middle;"></span>'
|
| 395 |
+
)
|
|
|
|
| 396 |
|
| 397 |
footnote = (
|
| 398 |
+
f'<div style="font-size:10px;color:#888;text-align:center;margin-top:10px;">'
|
| 399 |
f'Each figure = 1 patient out of 100. '
|
| 400 |
f'<span style="color:{EVENT_COLOR};font-weight:600;">■ Red = Event</span> '
|
| 401 |
f'<span style="color:{NO_EVENT_COLOR};font-weight:600;">■ Green = No Event</span>'
|
| 402 |
f'</div>'
|
| 403 |
)
|
| 404 |
|
| 405 |
+
return (
|
| 406 |
+
f'<div style="font-family:\'Segoe UI\',Arial,sans-serif;padding:4px 0;text-align:center;">'
|
| 407 |
+
f'<div style="font-size:15px;font-weight:700;color:#1565c0;margin-bottom:2px;">{label}</div>'
|
| 408 |
+
f'<div style="font-size:11px;color:#888;margin-bottom:10px;">{idx+1} / {total}</div>'
|
| 409 |
+
f'<div style="margin-bottom:2px;">{dots}</div>'
|
| 410 |
+
f'<div style="margin-top:10px;">{card_html}</div>'
|
| 411 |
+
f'{footnote}'
|
| 412 |
+
f'</div>'
|
| 413 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 414 |
|
| 415 |
|
| 416 |
+
def _render_comparison_icon_page(all_probs_list, labels, colors, idx):
|
| 417 |
+
"""Render comparison icon carousel page for a given outcome index (no JS)."""
|
| 418 |
+
outcome = ICON_OUTCOMES[idx]
|
| 419 |
+
total = len(ICON_OUTCOMES)
|
| 420 |
+
out_label = OUTCOME_TITLES.get(outcome, outcome)
|
| 421 |
+
|
| 422 |
+
# Dots breadcrumb
|
| 423 |
+
dots = ""
|
| 424 |
+
for i in range(total):
|
| 425 |
+
active = i == idx
|
| 426 |
+
dots += (
|
| 427 |
+
f'<span style="display:inline-block;width:{"10" if active else "7"}px;'
|
| 428 |
+
f'height:{"10" if active else "7"}px;border-radius:50%;'
|
| 429 |
+
f'background:{"#1565c0" if active else "#ccc"};'
|
| 430 |
+
f'margin:0 3px;vertical-align:middle;"></span>'
|
| 431 |
+
)
|
| 432 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 433 |
rows_html = ""
|
| 434 |
+
for probs, label, color in zip(all_probs_list, labels, colors):
|
| 435 |
+
prob = probs.get(outcome, 0.0)
|
| 436 |
+
card = _render_single_icon_card(prob, outcome, label, color)
|
| 437 |
rows_html += (
|
| 438 |
+
f'<div style="display:flex;align-items:flex-start;gap:10px;margin-bottom:12px;">'
|
| 439 |
+
f'<div style="width:80px;flex-shrink:0;font-size:9px;font-weight:700;'
|
| 440 |
+
f'text-align:center;border-radius:4px;padding:4px 6px;color:#fff;'
|
| 441 |
+
f'background:{color};white-space:normal;word-break:break-word;">{label}</div>'
|
| 442 |
+
f'<div style="flex:1;">{card}</div>'
|
|
|
|
| 443 |
f'</div>'
|
| 444 |
)
|
| 445 |
|
|
|
|
| 451 |
f'</div>'
|
| 452 |
)
|
| 453 |
|
| 454 |
+
return (
|
| 455 |
+
f'<div style="font-family:\'Segoe UI\',Arial,sans-serif;padding:4px 0;text-align:center;">'
|
| 456 |
+
f'<div style="font-size:15px;font-weight:700;color:#1565c0;margin-bottom:2px;">{out_label}</div>'
|
| 457 |
+
f'<div style="font-size:11px;color:#888;margin-bottom:6px;">{idx+1} / {total}</div>'
|
| 458 |
+
f'<div style="margin-bottom:10px;">{dots}</div>'
|
| 459 |
+
f'{rows_html}'
|
| 460 |
+
f'{footnote}'
|
| 461 |
+
f'</div>'
|
| 462 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 463 |
|
| 464 |
|
| 465 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 466 |
+
# OTHER HTML RENDERERS
|
| 467 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 468 |
+
|
| 469 |
def _delta_color_html(delta, is_survival):
|
| 470 |
if abs(delta) < 0.0005:
|
| 471 |
color = "#888888"
|
|
|
|
| 580 |
# SHAP CAROUSEL HELPERS
|
| 581 |
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 582 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 583 |
def _shap_counter_html(idx):
|
| 584 |
labels = [SHAP_LABELS.get(o, o) for o in SHAP_ORDER]
|
| 585 |
items = " Β· ".join(
|
|
|
|
| 617 |
})
|
| 618 |
df = pd.DataFrame(rows)
|
| 619 |
|
| 620 |
+
shap_plots = create_all_shap_plots(user_vals, max_display=10)
|
|
|
|
| 621 |
|
| 622 |
+
# Icon carousel: start at index 0, store probs in State
|
| 623 |
+
icon_html = _render_icon_carousel_page(calibrated_probs, 0)
|
| 624 |
+
first_shap = shap_plots[SHAP_ORDER[0]]
|
| 625 |
+
shap_crumb = _shap_counter_html(0)
|
| 626 |
|
| 627 |
return (
|
| 628 |
df,
|
| 629 |
+
calibrated_probs, # stored in State for carousel navigation
|
| 630 |
+
0, # icon carousel index State
|
| 631 |
+
icon_html, # displayed icon card
|
| 632 |
+
shap_plots, # stored in State
|
| 633 |
+
0, # shap index State
|
| 634 |
+
first_shap, # displayed plot
|
| 635 |
+
shap_crumb, # breadcrumb HTML
|
| 636 |
)
|
| 637 |
except Exception as e:
|
| 638 |
print(traceback.format_exc())
|
|
|
|
| 673 |
padding: 8px 22px !important; border-radius: 6px !important;
|
| 674 |
}
|
| 675 |
.shap-nav-button:hover { background: linear-gradient(to right, #263238, #455a64) !important; }
|
| 676 |
+
.icon-nav-button {
|
| 677 |
+
background: linear-gradient(to right, #1565c0, #1e88e5) !important;
|
| 678 |
+
border: none !important; color: white !important;
|
| 679 |
+
font-weight: bold !important; font-size: 18px !important;
|
| 680 |
+
padding: 8px 22px !important; border-radius: 6px !important;
|
| 681 |
+
}
|
| 682 |
+
.icon-nav-button:hover { background: linear-gradient(to right, #0d47a1, #1565c0) !important; }
|
| 683 |
.output-dataframe table td:first-child,
|
| 684 |
.output-dataframe table th:first-child {
|
| 685 |
white-space: normal !important; word-break: break-word !important; min-width: 240px !important;
|
|
|
|
| 764 |
wrap=True,
|
| 765 |
)
|
| 766 |
|
| 767 |
+
# ββ Icon Array Carousel (Tab 1) β Python-driven βββββββββββββββββββ
|
| 768 |
gr.Markdown("---")
|
| 769 |
gr.Markdown("## Outcome Probability β Icon Arrays")
|
| 770 |
gr.Markdown("*Use the β β arrows to browse outcomes one at a time.*")
|
| 771 |
+
|
| 772 |
+
# State: store probs dict + current index
|
| 773 |
+
icon_probs_state = gr.State(None)
|
| 774 |
+
icon_idx_state = gr.State(0)
|
| 775 |
+
|
| 776 |
+
with gr.Row():
|
| 777 |
+
icon_prev_btn = gr.Button("β", elem_classes="icon-nav-button", size="sm", scale=0)
|
| 778 |
+
icon_display = gr.HTML(scale=4)
|
| 779 |
+
icon_next_btn = gr.Button("β", elem_classes="icon-nav-button", size="sm", scale=0)
|
| 780 |
+
|
| 781 |
+
def _on_icon_prev(idx, probs):
|
| 782 |
+
if probs is None:
|
| 783 |
+
return idx, ""
|
| 784 |
+
new_idx = max(0, idx - 1)
|
| 785 |
+
return new_idx, _render_icon_carousel_page(probs, new_idx)
|
| 786 |
+
|
| 787 |
+
def _on_icon_next(idx, probs):
|
| 788 |
+
if probs is None:
|
| 789 |
+
return idx, ""
|
| 790 |
+
new_idx = min(len(ICON_OUTCOMES) - 1, idx + 1)
|
| 791 |
+
return new_idx, _render_icon_carousel_page(probs, new_idx)
|
| 792 |
+
|
| 793 |
+
icon_prev_btn.click(
|
| 794 |
+
fn=_on_icon_prev,
|
| 795 |
+
inputs=[icon_idx_state, icon_probs_state],
|
| 796 |
+
outputs=[icon_idx_state, icon_display],
|
| 797 |
+
)
|
| 798 |
+
icon_next_btn.click(
|
| 799 |
+
fn=_on_icon_next,
|
| 800 |
+
inputs=[icon_idx_state, icon_probs_state],
|
| 801 |
+
outputs=[icon_idx_state, icon_display],
|
| 802 |
+
)
|
| 803 |
|
| 804 |
# ββ SHAP Carousel (Tab 1) βββββββββββββββββββββββββββββββββββββββββ
|
| 805 |
gr.Markdown("---")
|
| 806 |
gr.Markdown("## SHAP β Feature Importance")
|
| 807 |
gr.Markdown("*Use the β β buttons to browse SHAP plots one at a time.*")
|
| 808 |
|
|
|
|
| 809 |
shap_plots_state = gr.State(None)
|
| 810 |
shap_idx_state = gr.State(0)
|
| 811 |
|
|
|
|
| 816 |
|
| 817 |
shap_display = gr.Plot(label="SHAP Feature Importance")
|
| 818 |
|
|
|
|
| 819 |
def _on_shap_prev(idx, plots):
|
| 820 |
new_idx = max(0, idx - 1)
|
| 821 |
plot = plots[SHAP_ORDER[new_idx]] if plots else None
|
|
|
|
| 842 |
inputs=inputs_list,
|
| 843 |
outputs=[
|
| 844 |
output_table,
|
| 845 |
+
icon_probs_state, # β store probs dict
|
| 846 |
+
icon_idx_state, # β reset to 0
|
| 847 |
+
icon_display, # β show first card
|
| 848 |
shap_plots_state,
|
| 849 |
shap_idx_state,
|
| 850 |
shap_display,
|
|
|
|
| 1090 |
wi_table_html = gr.HTML()
|
| 1091 |
|
| 1092 |
gr.Markdown("---")
|
| 1093 |
+
|
| 1094 |
+
# ββ Icon Arrays β comparison, Python-driven carousel βββββββββββββ
|
| 1095 |
with gr.Accordion("Outcome Icon Arrays β Baseline vs Scenarios", open=False):
|
| 1096 |
gr.Markdown(
|
| 1097 |
+
"*One row per scenario. Use the β β arrows to browse outcomes one at a time.*"
|
| 1098 |
+
)
|
| 1099 |
+
|
| 1100 |
+
wi_cmp_probs_state = gr.State(None) # list of dicts
|
| 1101 |
+
wi_cmp_labels_state = gr.State(None) # list of labels
|
| 1102 |
+
wi_cmp_colors_state = gr.State(None) # list of colors
|
| 1103 |
+
wi_cmp_idx_state = gr.State(0)
|
| 1104 |
+
|
| 1105 |
+
with gr.Row():
|
| 1106 |
+
wi_cmp_prev_btn = gr.Button("β", elem_classes="icon-nav-button", size="sm", scale=0)
|
| 1107 |
+
wi_icon_html = gr.HTML(scale=4)
|
| 1108 |
+
wi_cmp_next_btn = gr.Button("β", elem_classes="icon-nav-button", size="sm", scale=0)
|
| 1109 |
+
|
| 1110 |
+
def _on_cmp_prev(idx, probs_list, labels, colors):
|
| 1111 |
+
if probs_list is None:
|
| 1112 |
+
return idx, ""
|
| 1113 |
+
new_idx = max(0, idx - 1)
|
| 1114 |
+
return new_idx, _render_comparison_icon_page(probs_list, labels, colors, new_idx)
|
| 1115 |
+
|
| 1116 |
+
def _on_cmp_next(idx, probs_list, labels, colors):
|
| 1117 |
+
if probs_list is None:
|
| 1118 |
+
return idx, ""
|
| 1119 |
+
new_idx = min(len(ICON_OUTCOMES) - 1, idx + 1)
|
| 1120 |
+
return new_idx, _render_comparison_icon_page(probs_list, labels, colors, new_idx)
|
| 1121 |
+
|
| 1122 |
+
wi_cmp_prev_btn.click(
|
| 1123 |
+
fn=_on_cmp_prev,
|
| 1124 |
+
inputs=[wi_cmp_idx_state, wi_cmp_probs_state, wi_cmp_labels_state, wi_cmp_colors_state],
|
| 1125 |
+
outputs=[wi_cmp_idx_state, wi_icon_html],
|
| 1126 |
+
)
|
| 1127 |
+
wi_cmp_next_btn.click(
|
| 1128 |
+
fn=_on_cmp_next,
|
| 1129 |
+
inputs=[wi_cmp_idx_state, wi_cmp_probs_state, wi_cmp_labels_state, wi_cmp_colors_state],
|
| 1130 |
+
outputs=[wi_cmp_idx_state, wi_icon_html],
|
| 1131 |
)
|
|
|
|
| 1132 |
|
| 1133 |
gr.Markdown("---")
|
| 1134 |
|
|
|
|
| 1140 |
wi_shap_base_idx = gr.State(0)
|
| 1141 |
|
| 1142 |
with gr.Row():
|
| 1143 |
+
wb_shap_prev = gr.Button("β", elem_classes="shap-nav-button", size="sm", scale=0)
|
| 1144 |
wb_shap_crumb = gr.HTML(value="", scale=4)
|
| 1145 |
+
wb_shap_next = gr.Button("β", elem_classes="shap-nav-button", size="sm", scale=0)
|
| 1146 |
wb_shap_plot = gr.Plot(label="Baseline β SHAP")
|
| 1147 |
|
| 1148 |
def _wb_prev(idx, plots):
|
|
|
|
| 1190 |
wi_shap_scen_plots.append(s_plot)
|
| 1191 |
wi_shap_scen_crumbs.append(s_crumb)
|
| 1192 |
|
|
|
|
| 1193 |
def _make_prev(st, ix):
|
| 1194 |
def fn(idx, plots):
|
| 1195 |
new_idx = max(0, idx - 1)
|
|
|
|
| 1240 |
for s in range(MAX_SCENARIOS)
|
| 1241 |
]
|
| 1242 |
|
| 1243 |
+
def _empty_outputs():
|
| 1244 |
+
# violation, table, cmp_probs, cmp_labels, cmp_colors, cmp_idx, icon_html
|
| 1245 |
+
# base_store, base_idx, base_plot, base_crumb
|
| 1246 |
+
# per scenario: store, idx, plot, crumb
|
| 1247 |
+
out = ["", "", None, None, None, 0, ""]
|
| 1248 |
+
out += [None, 0, None, ""]
|
| 1249 |
+
for _ in range(MAX_SCENARIOS):
|
| 1250 |
+
out += [None, 0, None, ""]
|
| 1251 |
+
return tuple(out)
|
| 1252 |
|
| 1253 |
try:
|
| 1254 |
base_dict = _values_to_dict(base_vals)
|
|
|
|
| 1263 |
all_violations.extend(v)
|
| 1264 |
scenario_dicts_active.append(sd)
|
| 1265 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1266 |
if all_violations:
|
| 1267 |
+
out = [_build_violation_html(all_violations), "", None, None, None, 0, ""]
|
| 1268 |
+
out += [None, 0, None, ""]
|
| 1269 |
for _ in range(MAX_SCENARIOS):
|
| 1270 |
out += [None, 0, None, ""]
|
| 1271 |
return tuple(out)
|
|
|
|
| 1295 |
base_probs, base_ci, scen_probs_list, scen_ci_list, scen_labels, scen_colors
|
| 1296 |
)
|
| 1297 |
|
| 1298 |
+
# Icon carousel: store all probs + labels + colors in State
|
| 1299 |
+
all_probs_list = [base_probs] + scen_probs_list
|
| 1300 |
+
all_labels = ["Baseline"] + scen_labels
|
| 1301 |
+
all_colors = ["#1565c0"] + scen_colors
|
| 1302 |
+
icon_html_first = _render_comparison_icon_page(all_probs_list, all_labels, all_colors, 0)
|
|
|
|
| 1303 |
|
| 1304 |
# SHAP β baseline
|
| 1305 |
base_shap_plots = create_all_shap_plots(base_dict, max_display=10)
|
| 1306 |
first_base_plot = base_shap_plots[SHAP_ORDER[0]]
|
| 1307 |
base_crumb = _shap_counter_html(0)
|
| 1308 |
|
| 1309 |
+
# SHAP β scenarios
|
| 1310 |
+
scen_shap_data = []
|
| 1311 |
for s in range(MAX_SCENARIOS):
|
| 1312 |
if s < n_scen:
|
| 1313 |
sp_plots = create_all_shap_plots(scenario_dicts_active[s], max_display=10)
|
|
|
|
| 1315 |
else:
|
| 1316 |
scen_shap_data.append((None, None, ""))
|
| 1317 |
|
| 1318 |
+
out = [
|
| 1319 |
+
"", # violation html
|
| 1320 |
+
table_html, # table
|
| 1321 |
+
all_probs_list, # cmp_probs state
|
| 1322 |
+
all_labels, # cmp_labels state
|
| 1323 |
+
all_colors, # cmp_colors state
|
| 1324 |
+
0, # cmp_idx state
|
| 1325 |
+
icon_html_first, # icon display
|
| 1326 |
+
base_shap_plots, # base shap store
|
| 1327 |
+
0, # base shap idx
|
| 1328 |
+
first_base_plot, # base shap plot
|
| 1329 |
+
base_crumb, # base shap crumb
|
| 1330 |
+
]
|
| 1331 |
for s in range(MAX_SCENARIOS):
|
| 1332 |
store, plot0, crumb0 = scen_shap_data[s]
|
| 1333 |
out += [store, 0, plot0, crumb0]
|
|
|
|
| 1340 |
|
| 1341 |
# Build output list
|
| 1342 |
all_run_outputs = (
|
| 1343 |
+
[wi_violation_html, wi_table_html,
|
| 1344 |
+
wi_cmp_probs_state, wi_cmp_labels_state, wi_cmp_colors_state,
|
| 1345 |
+
wi_cmp_idx_state, wi_icon_html]
|
| 1346 |
+ [wi_shap_base_store, wi_shap_base_idx, wb_shap_plot, wb_shap_crumb]
|
| 1347 |
+ [item for s in range(MAX_SCENARIOS)
|
| 1348 |
for item in (wi_shap_scen_stores[s], wi_shap_scen_idxs[s],
|