feat: extend comprehensive monthly settlement snapshots (#18)

This commit is contained in:
gitea-impl-agent
2026-10-13 04:36:38 +00:00
parent f67c07adf0
commit dd35e0785f
30 changed files with 1817 additions and 48 deletions
+34 -2
View File
@@ -1,5 +1,7 @@
from __future__ import annotations
import json
import streamlit as st
from jarca_ledger_mcp.adapters.streamlit import settlement_gateway as gateway
@@ -83,6 +85,14 @@ def render_settlement_drafts(db_path: str | None = None, month: str = "") -> Non
if draft.get("status") == "completed":
st.success(f"완료 — run {draft.get('final_run_id', '-')}; 변경 불가, 정정은 새 초안으로 생성합니다.")
if draft.get("status") in {"completed", "cancelled"}:
final = detail.get("final_response") if isinstance(detail.get("final_response"), dict) else {}
if final:
st.markdown("### 기록된 종합 월정산 결과")
st.caption(f"정책 버전: {final.get('aggregation_version', 'legacy')}")
for key, label in (("income_total", "수입"), ("expense_total", "지출"), ("net_cashflow", "순현금흐름"), ("total_assets", "자산"), ("total_liabilities", "부채"), ("net_worth", "순자산")):
st.write(label, final.get(key) if final.get(key) is not None else "미보존")
rows = final.get("cashflow_items", [])
if isinstance(rows, list): st.dataframe([{k: row.get(k) for k in ("cashflow_kind", "category", "subcategory", "amount", "transaction_count", "currency")} for row in rows if isinstance(row, dict)])
st.markdown("### 불변 이력")
st.write("final_run_id", draft.get("final_run_id"))
st.write("correction_of_run_id", draft.get("correction_of_run_id"))
@@ -102,6 +112,23 @@ def render_settlement_drafts(db_path: str | None = None, month: str = "") -> Non
except ValueError:
st.caption("날짜 차이를 계산할 수 없습니다.")
if item.get("status") == "missing": st.warning("누락 — 값은 0원이 아닙니다.")
st.markdown("### 종합 월정산 정책")
policy_now = draft.get("cashflow_policy") if isinstance(draft.get("cashflow_policy"), dict) else {"savings_categories": [], "category_decisions": []}
with st.form(f"cashflow_policy_{selected_id}"):
policy_text = st.text_area("정책 JSON (savings_categories 및 category_decisions)", value=json.dumps(policy_now, ensure_ascii=False, indent=2), height=220)
policy_submit = st.form_submit_button("정책 저장")
if policy_submit:
try:
policy = json.loads(policy_text)
if not isinstance(policy, dict) or not isinstance(policy.get("savings_categories", []), list) or not isinstance(policy.get("category_decisions", []), list): raise ValueError
for decision in policy.get("category_decisions", []):
if not isinstance(decision, dict) or decision.get("cashflow_kind") not in {"income", "expense", "excluded"}: raise ValueError
result = gateway.set_settlement_cashflow_policy(selected_id, version, actor, policy, db_path=db_path)
if result.get("success"): _invalidate_and_rerun(selected_id, result)
_safe_result(result)
except (ValueError, TypeError):
st.error("정책은 유효한 JSON이어야 하며 각 분류는 income, expense 또는 excluded로 지정하세요.")
with st.form(f"settlement_edit_{selected_id}"):
memo_edit = st.text_input("초안 메모 저장", value=draft.get("memo", ""))
source_type = st.selectbox("대상 유형", ["account", "manual_asset"])
@@ -179,7 +206,11 @@ def render_settlement_drafts(db_path: str | None = None, month: str = "") -> Non
review_result = review_result or st.session_state.get(f"review_result_{selected_id}")
if isinstance(review_result, dict) and review_result.get("success"):
review = review_result.get("review", review_result)
st.write("검토 합계 (잠정)", review.get("total_assets"), review.get("total_liabilities"), review.get("net_worth"))
st.write("검토 합계 (잠정)", *[review.get(k) for k in ("income_total", "expense_total", "net_cashflow", "total_assets", "total_liabilities", "net_worth")])
items_cf = review.get("cashflow_items", [])
if isinstance(items_cf, list): st.dataframe([{k: row.get(k) for k in ("cashflow_kind", "category", "subcategory", "amount", "transaction_count", "currency")} for row in items_cf if isinstance(row, dict)])
blockers = review.get("cashflow_blockers", [])
for blocker in blockers if isinstance(blockers, list) else []: st.warning(str(blocker.get("code", "cashflow_blocked")) if isinstance(blocker, dict) else "현금흐름 블로커")
st.write("누락", review.get("missing_items", []))
if review.get("contains_substitutions"):
st.warning("대체 평가자료를 포함한 월정산")
@@ -192,7 +223,8 @@ def render_settlement_drafts(db_path: str | None = None, month: str = "") -> Non
allow_missing = st.checkbox("누락 승인", value=False)
allow_carried = st.checkbox("이월 승인", value=False)
allow_substitute = st.checkbox("대체자료 승인", value=False)
approve = st.form_submit_button("검토 결과 승인")
approve = st.form_submit_button("검토 결과 승인", disabled=bool(review.get("cashflow_blockers")))
if review.get("cashflow_blockers"): st.caption("현금흐름 블로커를 해소해야 승인할 수 있습니다.")
if approve:
if st.session_state.get(state_key) != (selected_id, review.get("version", version), fingerprint):
st.error("선택한 초안/버전/검토 fingerprint가 변경되었습니다. 다시 검토하세요.")