Files
jarca-ledger-mcp/docs/requirements/req-003_account-valuation-history.md
T

20 KiB

schema, id, title, revision, status, depends_on, applies_to, language, issue, implementation_pr
schema id title revision status depends_on applies_to language issue implementation_pr
gitea-agent.requirement/v1 REQ-003 계좌 기준일 평가 이력과 자산 마감 연동 1.0 approved
ledger
dashboard
mcp
ko #12 #13

REQ-003 — 계좌 기준일 평가 이력과 자산 마감 연동

1. 목적

계좌의 기준일 평가 증거를 append-only 이력으로 저장·선택하고, 검증 가능한 자산만 자산/마감 및 월정산의 역사적 순자산에 반영한다.

2. 배경

구현은 SQLite schema 12를 schema 11에 additive하게 적용한다. 계좌 잔액·import 결과는 실제 증거가 없는 한 unverified 후보이고, 기존 거래·인증·메뉴 계약을 바꾸지 않는다.

3. 범위

포함

  • account_valuations, asset_snapshot_runs, asset_snapshots 확장과 exact/as_of 선택
  • idempotency, append-only correction, 투자 optional metrics, loan 대표 부채
  • read-only preview, explicit approval conditional close, immutable evidence, correction/re-close
  • 계좌·import 후보, reconciliation, MCP·Dashboard·월정산 공통 read model

제외

  • credential/실제 금융 데이터, 자동 추정 거래 생성, fake investment transaction, 거래 원장 재작성
  • 외부 자산 가격 API, 환율 변환, 자동 승인, unverified/future/unknown 값 승인
  • Dashboard 인증·logout·cookie, MCP 인증의 신규 도입, 기존 메뉴 순서 변경

4. 용어와 기준

  • verified leaf: supersedes_id로 더 최신 child가 없는 verification_status=verified 행.
  • exact: 요청 basis_date와 일치하는 verified leaf만 선택한다.
  • as_of: 미래를 보지 않고 요청일 이전 최신 verified leaf를 선택하며 carry-forward와 age를 표시한다.
  • last_balance: opening evidence가 아니라 외부 현재값과 비교하는 external current comparison이다. opening evidence와 balance_after가 모두 없으면 INCOMPLETE_CALCULATION이다.
  • principal 물리 필드: principal은 investment의 reported principal amount 또는 loan의 outstanding principal이며, reported_principal_kind와 reported_principal_label만 source-declared 의미를 보존한다. generic label 필드는 없다.
  • 대표 부채: valuation_kind가 loan/loan_balance이고 representative_liability_definition이 principal_only 또는 principal_interest_fees인 하나의 대표 금액만 합산한다. loan purpose, definition, ambiguity는 report에 보존한다.
  • 정본 구현: jarca_ledger_mcp/application/account_valuation_service.py, assets_service.py, reconciliation_service.py, migrations.py 및 named tests.

5. 요구사항

REQ-003.01 — additive 이력과 legacy 보존

schema 11의 테이블·필드·거래를 삭제하거나 추측 이주하지 않고 schema 12의 account_valuations, asset_snapshot_runs를 추가하며 asset_snapshots에 필요한 필드를 additive하게 확장한다. migration은 idempotent다.

영향 범위

  • 인터페이스: 기존 service/MCP 응답 envelope 유지, 신규 valuation/snapshot fields additive
  • 영속성: SQLite schema 11→12, 기존 rows 보존
  • 후속 사용 주체: service, MCP tool, Dashboard gateway/page, 월정산
  • 마이그레이션: migrate_db()의 CREATE TABLE IF NOT EXISTS/ADD COLUMN
  • 호환성: REQ-001 auth, cookie/logout, REQ-002 메뉴 10개/order, query/shell/browser evidence 유지
  • 배포 전제 조건: SQLite write 권한과 기존 DB 백업

REQ-003.02 — verified leaf 선택과 범위

