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:
@@ -213,11 +213,7 @@ if uploaded is not None:
|
|||||||
# Individual group cards
|
# Individual group cards
|
||||||
decisions = st.session_state["review_decisions"]
|
decisions = st.session_state["review_decisions"]
|
||||||
for i, group in enumerate(result.match_groups):
|
for i, group in enumerate(result.match_groups):
|
||||||
decision = match_group_card(group, df, group_num=i + 1)
|
match_group_card(group, df, group_num=i + 1)
|
||||||
if decision is not None:
|
|
||||||
decisions[group.group_id] = decision
|
|
||||||
st.session_state["review_decisions"] = decisions
|
|
||||||
st.rerun()
|
|
||||||
|
|
||||||
# Show decision summary
|
# Show decision summary
|
||||||
if decisions:
|
if decisions:
|
||||||
|
|||||||
@@ -262,26 +262,37 @@ def match_group_card(
|
|||||||
group: MatchResult,
|
group: MatchResult,
|
||||||
df: pd.DataFrame,
|
df: pd.DataFrame,
|
||||||
group_num: int,
|
group_num: int,
|
||||||
) -> Optional[bool]:
|
) -> None:
|
||||||
"""Render an expandable match group card with side-by-side diff.
|
"""Render an expandable match group card with side-by-side diff.
|
||||||
|
|
||||||
Returns:
|
Decisions are stored directly in ``st.session_state["review_decisions"]``
|
||||||
True — user clicked Merge (accept match)
|
via ``on_click`` callbacks so that other expanders keep their state on
|
||||||
False — user clicked Keep Both (reject match)
|
rerun.
|
||||||
None — no decision yet
|
|
||||||
"""
|
"""
|
||||||
confidence = group.confidence
|
confidence = group.confidence
|
||||||
auto_expand = confidence < 95.0
|
|
||||||
matched_on = ", ".join(group.matched_on)
|
matched_on = ", ".join(group.matched_on)
|
||||||
n_rows = len(group.row_indices)
|
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 = (
|
label = (
|
||||||
f"Group {group_num}: {n_rows} rows "
|
f"Group {group_num}: {n_rows} rows "
|
||||||
f"(confidence: {confidence:.0f}%) "
|
f"(confidence: {confidence:.0f}%) "
|
||||||
f"[{matched_on}]"
|
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
|
# Build comparison DataFrame
|
||||||
display_cols = [c for c in df.columns if not str(c).startswith("_norm_")]
|
display_cols = [c for c in df.columns if not str(c).startswith("_norm_")]
|
||||||
rows_data = []
|
rows_data = []
|
||||||
@@ -312,28 +323,32 @@ def match_group_card(
|
|||||||
styled = compare_df.style.apply(_highlight_diffs, axis=0)
|
styled = compare_df.style.apply(_highlight_diffs, axis=0)
|
||||||
st.dataframe(styled, use_container_width=True)
|
st.dataframe(styled, use_container_width=True)
|
||||||
|
|
||||||
# Action buttons
|
if has_decision:
|
||||||
btn_left, btn_mid, btn_right = st.columns(3)
|
# Show current decision with option to undo
|
||||||
merge_key = f"merge_{group.group_id}"
|
if decision_val is True:
|
||||||
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:
|
|
||||||
st.success("Decision: Merge")
|
st.success("Decision: Merge")
|
||||||
elif decision is False:
|
else:
|
||||||
st.info("Decision: Keep Both")
|
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)
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user