계좌 평가 이력과 자산 마감 데이터 모델
목적과 범위
계좌 평가 증거, 수동 자산, append-only 자산 마감과 기존 ledger entities의 실제 SQLite 정본을 기록한다.
- 포함 범위:
accounts, transactions, manual_assets, account_valuations, asset_snapshots, asset_snapshot_runs, reconciliation_sessions, review_decisions, import_batches, schema_meta
- 제외 범위: auth cookie/session, external price/FX store, inferred transactions, actual credentials/data
정본 엔터티
accounts — 기존 계좌
- 책임: 계좌 identity/current display balance와 account type/liquidity metadata
- 식별자:
id
- 생명 주기: 기존 CRUD; valuation history는 별도 append-only
필드
| 필드 |
타입 |
필수 여부 |
기본값 |
허용 값·형식 |
단위·정밀도 |
민감정보 |
id |
TEXT |
필수 |
없음 |
generated account id |
식별자 |
아니오 |
name |
TEXT |
필수 |
없음 |
non-empty |
없음 |
아니오 |
type |
TEXT |
필수 |
bank |
bank/card/cash/investment/loan/other |
enum |
아니오 |
currency |
TEXT |
필수 |
KRW |
supported currency |
ISO code |
아니오 |
last_balance |
INTEGER |
선택 |
NULL |
exact integer |
currency unit |
아니오 |
last_balance_basis_date |
TEXT |
선택 |
'' |
YYYY-MM-DD or unknown |
date of the evidence represented by last_balance; empty/unknown is explicit |
아니오 |
last_synced_at |
TEXT |
선택 |
'' |
ISO date/time |
timestamp |
아니오 |
statement_status |
TEXT |
필수 |
unknown |
existing status enum |
없음 |
아니오 |
liquidity_bucket |
TEXT |
필수 |
other |
cash/deposit/investment/real_estate/other/loan |
enum |
아니오 |
account_number_masked |
TEXT |
필수 |
'' |
masked only |
redacted |
민감 가능 |
account-valuations — 기준일 계좌 평가
- 책임: 날짜가 지정된 평가 evidence와 선택/correction provenance. investment
principal은 reported principal amount이고 loan principal은 outstanding principal이다.
- 식별자:
id
- 생명 주기: insert-only; correction은
supersedes_id child
필드
| 필드 |
타입 |
필수 여부 |
기본값 |
허용 값·형식 |
단위·정밀도 |
민감정보 |
id |
TEXT |
필수 |
없음 |
generated valuation id |
식별자 |
아니오 |
account_id |
TEXT FK |
필수 |
없음 |
accounts.id |
관계 |
아니오 |
basis_date |
TEXT |
필수 |
없음 |
YYYY-MM-DD |
date |
아니오 |
amount |
INTEGER |
필수 |
없음 |
exact signed integer |
currency unit |
아니오 |
currency |
TEXT |
필수 |
KRW |
supported currency |
ISO code |
아니오 |
valuation_kind |
TEXT |
필수 |
balance |
balance/cash_balance/total_value/loan/loan_balance |
purpose |
아니오 |
source_type |
TEXT |
필수 |
manual |
manual/import/account_write/reconciliation/snapshot |
enum |
아니오 |
source_ref |
TEXT |
필수 |
'' |
source/hash/reference |
opaque |
민감 가능 |
verification_status |
TEXT |
필수 |
unverified |
verified/unverified/rejected |
enum |
아니오 |
recorded_at |
TEXT |
필수 |
없음 |
ISO timestamp |
timestamp |
아니오 |
memo |
TEXT |
필수 |
'' |
operator note |
없음 |
synthetic only |
idempotency_key |
TEXT |
필수 |
'' |
caller key |
opaque |
아니오 |
supersedes_id |
TEXT FK |
선택 |
NULL |
parent valuation |
one child |
아니오 |
correction_reason |
TEXT |
필수 |
'' |
required for correction |
없음 |
아니오 |
as_of_at/timezone |
TEXT |
함께 선택 |
NULL |
timezone-aware ISO + IANA |
timestamp/zone |
아니오 |
cost_basis,net_contributed,profit,principal,interest,fee |
INTEGER |
선택 |
NULL |
exact integer; NULL distinct from 0 |
currency unit |
아니오 |
reported_principal_kind,reported_principal_label,return_basis |
TEXT |
선택 |
NULL |
source-declared |
없음 |
아니오 |
return_rate |
REAL |
선택 |
NULL |
finite number |
ratio |
아니오 |
representative_liability_definition |
TEXT |
loan 필수 |
NULL |
principal_only/principal_interest_fees |
enum |
아니오 |
select_account_valuation 선택 규칙과 기존 인덱스 매핑
select_account_valuation은 account_id is mandatory인 단일 평가 선택 query다. currency and valuation_kind/purpose are optional filters이며, basis_date exact match or <= upper bound 조건을 지원한다.
- 선택 결과는
verified leaf valuations only다. 즉 superseded parent가 아닌 leaf만 권위로 사용하며, future-dated and unverified valuations are excluded한다.
idx_account_valuations_account_date supports account/date selection; idx_account_valuations_status_date supports status/date selection. neither index covers account + currency + purpose이므로 해당 조합의 전체 선택 조건을 기존 인덱스가 대신 보장한다고 해석하지 않는다.
idempotency and single_correction indexes enforce write integrity, not selection이다. 이 인덱스들은 재기록 방지와 correction 단일 child 불변조건을 위한 것이며 select_account_valuation의 후보 검색 인덱스가 아니다.
manual-assets — 수동 자산/부채
- 책임: transaction이 아닌 평가 source
- 식별자:
id
- 생명 주기: update/deactivate; closed snapshot은 별도 immutable copy
필드
| 필드 |
타입 |
필수 여부 |
기본값 |
허용 값·형식 |
단위·정밀도 |
민감정보 |
id |
TEXT |
필수 |
없음 |
generated asset id |
식별자 |
아니오 |
name |
TEXT |
필수 |
없음 |
non-empty |
없음 |
아니오 |
liquidity_bucket |
TEXT |
필수 |
other |
normalized bucket |
enum |
아니오 |
valuation_amount |
INTEGER |
선택 |
NULL |
exact integer; NULL unvalued |
currency unit |
아니오 |
valuation_date |
TEXT |
필수 |
'' |
YYYY-MM-DD |
date |
아니오 |
currency |
TEXT |
필수 |
KRW |
supported currency |
ISO code |
아니오 |
source_note,memo |
TEXT |
필수 |
'' |
synthetic/redacted note |
없음 |
민감 가능 |
active |
INTEGER |
필수 |
1 |
0/1 |
boolean |
아니오 |
asset-snapshots — closed snapshot rows
- 책임: basis-date historical immutable item copy
- 식별자:
id
- 생명 주기: insert-only; correction run creates new rows
필드
| 필드 |
타입 |
필수 여부 |
기본값 |
허용 값·형식 |
단위·정밀도 |
민감정보 |
id,basis_date,source_type,name,created_at |
TEXT |
필수 |
없음 |
generated/date/account/manual_asset |
identity/date |
아니오 |
source_id,currency,liquidity_bucket,valuation_source,source_note,memo |
TEXT |
필수 |
''/KRW/other |
source metadata |
없음 |
가능 |
amount |
INTEGER |
선택 |
NULL |
exact integer |
currency unit |
아니오 |
snapshot_run_id |
TEXT |
필수 |
'' |
asset_snapshot_runs.id |
relation |
아니오 |
correction_of_run_id,correction_reason |
TEXT |
필수 |
'' |
prior run/reason |
provenance |
아니오 |
is_correction |
INTEGER |
필수 |
0 |
0/1 |
boolean |
아니오 |
closed_at |
TEXT |
필수 |
'' |
ISO timestamp |
timestamp |
아니오 |
valuation_id,selection_policy,verification_status,evidence_kind,exclusion_reason |
TEXT |
additive |
empty/exact/unverified/applied/empty |
provenance enum |
없음 |
아니오 |
asset-snapshot-runs — close 실행 증거
- 책임: approval, selection, applied/excluded/carried evidence와 run identity
- 식별자:
id
- 생명 주기: insert-only; correction points to prior run
필드
| 필드 |
타입 |
필수 여부 |
기본값 |
허용 값·형식 |
단위·정밀도 |
민감정보 |
id,basis_date,close_status |
TEXT |
필수 |
없음 |
complete_exact/incomplete_draft/conditional |
identity/date/status |
아니오 |
selection_policy |
TEXT |
필수 |
exact |
exact/as_of |
enum |
아니오 |
idempotency_key |
TEXT |
필수 |
'' |
unique when non-empty |
opaque |
아니오 |
approved_by,approved_at,approval_reason,approval_reference |
TEXT |
필수 |
empty |
explicit approval metadata |
provenance |
아니오 |
correction_of_run_id,correction_reason |
TEXT |
필수 |
empty |
prior run/reason |
provenance |
아니오 |
applied_evidence,excluded_evidence,carried_evidence |
TEXT JSON |
필수 |
[] |
redacted evidence arrays |
JSON |
가능 |
created_at |
TEXT |
필수 |
없음 |
ISO timestamp |
timestamp |
아니오 |
transactions, import-batches, reconciliation-sessions, review-decisions, schema-meta — 기존 entities
기존 필드와 관계를 보존한다. import는 거래를 기록할 수 있지만 balance valuation candidate는 dated unverified row만 추가한다. loan cache writes without an explicit liability definition do not append an account_valuations candidate; current cache와 transaction import는 그대로 완료하며 정의를 추정하지 않는다. reconciliation은 calculated balance provenance와 VALUATION_MANAGED 상태를 보존하며 investment valuation 때문에 transaction을 생성하지 않는다. schema_meta.schema_version은 12다.
현재 계좌 잔액 캐시와 기준일
last_balance is the current cache value: it is the latest current balance observed by an import or account write, not a historical valuation and not opening evidence.
last_balance_basis_date is the basis field for the evidence represented by that cache. It is a date when known; legacy or date-less writes store unknown/NULL and must not be inferred from last_synced_at or a valuation row.
last_synced_at is the execution/sync timestamp: when the import or account write advanced the cache, not the evidence basis date.
- imports and account writes advance the account cache; non-loan writes may also append a dated
account_valuations candidate. loan cache writes without an explicit liability definition do not append historical evidence, and updating the cache does not rewrite historical rows.
account_valuations is the historical authority. A dated valuation remains append-only evidence even when it is older than the current cache; older evidence never rolls back the current cache.
- Every valuation attached to a
loan account has an explicit principal_only or principal_interest_fees representative_liability_definition. A non-loan to loan conversion with any undefined or invalid attached valuation is rejected without changing the account or history; conversion with no valuations is allowed.
관계와 불변조건
관계
| 출발 엔터티 |
관계 |
대상 엔터티 |
카디널리티 |
필수 여부 |
소유권 |
삭제 규칙 |
accounts |
has valuations |
account-valuations |
1:N |
required |
account service |
no cascade/history preserved |
account-valuations |
supersedes |
account-valuations |
0..1 child |
optional |
valuation service |
append-only |
asset-snapshot-runs |
contains |
asset-snapshots |
1:N/possibly zero |
required |
snapshot service |
no delete |
account-valuations |
evidence for |
asset-snapshots |
0..N |
optional |
snapshot run |
copy only |
import-batches |
produces candidate |
account-valuations |
0..N |
optional |
import service |
candidate preserved |
accounts |
has reconciliation |
reconciliation-sessions |
1:N |
optional |
reconciliation service |
no valuation side effect |
불변조건
idx_account_valuations_single_correction ensures one direct child; correction parent scope is same account/date/currency/purpose. Cyclic or self parent, second child, missing reason, and conflict are no-write failures.
source_ref is account-scoped evidence identity even when an explicit key is supplied; key/source mismatch conflicts. This is evidence idempotency. There is no generic label; only source-declared reported_principal_kind and reported_principal_label are physical fields.
- only verified leaves are authority; unknown/future/unverified are excluded or warning evidence.
last_balance is external current comparison, not opening evidence; missing balance_after/opening evidence is INCOMPLETE_CALCULATION.
- preview does not insert; close inserts run and rows atomically; latest run is report authority. Reports expose actual valuation_date, age, carry, verification, run, approval, correction, warnings, and exclusions across close response, live, closed, timeseries, and monthly.
monthly_source_snapshot_id is a read-model alias of snapshot_run_id, not a persistence field. Investment unavailable and unavailable investment, loan purpose/definition/ambiguity, and reconciliation completeness remain explicit; no fake writes.
asset_buckets - loan is the net-worth formula; representative loan only, no fake transactions.
저장과 마이그레이션
- 저장 위치: resolved SQLite path from
jarca_ledger_mcp.infrastructure.sqlite.connection
- 직렬화 형식: SQLite tables; evidence arrays JSON UTF-8
- 스키마 버전: schema 11 → schema 12 additive
- 기존 데이터 처리:
migrate_db() creates missing tables/columns/indexes; this is an additive migration with no guessed migration or deletion. legacy rows with unknown basis remain unknown; no inference is permitted.
- 하위 호환성: existing ledger/auth/menu/MCP contracts and legacy rows remain readable
- 롤백 조건: pre-migration backup restore boundary; do not down-migrate or delete valuation history in application code
검증
- 정상 데이터:
tests/test_account_valuations.py, tests/test_asset_snapshots.py
- 경계 데이터: NULL/0, stale carry-forward, correction, empty snapshot tables
- 오류 데이터: invalid currency/kind, missing loan definition, idempotency/correction conflict
- 저장 후 재조회: service list/select/report and contract regression tests
- 마이그레이션:
test_*schema* tests plus python -m pytest tests/test_account_valuations.py tests/test_asset_snapshots.py