exact/as_of 선택은 account/currency/valuation_kind scope를 적용하고 future, unverified, rejected, unknown을 authority에서 제외한다. newer unverified candidate는 warning으로 남기며 investment는 total_value, balance, cash_balance 우선, loan은 loan_balance, loan, balance 우선이다.

영향 범위

  • 인터페이스: select_account_valuation(), _account_valuation_items(), report warnings
  • 영속성: 상태·기준일·통화·kind와 candidate evidence 보존
  • 후속 사용 주체: snapshot preview/close, Dashboard 계좌·자산, reconciliation
  • 마이그레이션: 없음
  • 호환성: 기존 last_balance fallback은 basis-date 없는 legacy report에서만 유지
  • 배포 전제 조건: ISO date와 허용 currency/kind

REQ-003.03 — idempotency와 append-only correction

같은 idempotency payload는 기존 row/run을 재사용하고 다른 payload는 idempotency_conflict다. source_ref와 fingerprint가 evidence idempotency를 이룬다. correction은 원본을 갱신하지 않고 단일 child를 append한다. correction parent scope는 same account/date/currency/purpose이고 cyclic parent와 self parent는 거절한다. parent당 one child만 허용하며 correction_reason은 필수다. reason 누락, parent 불일치, cyclic/self parent, 다른 child 및 DB 충돌은 no-write correction_conflict다. source_ref는 account-scoped evidence identity이며 explicit key가 있어도 evidence identity에 적용한다. key/source mismatch는 conflict다.

영향 범위

  • 인터페이스: register_account_valuation(), correct_account_valuation(), close_asset_snapshot()
  • 영속성: unique idempotency 및 single-correction indexes
  • 후속 사용 주체: retry caller와 history/report
  • 마이그레이션: unique indexes additive
  • 호환성: 성공/실패 envelope와 기존 error_code 유지
  • 배포 전제 조건: transaction-scoped SQLite write

REQ-003.04 — 금액·옵션·대출 의미

amount와 principal/interest/fee/cost_basis/net_contributed/profit은 exact integer이고 NULL은 미제공, 0은 명시적 영(0)이다. 투자 optional metrics는 저장하되 total valuation을 다시 합산하지 않는다. loan은 대표 정의를 필수로 하고 principal/interest/fee의 unknown을 NULL로 보존하며 대표 금액만 총부채에 합산한다.

영향 범위

  • 인터페이스: valuation input/output fields, validation errors
  • 영속성: INTEGER/REAL return_rate와 nullable optional columns
  • 후속 사용 주체: net-worth formula와 UI labels
  • 마이그레이션: 없음
  • 호환성: 기존 integer ledger amounts와 double-sum 금지
  • 배포 전제 조건: caller가 currency/definition을 명시

REQ-003.05 — current와 historical authority

current latest display와 historical authority를 분리한다. last_balance는 opening evidence가 아니라 external current comparison이며, balance_after/opening evidence가 모두 없으면 INCOMPLETE_CALCULATION이다. 늦게 도착한 historical row는 current를 과거로 되돌리지 않으며 cannot roll back newer current cache. account/import writes는 real dated evidence가 있을 때만 dated unverified candidate를 만든다. 대출 cache write는 대표 부채 정의가 없으면 이력 후보를 추가하지 않는다. 이때 current cache와 import 처리는 유지하고 정의를 추정하지 않는다.

영향 범위

  • 인터페이스: account update/import candidate, list/select/report
  • 영속성: basis_date/recorded_at 분리
  • 후속 사용 주체: 계좌 화면, reconciliation, monthly settlement
  • 마이그레이션: 없음
  • 호환성: account name-only update는 candidate를 만들지 않음
  • 배포 전제 조건: source date와 source_ref/hash

REQ-003.06 — preview·close·approval

preview는 DB를 쓰지 않고 complete_exact, incomplete_draft, conditional 상태와 warnings/exclusions를 반환한다. close response는 run, approval, correction, warnings, exclusions를 포함한다. close는 need-based explicit approved_by, approval_reason, approval_reference와 flags를 요구하며 immutable run evidence를 기록한다. explicit dated manual asset은 검증된 manual evidence로 취급할 수 있다. unverified/future/unknown/ambiguous, including future unknown, 은 승인 없이 close할 수 없다.

