feat: use list actions for uncategorized groups
This commit is contained in:
@@ -6,6 +6,7 @@ from __future__ import annotations
|
|||||||
import streamlit as st
|
import streamlit as st
|
||||||
|
|
||||||
from jarca_ledger_mcp.adapters.streamlit import categorization_gateway
|
from jarca_ledger_mcp.adapters.streamlit import categorization_gateway
|
||||||
|
from dashboard.components import JarcaList
|
||||||
from dashboard.common import * # noqa: F403, F405
|
from dashboard.common import * # noqa: F403, F405
|
||||||
|
|
||||||
def render_uncategorized_action_table(rows: list[dict[str, Any]], db_path: str) -> None:
|
def render_uncategorized_action_table(rows: list[dict[str, Any]], db_path: str) -> None:
|
||||||
@@ -56,42 +57,57 @@ def save_merchant_rule_from_recommendation(merchant: str, tx_type: str, rec: dic
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def render_uncategorized_groups_header(groups: list[dict[str, Any]]) -> None:
|
||||||
|
st.markdown(f"{len(groups)}개 사용처 그룹")
|
||||||
|
|
||||||
|
|
||||||
|
def render_uncategorized_group(group: dict[str, Any], db_path: str) -> None:
|
||||||
|
rec = group.get("recommendation") or {}
|
||||||
|
cols = st.columns([2.0, 0.7, 1.0, 1.55, 1.0], vertical_alignment="center")
|
||||||
|
merchant = group.get("merchant") or group.get("raw_summary") or "-"
|
||||||
|
group_key = group.get("sample_transaction_id") or merchant
|
||||||
|
cols[0].markdown(f"**{merchant}** \n<span style='color:#64748b;font-size:0.82rem'>{group.get('raw_summary') or '-'} · {group.get('type') or '-'}</span>", unsafe_allow_html=True)
|
||||||
|
cols[1].write(f"{int(group.get('count') or 0):,}건")
|
||||||
|
cols[2].write(format_krw(group.get("total_amount")))
|
||||||
|
cols[3].write(recommendation_text(rec))
|
||||||
|
apply_disabled = not bool(rec.get("category"))
|
||||||
|
action = cols[4].menu_button(
|
||||||
|
"작업",
|
||||||
|
options=["그룹 적용", "규칙 만들기"],
|
||||||
|
key=f"uncat_group_action_{group_key}",
|
||||||
|
disabled=apply_disabled,
|
||||||
|
width="stretch",
|
||||||
|
)
|
||||||
|
if action == "그룹 적용":
|
||||||
|
res = categorization_gateway.apply_uncategorized_group_recommendation(
|
||||||
|
group.get("merchant", ""), group.get("raw_summary", ""), group.get("type", ""), db_path=db_path
|
||||||
|
)
|
||||||
|
clear_cache()
|
||||||
|
if res.get("success"):
|
||||||
|
st.success(f"그룹 {res.get('applied_count', 0):,}건을 {recommendation_text(res.get('recommendation'))}로 분류했습니다.")
|
||||||
|
st.rerun()
|
||||||
|
else:
|
||||||
|
st.error(res.get("error", res))
|
||||||
|
elif action == "규칙 만들기":
|
||||||
|
res = save_merchant_rule_from_recommendation(group.get("merchant", ""), group.get("type", ""), rec, db_path)
|
||||||
|
clear_cache()
|
||||||
|
if res.get("success"):
|
||||||
|
st.success("사용처 규칙을 저장했습니다.")
|
||||||
|
st.rerun()
|
||||||
|
else:
|
||||||
|
st.warning(res.get("error", res))
|
||||||
|
|
||||||
|
st.caption(f"최근: {group.get('latest_date') or '-'} · 최대금액: {format_krw(group.get('max_amount'))} · 근거: {rec.get('reason') or '추천 없음'}")
|
||||||
|
|
||||||
|
|
||||||
def render_uncategorized_groups(groups: list[dict[str, Any]], db_path: str, repeated_only: bool = False) -> None:
|
def render_uncategorized_groups(groups: list[dict[str, Any]], db_path: str, repeated_only: bool = False) -> None:
|
||||||
visible = [g for g in groups if not repeated_only or int(g.get("count") or 0) >= 2]
|
visible = [g for g in groups if not repeated_only or int(g.get("count") or 0) >= 2][:10]
|
||||||
visible = visible[:10]
|
JarcaList(
|
||||||
if not visible:
|
render_header=render_uncategorized_groups_header,
|
||||||
st.info("반복 후보가 없습니다.")
|
render_item=lambda group: render_uncategorized_group(group, db_path),
|
||||||
return
|
items=visible,
|
||||||
for idx, group in enumerate(visible):
|
empty_message="반복 후보가 없습니다.",
|
||||||
rec = group.get("recommendation") or {}
|
).render()
|
||||||
cols = st.columns([2.0, 0.7, 1.0, 1.55, 1.0, 1.0], vertical_alignment="center")
|
|
||||||
merchant = group.get("merchant") or group.get("raw_summary") or "-"
|
|
||||||
cols[0].markdown(f"**{merchant}** \n<span style='color:#64748b;font-size:0.82rem'>{group.get('raw_summary') or '-'} · {group.get('type') or '-'}</span>", unsafe_allow_html=True)
|
|
||||||
cols[1].write(f"{int(group.get('count') or 0):,}건")
|
|
||||||
cols[2].write(format_krw(group.get("total_amount")))
|
|
||||||
cols[3].write(recommendation_text(rec))
|
|
||||||
apply_disabled = not bool(rec.get("category"))
|
|
||||||
if cols[4].button("그룹 적용", key=f"uncat_group_apply_{idx}_{merchant}", disabled=apply_disabled, use_container_width=True):
|
|
||||||
res = categorization_gateway.apply_uncategorized_group_recommendation(
|
|
||||||
group.get("merchant", ""), group.get("raw_summary", ""), group.get("type", ""), db_path=db_path
|
|
||||||
)
|
|
||||||
clear_cache()
|
|
||||||
if res.get("success"):
|
|
||||||
st.success(f"그룹 {res.get('applied_count', 0):,}건을 {recommendation_text(res.get('recommendation'))}로 분류했습니다.")
|
|
||||||
st.rerun()
|
|
||||||
else:
|
|
||||||
st.error(res.get("error", res))
|
|
||||||
if cols[5].button("규칙 만들기", key=f"uncat_group_rule_{idx}_{merchant}", disabled=apply_disabled, use_container_width=True):
|
|
||||||
res = save_merchant_rule_from_recommendation(group.get("merchant", ""), group.get("type", ""), rec, db_path)
|
|
||||||
clear_cache()
|
|
||||||
if res.get("success"):
|
|
||||||
st.success("사용처 규칙을 저장했습니다.")
|
|
||||||
st.rerun()
|
|
||||||
else:
|
|
||||||
st.warning(res.get("error", res))
|
|
||||||
st.caption(f"최근: {group.get('latest_date') or '-'} · 최대금액: {format_krw(group.get('max_amount'))} · 근거: {rec.get('reason') or '추천 없음'}")
|
|
||||||
if idx < len(visible) - 1:
|
|
||||||
st.divider()
|
|
||||||
|
|
||||||
|
|
||||||
def render_uncategorized_work_items(rows: list[dict[str, Any]], db_path: str, recommended_only: bool = False) -> None:
|
def render_uncategorized_work_items(rows: list[dict[str, Any]], db_path: str, recommended_only: bool = False) -> None:
|
||||||
|
|||||||
Reference in New Issue
Block a user