[verified] Implement account valuation history for issue 12

This commit is contained in:
2026-10-06 08:25:43 +00:00
parent 5813dd4841
commit 8df7c26c84
30 changed files with 1950 additions and 121 deletions
+44
View File
@@ -176,6 +176,50 @@ def page_accounts(db_path: str) -> None:
accounts_data = accounts_res.get("accounts", []) if accounts_res.get("success") else []
if accounts_data:
render_account_list(accounts_data)
selected_id = st.selectbox("평가 이력 계좌", [a["id"] for a in accounts_data], format_func=lambda value: next((a.get("name", value) for a in accounts_data if a["id"] == value), value))
selected_account = next(a for a in accounts_data if a["id"] == selected_id)
history = accounts_gateway.list_account_valuations(selected_id, db_path=db_path)
valuations = history.get("valuations", []) if history.get("success") else []
if valuations:
st.dataframe(valuations, width="stretch", hide_index=True)
# The list is historical insertion order; a rejected/corrected
# tail is not necessarily the effective current valuation.
basis = (selected_account.get("last_synced_at") or "")[:10]
selected = accounts_gateway.select_account_valuation(selected_id, basis, as_of=True, db_path=db_path) if basis else {}
selected_valuation = selected.get("valuation") if selected.get("success") else None
latest = selected_valuation or next(
(item for item in reversed(valuations) if item.get("verification_status") == "verified"), valuations[-1]
)
st.caption(f"최근 실제 기준일: {latest.get('basis_date', '-')} · 상태: {latest.get('verification_status', 'unknown')} · 종류: {latest.get('valuation_kind', 'unknown')}")
else:
st.warning("평가 이력이 없습니다.")
with st.expander("계좌 평가 등록"):
with st.form(f"account_valuation_register_{selected_id}"):
v_date = st.text_input("평가 기준일", value=selected_account.get("last_synced_at", "")[:10])
v_amount = st.number_input("평가 금액", value=int(selected_account.get("last_balance") or 0), step=1)
default_kind = "loan_balance" if selected_account.get("type") == "loan" else ("total_value" if selected_account.get("type") == "investment" else "cash_balance")
v_kind = st.selectbox("평가 종류", ["cash_balance", "balance", "total_value", "loan_balance", "loan"], index=["cash_balance", "balance", "total_value", "loan_balance", "loan"].index(default_kind))
v_status = st.selectbox("검증 상태", ["unverified", "verified", "rejected"])
v_definition = st.selectbox("대표 부채 정의(대출 필수)", ["", "principal_only", "principal_interest_fees"])
v_submit = st.form_submit_button("평가 등록")
if v_submit:
result = accounts_gateway.register_account_valuation(selected_id, v_date, int(v_amount), valuation_kind=v_kind, verification_status=v_status, representative_liability_definition=v_definition or None, db_path=db_path)
if result.get("success"):
clear_cache(); st.success("평가를 등록했습니다."); st.rerun()
else:
st.error(result.get("error", result))
with st.expander("계좌 평가 정정"):
with st.form(f"account_valuation_correct_{selected_id}"):
correction_id = st.text_input("정정 대상 평가 ID")
correction_amount = st.number_input("정정 금액", value=0, step=1, key=f"correction_amount_{selected_id}")
correction_reason = st.text_input("정정 사유")
correction_submit = st.form_submit_button("평가 정정")
if correction_submit:
result = accounts_gateway.correct_account_valuation(correction_id, int(correction_amount), correction_reason, db_path=db_path)
if result.get("success"):
clear_cache(); st.success("평가를 정정했습니다."); st.rerun()
else:
st.error(result.get("error", result))
account_edit_id = st.session_state.get("account_edit_id")
if account_edit_id:
account = next((item for item in accounts_data if item["id"] == account_edit_id), None)
+18 -5
View File
@@ -75,15 +75,28 @@ def page_assets(db_path: str) -> None:
st.divider()
render_section_label("🔒", "월말 스냅샷 Preview / Close")
st.caption("Preview는 DB를 변경하지 않습니다. 같은 기준일을 다시 close하면 정정 사유가 필요하며 기존 행을 덮어쓰지 않고 새 correction run을 append합니다.")
c1, c2, c3 = st.columns([1, 1.5, 1.5])
c1, c2, c3, c4 = st.columns([1, 1, 1.5, 1.5])
basis_date = c1.text_input("마감 기준일", value=f"{current_kst_month()}-01", help="YYYY-MM-DD")
close_note = c2.text_input("Close 출처 메모", value="month-end close")
correction_reason = c3.text_input("정정 사유(재마감 시 필수)", value="")
selection_policy = c2.selectbox("선택 정책", ["exact", "as_of"], format_func=lambda value: "정확히 해당일" if value == "exact" else "기준일 현재(as-of)")
close_note = c3.text_input("Close 출처 메모", value="month-end close")
correction_reason = c4.text_input("정정 사유(재마감 시 필수)", value="")
p1, p2 = st.columns(2)
approval_required = st.checkbox("조건부 close 승인 명시", value=False)
approval_cols = st.columns(3)
approved_by = approval_cols[0].text_input("승인자")
approval_reason = approval_cols[1].text_input("승인 사유")
approval_reference = approval_cols[2].text_input("승인 근거 참조")
allow_missing = st.checkbox("누락 제외 허용", value=False)
allow_carry = st.checkbox("as-of 이월 허용", value=False)
if p1.button("스냅샷 Preview", type="secondary"):
st.session_state["asset_snapshot_preview"] = assets_gateway.preview_asset_snapshot_close(basis_date, db_path=db_path)
st.session_state["asset_snapshot_preview"] = assets_gateway.preview_asset_snapshot_close(basis_date, selection_policy=selection_policy, db_path=db_path)
if p2.button("스냅샷 Close 실행", type="primary"):
st.session_state["asset_snapshot_close"] = assets_gateway.close_asset_snapshot(basis_date, source_note=close_note, correction_reason=correction_reason, db_path=db_path)
st.session_state["asset_snapshot_close"] = assets_gateway.close_asset_snapshot(
basis_date, source_note=close_note, correction_reason=correction_reason,
selection_policy=selection_policy, allow_missing_exclusions=allow_missing,
allow_as_of_carry_forward=allow_carry, approved_by=approved_by if approval_required else "",
approval_reason=approval_reason if approval_required else "", approval_reference=approval_reference if approval_required else "", db_path=db_path,
)
clear_cache()
preview = st.session_state.get("asset_snapshot_preview")
close_res = st.session_state.get("asset_snapshot_close")
+4 -1
View File
@@ -202,7 +202,10 @@ def page_monthly_settlement(db_path: str) -> None:
))
m4.metric("자산 마감 상태", latest_asset_status)
st.caption(f"자산 상세: {warning_text}")
st.caption(f"마감 기준일: {asset_snapshot.get('basis_date', basis_date)}")
st.caption(f"마감 기준일: {asset_snapshot.get('basis_date', basis_date)} · 선택 정책: {asset_snapshot.get('selection_policy', 'exact')} · close_status: {asset_snapshot.get('close_status', '미확인')}")
close_metadata = asset_snapshot.get("close_metadata", {}) if isinstance(asset_snapshot, dict) else {}
if close_metadata.get("approved_by") or close_metadata.get("approval_reason"):
st.caption(f"조건부 승인: {close_metadata.get('approved_by', '-')} · {close_metadata.get('approval_reason', '-')} · {close_metadata.get('approval_reference', '-')}")
st.markdown("- [자산/마감 화면에서 스냅샷 확인](?page=자산/마감)")
render_section_label("📜", "월정산 결과 / Evidence")