fix: prevent match group expanders from collapsing on button click

Replace st.rerun() with on_click callbacks so decisions write to
session state before the natural rerun. Decided groups auto-collapse
with status in the label; undecided groups stay expanded. Added undo
button on decided groups.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-28 23:25:12 +00:00
parent b871ab24fc
commit 39e139d777
2 changed files with 42 additions and 31 deletions

View File

@@ -262,26 +262,37 @@ def match_group_card(
group: MatchResult,
df: pd.DataFrame,
group_num: int,
) -> Optional[bool]:
) -> None:
"""Render an expandable match group card with side-by-side diff.
Returns:
True — user clicked Merge (accept match)
False — user clicked Keep Both (reject match)
None — no decision yet
Decisions are stored directly in ``st.session_state["review_decisions"]``
via ``on_click`` callbacks so that other expanders keep their state on
rerun.
"""
confidence = group.confidence
auto_expand = confidence < 95.0
matched_on = ", ".join(group.matched_on)
n_rows = len(group.row_indices)
gid = group.group_id
decisions = st.session_state.get("review_decisions", {})
has_decision = gid in decisions
decision_val = decisions.get(gid)
# Build label — append decision status if already decided
label = (
f"Group {group_num}: {n_rows} rows "
f"(confidence: {confidence:.0f}%) "
f"[{matched_on}]"
)
if decision_val is True:
label += " — Merged"
elif decision_val is False:
label += " — Kept Both"
with st.expander(label, expanded=auto_expand):
# Decided groups collapse; undecided groups stay open
expanded = not has_decision
with st.expander(label, expanded=expanded):
# Build comparison DataFrame
display_cols = [c for c in df.columns if not str(c).startswith("_norm_")]
rows_data = []
@@ -312,28 +323,32 @@ def match_group_card(
styled = compare_df.style.apply(_highlight_diffs, axis=0)
st.dataframe(styled, use_container_width=True)
# Action buttons
btn_left, btn_mid, btn_right = st.columns(3)
merge_key = f"merge_{group.group_id}"
keep_key = f"keep_{group.group_id}"
with btn_left:
if st.button("Merge", key=merge_key, type="primary"):
return True
with btn_mid:
if st.button("Keep Both", key=keep_key):
return False
# Check session state for previous decisions
decisions = st.session_state.get("review_decisions", {})
if group.group_id in decisions:
decision = decisions[group.group_id]
if decision is True:
if has_decision:
# Show current decision with option to undo
if decision_val is True:
st.success("Decision: Merge")
elif decision is False:
else:
st.info("Decision: Keep Both")
return None
def _undo(g=gid):
st.session_state["review_decisions"].pop(g, None)
st.button("Undo", key=f"undo_{gid}", on_click=_undo)
else:
# Action buttons — on_click writes to session state before rerun
def _on_merge(g=gid):
st.session_state["review_decisions"][g] = True
def _on_keep(g=gid):
st.session_state["review_decisions"][g] = False
btn_left, btn_mid, _btn_right = st.columns(3)
with btn_left:
st.button("Merge", key=f"merge_{gid}",
type="primary", on_click=_on_merge)
with btn_mid:
st.button("Keep Both", key=f"keep_{gid}",
on_click=_on_keep)
# ---------------------------------------------------------------------------