Update pages/02_Workflow_UI.py
Browse files- pages/02_Workflow_UI.py +36 -91
pages/02_Workflow_UI.py
CHANGED
|
@@ -3,20 +3,28 @@
|
|
| 3 |
# Phase 3 β Tabbed Workflow UI (PCP Referral β Specialist Review)
|
| 4 |
# Fully integrated with V2 backend: store, billing, modal_templates, config,
|
| 5 |
# explainability, guideline_annotator, ai_core.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
# -----------------------------------------------------------------------------
|
| 7 |
|
| 8 |
from __future__ import annotations
|
| 9 |
|
| 10 |
import os
|
|
|
|
|
|
|
| 11 |
from pathlib import Path
|
| 12 |
-
from typing import Any, Dict, List,
|
| 13 |
|
| 14 |
import streamlit as st
|
| 15 |
|
| 16 |
from src import store, billing, modal_templates, config
|
| 17 |
from src.styles import inject_base_css
|
| 18 |
from src.paths import faiss_index_dir, initialize_environment
|
| 19 |
-
from src.prompt_builder import build_referral_summary
|
| 20 |
from src.explainability import chips_from_text, ensure_chip_schema
|
| 21 |
from src.guideline_annotator import annotate_guidelines
|
| 22 |
from src.ai_core import generate_soap_draft
|
|
@@ -28,7 +36,7 @@ inject_base_css()
|
|
| 28 |
initialize_environment()
|
| 29 |
|
| 30 |
st.title("Step 2 β Workflow")
|
| 31 |
-
st.caption("PCP Referral β Specialist Review. Demo only β de
|
| 32 |
|
| 33 |
# --------------------------- Helpers ------------------------------------
|
| 34 |
|
|
@@ -70,9 +78,7 @@ def _as_text(x: Any) -> str:
|
|
| 70 |
if isinstance(x, str):
|
| 71 |
return x
|
| 72 |
if isinstance(x, list):
|
| 73 |
-
|
| 74 |
-
items = [str(s).strip() for s in x if str(s).strip()]
|
| 75 |
-
return "\n".join(f"- {s}" for s in items)
|
| 76 |
return str(x)
|
| 77 |
|
| 78 |
def _soap_is_empty(soap: Dict[str, str]) -> bool:
|
|
@@ -135,7 +141,9 @@ def _note_markdown(case: Dict[str, Any], summary: str, soap: Dict[str, str], end
|
|
| 135 |
)
|
| 136 |
cites = ""
|
| 137 |
if endnotes:
|
| 138 |
-
cites = "\n## Guideline Citations\n" + "\n".join(
|
|
|
|
|
|
|
| 139 |
return header + body + cites + "\n"
|
| 140 |
|
| 141 |
def _seed_once() -> None:
|
|
@@ -151,8 +159,7 @@ def _case_options() -> List[Dict[str, Any]]:
|
|
| 151 |
return items
|
| 152 |
|
| 153 |
def _get_case(case_id: str) -> Dict[str, Any]:
|
| 154 |
-
|
| 155 |
-
return case
|
| 156 |
|
| 157 |
# --------------------------- Top banners --------------------------------
|
| 158 |
|
|
@@ -180,9 +187,8 @@ if not items:
|
|
| 180 |
st.error("No cases available.")
|
| 181 |
st.stop()
|
| 182 |
|
| 183 |
-
# ---
|
| 184 |
if st.button("β New Case", use_container_width=True):
|
| 185 |
-
# Generate a new unique case ID
|
| 186 |
new_id = store.new_case_id()
|
| 187 |
store.create_case({
|
| 188 |
"case_id": new_id,
|
|
@@ -196,28 +202,18 @@ if st.button("β New Case", use_container_width=True):
|
|
| 196 |
"labs": "",
|
| 197 |
"consent_obtained": False,
|
| 198 |
},
|
| 199 |
-
"soap_draft": {
|
| 200 |
-
"subjective": "",
|
| 201 |
-
"objective": "",
|
| 202 |
-
"assessment": "",
|
| 203 |
-
"plan": "",
|
| 204 |
-
},
|
| 205 |
"billing": {"minutes": 5, "spoke": False, "cpt_code": None, "attested": False},
|
| 206 |
"explainability": {"manually_modified": False},
|
| 207 |
})
|
| 208 |
st.success(f"Created new case {new_id}. Refreshing listβ¦")
|
| 209 |
st.rerun()
|
| 210 |
|
| 211 |
-
# --- Demo
|
| 212 |
-
import shutil
|
| 213 |
-
from src import config
|
| 214 |
-
|
| 215 |
if st.button("ποΈ Reset Demo (clear all cases)", use_container_width=True):
|
| 216 |
-
cases_dir = config.paths_dict()["cases_dir"]
|
| 217 |
try:
|
| 218 |
-
shutil.rmtree(cases_dir)
|
| 219 |
st.success("β
All demo cases cleared. The 4 default draft cases will be recreated on reload.")
|
| 220 |
-
# Reset seeding flag so defaults are reloaded automatically
|
| 221 |
st.session_state["_seeded"] = False
|
| 222 |
st.rerun()
|
| 223 |
except Exception as e:
|
|
@@ -238,14 +234,11 @@ st.session_state["_current_case_id"] = selected
|
|
| 238 |
case = _get_case(selected)
|
| 239 |
_case_summary_header(case)
|
| 240 |
|
| 241 |
-
|
| 242 |
# --------------------------- Tabs ----------------------------------------
|
| 243 |
|
| 244 |
tab_pcp, tab_spec = st.tabs(["PCP Referral", "Specialist Review"])
|
| 245 |
|
| 246 |
# ===== PCP Referral Tab =====
|
| 247 |
-
# Inside the PCP Referral tab (replace your existing block):
|
| 248 |
-
|
| 249 |
with tab_pcp:
|
| 250 |
is_draft = case.get("status") == "draft"
|
| 251 |
p = case.get("patient", {}) or {}
|
|
@@ -259,9 +252,8 @@ with tab_pcp:
|
|
| 259 |
"Clinical Summary",
|
| 260 |
value=c.get("summary", "") or c.get("history", ""),
|
| 261 |
height=160,
|
| 262 |
-
placeholder=("
|
| 263 |
-
"
|
| 264 |
-
"BNP 230; here for med optimization."),
|
| 265 |
disabled=not is_draft,
|
| 266 |
key=f"summary_{selected}",
|
| 267 |
)
|
|
@@ -301,7 +293,6 @@ with tab_pcp:
|
|
| 301 |
else:
|
| 302 |
st.info("Referral is not editable (status is In Review or Completed).")
|
| 303 |
|
| 304 |
-
|
| 305 |
# ===== Specialist Review Tab =====
|
| 306 |
with tab_spec:
|
| 307 |
status = case.get("status")
|
|
@@ -326,7 +317,6 @@ with tab_spec:
|
|
| 326 |
|
| 327 |
st.markdown("**CPT Autosuggest**")
|
| 328 |
elig_codes = [s.code for s in suggestions if s.eligible]
|
| 329 |
-
# Show suggestion cards
|
| 330 |
for s in suggestions:
|
| 331 |
with st.container():
|
| 332 |
status_icon = "β
" if s.eligible else "β οΈ"
|
|
@@ -341,23 +331,18 @@ with tab_spec:
|
|
| 341 |
chosen = None
|
| 342 |
|
| 343 |
attested_default = bool((case.get("billing", {}) or {}).get("attested", False))
|
| 344 |
-
attested = st.checkbox("I attest that the consult was performed and documented per interprofessional e
|
| 345 |
|
| 346 |
can_finalize = (not read_only) and (chosen is not None) and attested and (minutes >= 5)
|
| 347 |
-
|
| 348 |
-
# Mirror finalize CTA on right
|
| 349 |
finalize_right = st.button("Finalize Consult Note", type="primary", disabled=not can_finalize, use_container_width=True, key=f"finalize_r_{selected}")
|
| 350 |
|
| 351 |
# ---------- LEFT: Referral & SOAP ----------
|
| 352 |
with left:
|
| 353 |
st.subheader("Referral Summary")
|
| 354 |
summary = build_referral_summary(case)
|
| 355 |
-
|
| 356 |
-
# --- Debug expander for referral summary (shows text sent to MedGemma) ---
|
| 357 |
with st.expander("π Referral Summary (debug view)", expanded=False):
|
| 358 |
st.code(summary, language="markdown")
|
| 359 |
|
| 360 |
-
|
| 361 |
st.subheader("SOAP Draft")
|
| 362 |
soap = case.get("soap_draft", {}) or {"subjective": "", "objective": "", "assessment": "", "plan": ""}
|
| 363 |
gen_needed = (status == "submitted") and _soap_is_empty(soap)
|
|
@@ -365,7 +350,6 @@ with tab_spec:
|
|
| 365 |
if gen_needed and not read_only:
|
| 366 |
if st.button("Generate SOAP Draft (LLM)", key=f"gen_{selected}"):
|
| 367 |
try:
|
| 368 |
-
# Use normalized intake (patient + consult)
|
| 369 |
result = generate_soap_draft(
|
| 370 |
intake=case,
|
| 371 |
mode="mapping",
|
|
@@ -374,47 +358,28 @@ with tab_spec:
|
|
| 374 |
top_p=0.95,
|
| 375 |
)
|
| 376 |
s = result.get("soap", {}) or {}
|
| 377 |
-
|
| 378 |
-
# --- Normalize and flatten list fields ---
|
| 379 |
subj = _as_text(s.get("subjective", ""))
|
| 380 |
obj = _as_text(s.get("objective", ""))
|
| 381 |
|
| 382 |
assess_raw = s.get("assessment", "")
|
| 383 |
plan_raw = s.get("plan", "")
|
| 384 |
-
|
| 385 |
-
# Convert lists into newline-separated strings
|
| 386 |
assess = "\n".join(assess_raw) if isinstance(assess_raw, list) else str(assess_raw)
|
| 387 |
plan = "\n".join(plan_raw) if isinstance(plan_raw, list) else str(plan_raw)
|
| 388 |
|
| 389 |
-
except Exception as e:
|
| 390 |
-
subj = summary
|
| 391 |
-
obj = ""
|
| 392 |
-
assess = "β"
|
| 393 |
-
plan = "β"
|
| 394 |
st.warning(f"LLM generation unavailable; seeded a minimal draft. ({type(e).__name__})")
|
| 395 |
result = {"error": str(e)}
|
| 396 |
|
| 397 |
-
# --- Debug expander for raw MedGemma output (full JSON response) ---
|
| 398 |
with st.expander("π§ MedGemma raw response (debug view)", expanded=False):
|
| 399 |
-
import json
|
| 400 |
st.code(json.dumps(result, indent=2), language="json")
|
| 401 |
|
| 402 |
-
# Persist generated draft and mark unmodified so chips/guidelines show
|
| 403 |
store.update_case(selected, {
|
| 404 |
-
"soap_draft": {
|
| 405 |
-
"subjective": subj.strip(),
|
| 406 |
-
"objective": obj.strip(),
|
| 407 |
-
"assessment": assess.strip(),
|
| 408 |
-
"plan": plan.strip(),
|
| 409 |
-
},
|
| 410 |
"explainability": {"manually_modified": False},
|
| 411 |
})
|
| 412 |
-
|
| 413 |
-
|
| 414 |
-
case = _get_case(selected)
|
| 415 |
-
soap = case.get("soap_draft", {})
|
| 416 |
-
st.success("SOAP draft generated and saved. You can review it below.")
|
| 417 |
-
|
| 418 |
|
| 419 |
c1, c2 = st.columns(2)
|
| 420 |
with c1:
|
|
@@ -437,21 +402,18 @@ with tab_spec:
|
|
| 437 |
"soap_draft": {"subjective": subj_new, "objective": obj_new, "assessment": assess_new, "plan": plan_new},
|
| 438 |
"explainability": {"manually_modified": True},
|
| 439 |
})
|
| 440 |
-
#
|
| 441 |
-
case = _get_case(selected)
|
| 442 |
-
soap = case.get("soap_draft", soap)
|
| 443 |
|
| 444 |
-
# Post
|
| 445 |
exp = (case.get("explainability", {}) or {})
|
| 446 |
manually_modified = bool(exp.get("manually_modified", False))
|
| 447 |
-
|
| 448 |
if read_only:
|
| 449 |
manually_modified = False # allow showing in completed
|
| 450 |
|
| 451 |
if not manually_modified and (soap.get("plan") or "").strip():
|
| 452 |
-
with st.expander("Explainability tokens (post
|
| 453 |
-
_render_token_chips(
|
| 454 |
-
with st.expander("Guideline citations (post
|
| 455 |
g = _run_guidelines_cached(selected, soap.get("plan",""))
|
| 456 |
warn = (g.get("warning") or "").strip()
|
| 457 |
if warn:
|
|
@@ -468,13 +430,12 @@ with tab_spec:
|
|
| 468 |
st.caption("_No references found._")
|
| 469 |
else:
|
| 470 |
if not read_only and (status == "submitted"):
|
| 471 |
-
if st.button("Re
|
| 472 |
-
# Re-run and mark unmodified so blocks show
|
| 473 |
_run_guidelines_cached(selected, plan_new)
|
| 474 |
store.update_case(selected, {"explainability": {"manually_modified": False}})
|
| 475 |
st.rerun()
|
| 476 |
else:
|
| 477 |
-
st.caption("Guideline citations hidden after edits. Click **Re
|
| 478 |
|
| 479 |
# Mirror finalize CTA on left
|
| 480 |
finalize_left = st.button("Finalize Consult Note", type="primary", disabled=not can_finalize, key=f"finalize_l_{selected}")
|
|
@@ -506,8 +467,7 @@ with tab_spec:
|
|
| 506 |
rate = float(getattr(pick, "rate", 0.0)) if pick else 0.0
|
| 507 |
claim = billing.build_837_claim(case, code=str(chosen), rate=rate, minutes=int(minutes), spoke=bool(spoke), attested=True)
|
| 508 |
claim_path = config.make_export_path(selected, "Sample 837 Json.json")
|
| 509 |
-
|
| 510 |
-
Path(claim_path).write_text(_json.dumps(claim, ensure_ascii=False, indent=2), encoding="utf-8")
|
| 511 |
|
| 512 |
# Status β completed
|
| 513 |
store.set_status(selected, "completed")
|
|
@@ -521,21 +481,6 @@ with tab_spec:
|
|
| 521 |
st.caption(f"Claim: {claim_path}")
|
| 522 |
st.rerun()
|
| 523 |
|
| 524 |
-
# If already completed, surface actions to preview artifacts again
|
| 525 |
-
if status == "completed":
|
| 526 |
-
st.markdown("---")
|
| 527 |
-
st.subheader("Completed Actions")
|
| 528 |
-
if st.button("Send Consult Note to EHR (Preview)", key=f"preview_note_{selected}"):
|
| 529 |
-
# Recompose or read the most recent note (rebuild for simplicity)
|
| 530 |
-
g = _run_guidelines_cached(selected, (soap.get("plan") or ""))
|
| 531 |
-
endnotes = g.get("endnotes", [])
|
| 532 |
-
note_md = _note_markdown(case, summary, soap, endnotes=endnotes)
|
| 533 |
-
modal_templates.show_consult_note_preview(note_md)
|
| 534 |
-
if st.button("Submit 837 Claim (Preview)", key=f"preview_claim_{selected}"):
|
| 535 |
-
pick = next((s for s in billing.autosuggest_cpt(minutes=minutes, spoke=spoke) if s.code == (case.get('billing',{}).get('cpt_code'))), None)
|
| 536 |
-
rate = float(getattr(pick, "rate", 0.0)) if pick else 0.0
|
| 537 |
-
claim = billing.build_837_claim(case, code=str(case.get('billing',{}).get('cpt_code')), rate=rate, minutes=int(minutes), spoke=bool(spoke), attested=True)
|
| 538 |
-
modal_templates.show_837_claim_preview(claim)
|
| 539 |
|
| 540 |
|
| 541 |
|
|
|
|
| 3 |
# Phase 3 β Tabbed Workflow UI (PCP Referral β Specialist Review)
|
| 4 |
# Fully integrated with V2 backend: store, billing, modal_templates, config,
|
| 5 |
# explainability, guideline_annotator, ai_core.
|
| 6 |
+
# Consolidated fixes:
|
| 7 |
+
# - Clinical Summary (consult.summary) + Meds + Labs
|
| 8 |
+
# - Safe seeding, New Case, Reset Demo
|
| 9 |
+
# - SOAP list flattening + reliable UI refresh
|
| 10 |
+
# - Explainability/guidelines visibility + re-run
|
| 11 |
+
# - Billing autosuggest + finalize exports + modals
|
| 12 |
# -----------------------------------------------------------------------------
|
| 13 |
|
| 14 |
from __future__ import annotations
|
| 15 |
|
| 16 |
import os
|
| 17 |
+
import json
|
| 18 |
+
import shutil
|
| 19 |
from pathlib import Path
|
| 20 |
+
from typing import Any, Dict, List, Optional
|
| 21 |
|
| 22 |
import streamlit as st
|
| 23 |
|
| 24 |
from src import store, billing, modal_templates, config
|
| 25 |
from src.styles import inject_base_css
|
| 26 |
from src.paths import faiss_index_dir, initialize_environment
|
| 27 |
+
from src.prompt_builder import build_referral_summary
|
| 28 |
from src.explainability import chips_from_text, ensure_chip_schema
|
| 29 |
from src.guideline_annotator import annotate_guidelines
|
| 30 |
from src.ai_core import generate_soap_draft
|
|
|
|
| 36 |
initialize_environment()
|
| 37 |
|
| 38 |
st.title("Step 2 β Workflow")
|
| 39 |
+
st.caption("PCP Referral β Specialist Review. Demo only β de-identified data; not for clinical use.")
|
| 40 |
|
| 41 |
# --------------------------- Helpers ------------------------------------
|
| 42 |
|
|
|
|
| 78 |
if isinstance(x, str):
|
| 79 |
return x
|
| 80 |
if isinstance(x, list):
|
| 81 |
+
return "\n".join(str(s) for s in x)
|
|
|
|
|
|
|
| 82 |
return str(x)
|
| 83 |
|
| 84 |
def _soap_is_empty(soap: Dict[str, str]) -> bool:
|
|
|
|
| 141 |
)
|
| 142 |
cites = ""
|
| 143 |
if endnotes:
|
| 144 |
+
cites = "\n## Guideline Citations\n" + "\n".join(
|
| 145 |
+
[f"- [{e.get('n', i+1)}] {e.get('doc','Guideline')} p.{e.get('page','')}" for i, e in enumerate(endnotes)]
|
| 146 |
+
)
|
| 147 |
return header + body + cites + "\n"
|
| 148 |
|
| 149 |
def _seed_once() -> None:
|
|
|
|
| 159 |
return items
|
| 160 |
|
| 161 |
def _get_case(case_id: str) -> Dict[str, Any]:
|
| 162 |
+
return store.read_case(case_id) or {}
|
|
|
|
| 163 |
|
| 164 |
# --------------------------- Top banners --------------------------------
|
| 165 |
|
|
|
|
| 187 |
st.error("No cases available.")
|
| 188 |
st.stop()
|
| 189 |
|
| 190 |
+
# --- New Case ---
|
| 191 |
if st.button("β New Case", use_container_width=True):
|
|
|
|
| 192 |
new_id = store.new_case_id()
|
| 193 |
store.create_case({
|
| 194 |
"case_id": new_id,
|
|
|
|
| 202 |
"labs": "",
|
| 203 |
"consent_obtained": False,
|
| 204 |
},
|
| 205 |
+
"soap_draft": {"subjective": "", "objective": "", "assessment": "", "plan": ""},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 206 |
"billing": {"minutes": 5, "spoke": False, "cpt_code": None, "attested": False},
|
| 207 |
"explainability": {"manually_modified": False},
|
| 208 |
})
|
| 209 |
st.success(f"Created new case {new_id}. Refreshing listβ¦")
|
| 210 |
st.rerun()
|
| 211 |
|
| 212 |
+
# --- Reset Demo ---
|
|
|
|
|
|
|
|
|
|
| 213 |
if st.button("ποΈ Reset Demo (clear all cases)", use_container_width=True):
|
|
|
|
| 214 |
try:
|
| 215 |
+
shutil.rmtree(config.paths_dict()["cases_dir"])
|
| 216 |
st.success("β
All demo cases cleared. The 4 default draft cases will be recreated on reload.")
|
|
|
|
| 217 |
st.session_state["_seeded"] = False
|
| 218 |
st.rerun()
|
| 219 |
except Exception as e:
|
|
|
|
| 234 |
case = _get_case(selected)
|
| 235 |
_case_summary_header(case)
|
| 236 |
|
|
|
|
| 237 |
# --------------------------- Tabs ----------------------------------------
|
| 238 |
|
| 239 |
tab_pcp, tab_spec = st.tabs(["PCP Referral", "Specialist Review"])
|
| 240 |
|
| 241 |
# ===== PCP Referral Tab =====
|
|
|
|
|
|
|
| 242 |
with tab_pcp:
|
| 243 |
is_draft = case.get("status") == "draft"
|
| 244 |
p = case.get("patient", {}) or {}
|
|
|
|
| 252 |
"Clinical Summary",
|
| 253 |
value=c.get("summary", "") or c.get("history", ""),
|
| 254 |
height=160,
|
| 255 |
+
placeholder=("E.g., NYHA III HFrEF (EF 30%). BP 110/70 HR 62, euvolemic. "
|
| 256 |
+
"Echo: EF 30%, mild MR, no effusion. Stable; med optimization."),
|
|
|
|
| 257 |
disabled=not is_draft,
|
| 258 |
key=f"summary_{selected}",
|
| 259 |
)
|
|
|
|
| 293 |
else:
|
| 294 |
st.info("Referral is not editable (status is In Review or Completed).")
|
| 295 |
|
|
|
|
| 296 |
# ===== Specialist Review Tab =====
|
| 297 |
with tab_spec:
|
| 298 |
status = case.get("status")
|
|
|
|
| 317 |
|
| 318 |
st.markdown("**CPT Autosuggest**")
|
| 319 |
elig_codes = [s.code for s in suggestions if s.eligible]
|
|
|
|
| 320 |
for s in suggestions:
|
| 321 |
with st.container():
|
| 322 |
status_icon = "β
" if s.eligible else "β οΈ"
|
|
|
|
| 331 |
chosen = None
|
| 332 |
|
| 333 |
attested_default = bool((case.get("billing", {}) or {}).get("attested", False))
|
| 334 |
+
attested = st.checkbox("I attest that the consult was performed and documented per interprofessional e-consult requirements.", value=attested_default if read_only else False, disabled=read_only, key=f"att_{selected}")
|
| 335 |
|
| 336 |
can_finalize = (not read_only) and (chosen is not None) and attested and (minutes >= 5)
|
|
|
|
|
|
|
| 337 |
finalize_right = st.button("Finalize Consult Note", type="primary", disabled=not can_finalize, use_container_width=True, key=f"finalize_r_{selected}")
|
| 338 |
|
| 339 |
# ---------- LEFT: Referral & SOAP ----------
|
| 340 |
with left:
|
| 341 |
st.subheader("Referral Summary")
|
| 342 |
summary = build_referral_summary(case)
|
|
|
|
|
|
|
| 343 |
with st.expander("π Referral Summary (debug view)", expanded=False):
|
| 344 |
st.code(summary, language="markdown")
|
| 345 |
|
|
|
|
| 346 |
st.subheader("SOAP Draft")
|
| 347 |
soap = case.get("soap_draft", {}) or {"subjective": "", "objective": "", "assessment": "", "plan": ""}
|
| 348 |
gen_needed = (status == "submitted") and _soap_is_empty(soap)
|
|
|
|
| 350 |
if gen_needed and not read_only:
|
| 351 |
if st.button("Generate SOAP Draft (LLM)", key=f"gen_{selected}"):
|
| 352 |
try:
|
|
|
|
| 353 |
result = generate_soap_draft(
|
| 354 |
intake=case,
|
| 355 |
mode="mapping",
|
|
|
|
| 358 |
top_p=0.95,
|
| 359 |
)
|
| 360 |
s = result.get("soap", {}) or {}
|
|
|
|
|
|
|
| 361 |
subj = _as_text(s.get("subjective", ""))
|
| 362 |
obj = _as_text(s.get("objective", ""))
|
| 363 |
|
| 364 |
assess_raw = s.get("assessment", "")
|
| 365 |
plan_raw = s.get("plan", "")
|
|
|
|
|
|
|
| 366 |
assess = "\n".join(assess_raw) if isinstance(assess_raw, list) else str(assess_raw)
|
| 367 |
plan = "\n".join(plan_raw) if isinstance(plan_raw, list) else str(plan_raw)
|
| 368 |
|
| 369 |
+
except Exception as e:
|
| 370 |
+
subj, obj, assess, plan = summary, "", "β", "β"
|
|
|
|
|
|
|
|
|
|
| 371 |
st.warning(f"LLM generation unavailable; seeded a minimal draft. ({type(e).__name__})")
|
| 372 |
result = {"error": str(e)}
|
| 373 |
|
|
|
|
| 374 |
with st.expander("π§ MedGemma raw response (debug view)", expanded=False):
|
|
|
|
| 375 |
st.code(json.dumps(result, indent=2), language="json")
|
| 376 |
|
|
|
|
| 377 |
store.update_case(selected, {
|
| 378 |
+
"soap_draft": {"subjective": subj.strip(), "objective": obj.strip(), "assessment": assess.strip(), "plan": plan.strip()},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 379 |
"explainability": {"manually_modified": False},
|
| 380 |
})
|
| 381 |
+
# Force clean reload so text areas show new content
|
| 382 |
+
st.rerun()
|
|
|
|
|
|
|
|
|
|
|
|
|
| 383 |
|
| 384 |
c1, c2 = st.columns(2)
|
| 385 |
with c1:
|
|
|
|
| 402 |
"soap_draft": {"subjective": subj_new, "objective": obj_new, "assessment": assess_new, "plan": plan_new},
|
| 403 |
"explainability": {"manually_modified": True},
|
| 404 |
})
|
| 405 |
+
# Keep on same run; next interactions will show re-computed state
|
|
|
|
|
|
|
| 406 |
|
| 407 |
+
# Post-hoc Explainability & Guidelines
|
| 408 |
exp = (case.get("explainability", {}) or {})
|
| 409 |
manually_modified = bool(exp.get("manually_modified", False))
|
|
|
|
| 410 |
if read_only:
|
| 411 |
manually_modified = False # allow showing in completed
|
| 412 |
|
| 413 |
if not manually_modified and (soap.get("plan") or "").strip():
|
| 414 |
+
with st.expander("Explainability tokens (post-hoc)", expanded=False):
|
| 415 |
+
_render_token_chips(ensure_chip_schema(chips_from_text(soap.get("plan",""))))
|
| 416 |
+
with st.expander("Guideline citations (post-hoc)", expanded=True):
|
| 417 |
g = _run_guidelines_cached(selected, soap.get("plan",""))
|
| 418 |
warn = (g.get("warning") or "").strip()
|
| 419 |
if warn:
|
|
|
|
| 430 |
st.caption("_No references found._")
|
| 431 |
else:
|
| 432 |
if not read_only and (status == "submitted"):
|
| 433 |
+
if st.button("Re-run Guidelines", key=f"rerun_{selected}"):
|
|
|
|
| 434 |
_run_guidelines_cached(selected, plan_new)
|
| 435 |
store.update_case(selected, {"explainability": {"manually_modified": False}})
|
| 436 |
st.rerun()
|
| 437 |
else:
|
| 438 |
+
st.caption("Guideline citations hidden after edits. Click **Re-run Guidelines** to refresh.")
|
| 439 |
|
| 440 |
# Mirror finalize CTA on left
|
| 441 |
finalize_left = st.button("Finalize Consult Note", type="primary", disabled=not can_finalize, key=f"finalize_l_{selected}")
|
|
|
|
| 467 |
rate = float(getattr(pick, "rate", 0.0)) if pick else 0.0
|
| 468 |
claim = billing.build_837_claim(case, code=str(chosen), rate=rate, minutes=int(minutes), spoke=bool(spoke), attested=True)
|
| 469 |
claim_path = config.make_export_path(selected, "Sample 837 Json.json")
|
| 470 |
+
Path(claim_path).write_text(json.dumps(claim, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
|
|
| 471 |
|
| 472 |
# Status β completed
|
| 473 |
store.set_status(selected, "completed")
|
|
|
|
| 481 |
st.caption(f"Claim: {claim_path}")
|
| 482 |
st.rerun()
|
| 483 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 484 |
|
| 485 |
|
| 486 |
|