영향 범위

  • 인터페이스: preview_asset_snapshot_close(), close_asset_snapshot() 및 error codes
  • 영속성: run applied/excluded/carried evidence와 approval metadata
  • 후속 사용 주체: Dashboard 자산/마감, MCP operator
  • 마이그레이션: asset_snapshot_runs 신규
  • 호환성: preview read-only, 기존 report response fields 유지
  • 배포 전제 조건: operator approval identity와 reference

REQ-003.07 — correction/re-close와 formula

같은 basis date close는 correction_reason을 요구하고 새 run/rows를 append한다. 최신 run만 historical report authority이며 순자산은 asset_buckets - loan, missing valuation은 transaction inference 없이 제외한다.

영향 범위

  • 인터페이스: close/report/timeseries
  • 영속성: correction_of_run_id, is_correction, closed_at
  • 후속 사용 주체: timeseries와 월정산
  • 마이그레이션: asset snapshot columns/indexes
  • 호환성: legacy asset_snapshots rows are readable
  • 배포 전제 조건: close transaction atomicity

REQ-003.08 — reconciliation과 monthly propagation

investment가 verified valuation으로 관리되면 reconciliation status는 VALUATION_MANAGED이고 fake transaction을 만들지 않는다. report는 unavailable investment를 명시한다. investment unavailable과 reconciliation completeness를 report에 명시한다. regular ledger calculation은 calculation_basis/calculation_basis_at provenance를 유지한다. monthly settlement는 read-only로 동일 snapshot/report를 전파하며 no fake writes를 보장한다. live/current, closed, timeseries, monthly 각각은 actual valuation_date, age, carry, verification, run, approval, correction, warnings, exclusions를 보존한다. monthly_source_snapshot_id는 snapshot_run_id의 read-model alias이며 persistence field가 아니다.

영향 범위

  • 인터페이스: get_balance_reconciliation_report(), get_monthly_settlement_summary()
  • 영속성: reconciliation session/transaction은 valuation 때문에 자동 변경하지 않음
  • 후속 사용 주체: 월정산·overview·reconciliation UI
  • 마이그레이션: 없음
  • 호환성: existing reconciliation statuses and reports additive
  • 배포 전제 조건: service read model available

canonical D04-D06 contract: snapshot close aliases valuation_date, age_days, verification_status, evidence_kind, and monthly_source_snapshot_id at the top level while preserving the same meanings in nested provenance. Replay requires request-fingerprint equality and reconstructs from persisted evidence, never replay-only reused fields. Recursive close_metadata is allowlisted/redacted. Preview fingerprint input/output and authority drift rejection are mandatory. Dashboard missing DB is stable unavailable with no-create; initialized reads are no-write. Reconciliation requires opening evidence and coverage; incomplete state is INCOMPLETE_CALCULATION/reconciliation unavailable, with no inferred balance or fake transaction.

REQ-003.09 — shared application과 보안 경계

MCP와 Dashboard는 shared service/gateway를 사용한다. 기존 success/error envelope를 유지한다. Dashboard는 auth-before-DB이며 MCP auth를 claim하지 않는다. render는 read-only이고 implicit write를 일으키지 않는다.

영향 범위

  • 인터페이스: MCP @mcp.tool, Streamlit gateways/pages
  • 영속성: render/preview에는 write 없음; explicit form/close만 write
  • 후속 사용 주체: browser/operator/automation
  • 마이그레이션: 없음
  • 호환성: REQ-001 gate, cleanup/logout, MCP transport/auth scope 유지
  • 배포 전제 조건: Dashboard auth config, MCP runtime config

REQ-003.10 — privacy, compatibility, excluded scope

문서·tests·fixtures는 synthetic data만 사용하고 actual credential/account/financial data를 저장하지 않는다. Issue #12 밖의 OAuth, external prices, FX, automatic approval, transaction inference, redesign는 구현하지 않는다.

