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를 만든다.

영향 범위

  • 인터페이스: 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 순서를 보존한다.