"""RCA Card panel: the synthesized root-cause card and cross-tab jumps.
Renders the ``rca_card`` artifact (executive summary, validation gates, primary
hypothesis, alternatives, card-level evidence, recommended actions, analyst
review) with buttons that jump to the Evidence/Candidates tabs for a candidate.
"""
from __future__ import annotations
from typing import Any, Dict, Optional
import streamlit as st
from utils.navigation import goto_tab
from utils.text_helpers import truncate
[docs]
JsonDict = Dict[str, Any]
[docs]
def render_rca_card_panel(
rca_card: Optional[JsonDict],
evidence_bundle: Optional[JsonDict],
kg_context: Optional[JsonDict],
) -> None:
"""Render the RCA Card section.
@ In, rca_card, dict, the ``rca_card`` artifact; reads
``executive_summary``, ``validation_status``, ``primary_hypothesis``,
``alternatives``, ``evidence``, ``recommended_actions``,
``analyst_review``, ``provenance``
@ In, evidence_bundle, dict, optional; only ``bundle_id`` is shown as context
@ In, kg_context, dict, optional; only the subgraph id is shown as context
@ Out, None
"""
if not rca_card:
st.info("No `rca_card` loaded (common in fixture-only mode).")
return
ex = rca_card.get("executive_summary") or {}
status = rca_card.get("validation_status") or {}
prim = rca_card.get("primary_hypothesis") or {}
primary_cid = str(prim.get("candidate_id") or "")
# --- Executive row ---
ds = str(ex.get("decision_status") or "unknown")
if ds == "candidate_ready":
st.success(f"**decision_status:** {ds}")
elif ds in ("review_required", "insufficient_evidence"):
st.warning(f"**decision_status:** {ds}")
else:
st.info(f"**decision_status:** {ds}")
c1, c2, c3 = st.columns(3)
with c1:
st.metric("confidence (exec)", str(ex.get("confidence_label") or "—"))
with c2:
st.metric("schema_valid", str(status.get("schema_valid", "—")))
with c3:
st.metric("fallback_used", str(status.get("fallback_used", "—")))
st.markdown("### Executive summary")
st.write(ex.get("primary_conclusion") or "")
flags = ex.get("analyst_attention_flags") or []
if flags:
st.markdown("**Analyst attention**")
for f in flags:
st.caption(str(f))
# --- Validation gates ---
with st.expander("validation_status"):
st.json(status)
# --- Primary hypothesis ---
st.markdown("### Primary hypothesis")
if primary_cid:
b1, b2 = st.columns([1, 2])
with b1:
st.button(
"View evidence for primary",
key="rca_nav_primary_evidence",
on_click=goto_tab,
args=("Evidence", primary_cid),
)
with b2:
st.button(
"Open Candidates tab",
key="rca_nav_candidates",
on_click=goto_tab,
args=("Candidates",),
)
meta = {
"candidate_id": prim.get("candidate_id"),
"hypothesis_type": prim.get("hypothesis_type"),
"composite_score": prim.get("composite_score"),
"confidence_label": prim.get("confidence_label"),
}
st.write(meta)
st.markdown(f"**{prim.get('cause_label') or 'Cause'}**")
if prim.get("narrative"):
st.write(prim.get("narrative"))
why = prim.get("why_primary") or []
if why:
st.markdown("**Why primary**")
for line in why:
st.markdown(f"- {line}")
unc = prim.get("uncertainties") or []
if unc:
st.markdown("**Uncertainties**")
for line in unc:
st.markdown(f"- {line}")
cites = prim.get("citations") or []
if cites:
st.markdown("**Primary citations**")
for i, cit in enumerate(cites):
if not isinstance(cit, dict):
continue
with st.expander(
f"{cit.get('source_type', 'ref')} · {truncate(str(cit.get('source_id', '')), 40)}",
expanded=False,
):
st.write(
{
"claim_summary": cit.get("claim_summary"),
"source_type": cit.get("source_type"),
"source_id": cit.get("source_id"),
}
)
ex_text = cit.get("excerpt") or cit.get("claim_summary")
if ex_text:
st.text(str(ex_text))
# --- Alternatives ---
alts = rca_card.get("alternatives") or []
if alts:
st.markdown("### Alternatives")
for alt_i, alt in enumerate(alts[:8]):
if not isinstance(alt, dict):
continue
aid = str(alt.get("candidate_id") or "")
label = str(alt.get("cause_label") or aid)[:56]
with st.expander(f"{label}"):
st.write(
{
"candidate_id": aid,
"composite_score": alt.get("composite_score"),
"confidence_label": alt.get("confidence_label"),
"reason_not_primary": alt.get("reason_not_primary"),
}
)
sup = alt.get("supports") or []
weak = alt.get("weaknesses") or []
if sup:
st.markdown("**Supports**")
for s in sup:
st.caption(str(s))
if weak:
st.markdown("**Weaknesses**")
for w in weak:
st.caption(str(w))
if aid:
st.button(
"Evidence filter",
key=f"rca_alt_ev_{alt_i}_{aid}",
on_click=goto_tab,
args=("Evidence", aid),
)
# --- Card-level evidence (synthesizer view) ---
ev_card = rca_card.get("evidence") or []
if ev_card:
st.markdown("### Evidence (on card)")
for row in ev_card[:12]:
if not isinstance(row, dict):
continue
st.markdown(
f"`{row.get('evidence_id')}` · **{row.get('support_role')}** · `{row.get('doc_id')}`"
)
st.caption(str(row.get("summary") or ""))
if row.get("excerpt"):
st.text(str(row.get("excerpt")))
# --- Recommended actions ---
acts = rca_card.get("recommended_actions") or []
if acts:
st.markdown("### Recommended actions")
for a in acts[:8]:
if not isinstance(a, dict):
continue
st.markdown(f"- **{a.get('priority', '')}** {a.get('description', '')}")
lac = a.get("linked_candidate_id")
if lac:
st.button(
f"Evidence · {lac}",
key=f"rca_act_ev_{a.get('action_id')}",
on_click=goto_tab,
args=("Evidence", str(lac)),
)
# --- Analyst review ---
ar = rca_card.get("analyst_review") or {}
if ar:
st.markdown("### Analyst review")
st.write(
{
"decision_required": ar.get("decision_required"),
"writeback_recommendation": ar.get("writeback_recommendation"),
}
)
qs = ar.get("questions_to_resolve") or []
if qs:
st.markdown("**Questions to resolve**")
for q in qs[:12]:
st.markdown(f"- {q}")
# --- KG / bundle context (compact) ---
with st.expander("Cross-artifact context"):
st.write(
{
"rca_id": rca_card.get("rca_id"),
"event_id": rca_card.get("event_id"),
"input_artifacts": rca_card.get("input_artifacts"),
}
)
if kg_context:
st.caption("KG subgraph_id")
st.text(str((kg_context.get("subgraph_id") or kg_context.get("graph_id") or "")))
if evidence_bundle:
st.caption("evidence_bundle.bundle_id")
st.text(str(evidence_bundle.get("bundle_id") or ""))
with st.expander("rca_card provenance (raw)"):
st.json(rca_card.get("provenance") or {})