영향 범위

  • 인터페이스: redacted evidence와 generic errors
  • 영속성: synthetic source_note/memo only
  • 후속 사용 주체: tests/operator
  • 마이그레이션: 없음
  • 호환성: 기존 auth/menu/query/shell/browser evidence semantic inventory 0 missing
  • 배포 전제 조건: secret redaction과 fixture DB

6. 제약과 불변조건

  • verified leaf만 authority이며 future/unverified/rejected/unknown은 합산하지 않는다.
  • 동일 idempotency payload는 같은 ID를 반환하고 다른 payload는 conflict다.
  • correction과 re-close는 append-only이고 parent당 child/run은 하나다.
  • NULL과 0을 구별하고 모든 금액은 integer precision이다.
  • loan representative만 liability sum에 들어가고 investment metrics는 double sum하지 않는다.
  • preview와 render는 DB write가 없고 close는 하나의 transaction 경계다.
  • current display와 historical basis authority는 분리된다.
  • Dashboard auth gate는 DB보다 먼저이며 MCP 인증은 이 요구사항이 주장하지 않는다.
  • 실제 credential·token·cookie·account data는 문서·로그·fixture에 없다.

7. 인수 기준

REQ-003.01-AC01 — migration and preservation

Cases (정상/경계/실패): 정상 schema 12 init twice; 경계 schema 11 legacy DB; 실패 malformed legacy DB.

State Invariants: legacy tables/rows remain; migration repeats without duplicate columns.

판정 기준: schema_version=12, required tables/columns/indexes exist and old rows remain.

검증: tests/test_account_valuations.py::test_account_valuation_schema_is_v12_and_idempotent, tests/test_issue_12_regressions.py::test_schema12_migration_preserves_legacy_rows_indexes_and_is_idempotent. 검증 분류: level=integration; backend=sqlite

REQ-003.02-AC01 — selection policy

Cases (정상/경계/실패): exact verified; as_of carry-forward; future/unverified/ambiguous excluded/warned.

State Invariants: no future/unverified row is selected.

판정 기준: exact returns same-date verified leaf; as_of returns latest prior verified and age/warning.

검증: tests/test_account_valuations.py::test_select_exact_and_as_of_excludes_unverified_and_reports_carry_forward, source _account_valuation_items(). 검증 분류: level=unit; backend=application

REQ-003.03-AC01 — idempotent correction

Cases (정상/경계/실패): same payload reuse; different payload conflict; second correction fails.

State Invariants: one row/child per operation and original immutable.

판정 기준: exact error codes and count 2 after one correction.

검증: tests/test_account_valuations.py::test_register_same_idempotency_payload_reuses_row_and_changed_payload_conflicts, tests/test_account_valuations.py::test_correction_is_append_only_reasoned_and_single_child, tests/test_issue_12_regressions.py::test_empty_conditional_close_is_closed_and_second_close_requires_correction. 검증 분류: level=integration; backend=sqlite

REQ-003.04-AC01 — precision and representative loan

Cases (정상/경계/실패): integer zero; NULL optional metrics; loan missing definition rejected.

State Invariants: no float amount and no double sum.

판정 기준: optional fields round-trip exactly and loan definition validation is enforced.

검증: test_correction_preserves_optional_fields_unless_overridden, _validate_common() and schema static checks. 검증 분류: level=static; backend=sqlite

REQ-003.05-AC01 — authority separation

Cases (정상/경계/실패): dated candidate; late historical candidate; name-only update.

State Invariants: current/latest display cannot roll backward and candidate is unverified.

판정 기준: dated account write creates only unverified candidate; name-only does not.

검증: test_account_balance_write_records_unverified_candidate_only_with_real_basis_date, test_account_name_only_update_does_not_create_candidate. 검증 분류: level=unit; backend=application

REQ-003.06-AC01 — preview and approval

Cases (정상/경계/실패): complete exact close; incomplete draft; missing approval rejected.

State Invariants: preview row count unchanged; no unsafe evidence approved.

판정 기준: preview is read-only and close returns conditional_approval_required without metadata.

검증: test_manual_asset_update_deactivate_preview_and_close_correction_policy, preview_asset_snapshot_close(). 검증 분류: level=integration; backend=application

REQ-003.07-AC01 — immutable close correction

Cases (정상/경계/실패): first close; same-date correction; second unreasoned close.

State Invariants: old run/rows remain and latest run alone reports.

판정 기준: correction_of_run_id, count, and net worth match.

검증: test_manual_asset_update_deactivate_preview_and_close_correction_policy, assets_service.py close path. 검증 분류: level=integration; backend=sqlite

REQ-003.08-AC01 — reconciliation/monthly

Cases (정상/경계/실패): regular ledger calculation; verified investment; missing baseline.

State Invariants: no fake transaction; provenance fields present; monthly read is non-mutating.

판정 기준: status VALUATION_MANAGED for verified investment and monthly summary reads report.

검증: tests/test_service.py::test_statement_status_account_update_and_balance_reconciliation, tests/test_issue_12_regressions.py::test_reconciliation_uses_corrected_investment_leaf_without_transaction_count_change, tests/test_service.py::test_monthly_settlement_summary_read_model_foundation. 검증 분류: level=unit; backend=application

REQ-003.09-AC01 — shared adapters and auth

Cases (정상/경계/실패): MCP stdio/http config; authenticated Dashboard render; unauthenticated gate.

State Invariants: auth precedes DB; render does not write; response envelope has success.

판정 기준: actual tool/gateway names resolve and existing auth/menu tests pass.

검증: tests/test_mcp_http_runtime.py, tests/test_dashboard_authentication_app.py, tests/test_dashboard_menu_transition.py, server.py and gateways. 검증 분류: level=integration; backend=dashboard

REQ-003.10-AC01 — privacy and compatibility

Cases (정상/경계/실패): synthetic fixture; redaction search; actual secret/data detection.

State Invariants: no credential/token/cookie/real financial data in docs/tests/evidence.

판정 기준: semantic inventory missing=0 and ambiguous=0; placeholder=0.

검증: tests/test_requirement_docs.py, auth/menu evidence tests, repository redaction/static search. 검증 분류: level=static; backend=dashboard

8. 오류 및 충돌 계약

공통 envelope는 성공 시 success: true, 실패 시 success: false, error_code, error를 유지한다. 주요 codes는 validation_error, not_found, idempotency_conflict, correction_conflict, ambiguous_valuation, conditional_approval_required, db_error다. SQLite unique/transaction conflict는 추가 write 없이 실패한다.

9. 데이터 및 인터페이스 계약

서비스는 jarca_ledger_mcp.service re-export를 통해 호출되고 MCP는 jarca_ledger_mcp.adapters.mcp.server tools를 노출한다. Dashboard는 adapters/streamlit/accounts_gateway.py, assets_gateway.py와 dashboard/page_views/accounts.py, assets.py를 사용한다. 실제 fields/signatures는 docs/interface-contract.md에 고정한다.

10. 하위 호환성

REQ-001의 auth gate·cookie/cleanup/logout, REQ-002의 메뉴 10개와 order(월정산, 요약, 자산/마감, 거래내역, 확인필요 큐, 미분류, 분류 규칙, 카테고리, 계좌, Import), query/shell/background와 browser evidence 계약을 삭제·축약하지 않고 additive하게 유지한다. 기존 MCP stdio/streamable-http transport와 success/error envelope를 유지한다.

11. 추적성

  • 근거 이슈: #12
  • 구현 PR: #13
  • 관련 설계 문서: docs/data-model.md, docs/interface-contract.md, docs/project-structure.md, docs/runtime-flow.md
  • 관련 요구사항: REQ-001, REQ-002
  • DB lifecycle: writer/bootstrap은 명시적 init_db() 호출로만 생성·마이그레이션하고, read_db()는 SQLite mode=ro로 연다. missing DB는 db_not_initialized이며 read path는 no mkdir, no migrate, no commit이다. first-use write만 init_db를 수행하고 auth-before-DB 순서를 보존한다.