20 KiB
계좌 평가 이력과 자산 마감 데이터 모델
목적과 범위
계좌 평가 증거, 수동 자산, 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이고 loanprincipal은 outstanding principal이다. - 식별자:
id - 생명 주기: insert-only; correction은
supersedes_idchild
필드
| 필드 | 타입 | 필수 여부 | 기본값 | 허용 값·형식 | 단위·정밀도 | 민감정보 |
|---|---|---|---|---|---|---|
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_balanceis 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_dateis 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 fromlast_synced_ator a valuation row.last_synced_atis 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_valuationscandidate. loan cache writes without an explicit liability definition do not append historical evidence, and updating the cache does not rewrite historical rows. account_valuationsis 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
loanaccount has an explicitprincipal_onlyorprincipal_interest_feesrepresentative_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_correctionensures 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_refis 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-declaredreported_principal_kindandreported_principal_labelare physical fields.- only verified leaves are authority; unknown/future/unverified are excluded or warning evidence.
last_balanceis external current comparison, not opening evidence; missing balance_after/opening evidence isINCOMPLETE_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_idis a read-model alias ofsnapshot_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 - loanis 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 pluspython -m pytest tests/test_account_valuations.py tests/test_asset_snapshots.py
settlement-drafts — 월정산 초안
Schema 13 adds two additive tables; init_db()/migrate_db() remain the only schema writers and schema 11→12 history is unchanged. No draft backfill is inferred. Exact migrated columns (from infrastructure/sqlite/migrations.py) are listed below; SCHEMA_SQL and migration DDL are the schema authority.
| Table | Columns (SQLite types; ? means nullable) |
Indexes / constraints |
|---|---|---|
settlement_drafts |
id TEXT PK, month TEXT NOT NULL, basis_date TEXT NOT NULL, status TEXT NOT NULL, version INTEGER NOT NULL, created_by TEXT NOT NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, memo TEXT DEFAULT '', create_key TEXT UNIQUE NOT NULL, correction_of_run_id TEXT DEFAULT '', correction_reason TEXT DEFAULT '', final_run_id TEXT DEFAULT '', final_response TEXT DEFAULT '', cancelled_by TEXT DEFAULT '', cancellation_reason TEXT DEFAULT '', cancelled_at TEXT DEFAULT '', approval_fingerprint TEXT DEFAULT '', approval_version INTEGER DEFAULT 0, approved_by TEXT DEFAULT '', approved_at TEXT DEFAULT '', approval_reason TEXT DEFAULT '', approval_reference TEXT DEFAULT '', approval_flags TEXT DEFAULT '{}', additive original_request_hash TEXT DEFAULT '' |
idx_settlement_drafts_active_month(month) WHERE status='active' unique; idx_settlement_drafts_month(month,created_at) |
settlement_draft_items |
id TEXT PK, draft_id TEXT NOT NULL FK, source_type TEXT NOT NULL, source_id TEXT NOT NULL, valuation_id TEXT DEFAULT '', selection_mode TEXT DEFAULT 'exact', selection_reason TEXT DEFAULT '', included INTEGER DEFAULT 1, exclusion_reason TEXT DEFAULT '', version INTEGER DEFAULT 1, selected_by TEXT DEFAULT '', selected_at TEXT DEFAULT '', evidence_json TEXT DEFAULT '{}' |
UNIQUE(draft_id,source_type,source_id) |
settlement_drafts therefore owns generated draft ID, YYYY-MM month, fixed month-end basis_date, active/completed/cancelled status, positive version, creator/timestamps/memo, create idempotency key+request fingerprint, correction parent/reason, approval actor/time/reason/reference/flags/fingerprint/version, final run ID and persisted response. The partial unique active-month constraint is shared by normal/correction drafts. settlement_draft_items owns stable target type/ID, inclusion/reason, selected valuation and selection mode/reason, item version/audit, frozen source evidence/hash and correction-child identity. Monetary authority is derived from eligible frozen evidence, not editable amount columns.
Modes are exact, carried_before, and manual_substitute. exact requires confirmed same-day evidence; carried_before requires a verified prior valuation and reason; manual_substitute requires explicit valuation ID, reason and review/approval even when valuation date is future. Preserve date_difference_days = valuation_date - basis_date; age_days retains its historical basis-minus-valuation meaning. Unverified candidates remain visible with candidate_amount but authority amount NULL. Missing is distinct from substitution/exclusion. Manual evidence freezes semantic fields and hash; only explicit refresh/reselection changes it.
Frozen/public evidence retains valuation_date, original recorded_at, derived date_difference_direction (after/before/same/unknown) and age_days; loan evidence retains representative_liability_definition. recorded_at may be later than month end while valuation_date is exactly month end. These names are preserved through finalized response, reports, timeseries and monthly coverage; date differences and semantic hashes are derived from frozen JSON rather than separate draft-item columns. Actor/version/timestamps of approval are cleared together on draft mutation.
Review fingerprint binds draft version, target/inclusion state, full valuation/manual semantic evidence, correction child, warnings, amounts/classification/date and prior run. Approved fingerprint/version become invalid on mutation or external drift. Finalize checks idempotency replay before active status, then validates version, approval, fingerprint, current evidence and correction parent, and inserts run+rows+draft completion/link in one BEGIN IMMEDIATE. Rollback on any SQLite failure; replay returns persisted response.
KRW only aggregation; non-KRW is warned/excluded, never converted. NULL is not zero; no targets yields provisional zero plus completeness and unregistered-liability warning, not a complete close. Existing snapshot rows preserve full frozen evidence, approval, date/mode/difference and actors; substitution, carried and approved-missing flags are orthogonal. monthly_source_snapshot_id remains a read-model alias, never a persistence field.
Issue #18 — schema 14 comprehensive settlement additions
asset_snapshot_runs remains the run header. Schema 13→14 adds settlement month, currency, six nullable legacy-compatible INTEGER totals (income_total, expense_total, net_cashflow, total_assets, total_liabilities, net_worth), cashflow_policy_json, aggregation_version, snapshot_schema_version, and source_snapshot_json; preserve existing basis/approval/draft/correction/idempotency fields. Old asset-only run totals remain NULL; no current-ledger backfill. Migration is additive/idempotent; old unfinished drafts require fresh comprehensive review/approval.
settlement_cashflow_snapshots: eight columns id, settlement_run_id, cashflow_kind, category, subcategory, amount, transaction_count, currency; cashflow_kind is income/expense/excluded, amount signed INTEGER, count positive, category fields NOT NULL (empty string is explicit), FK to asset_snapshot_runs.id with RESTRICT, unique (settlement_run_id,cashflow_kind,category,subcategory,currency), and run lookup index. Rows are grouped details for exactly one run.
Policy is stored on draft and header. Savings means only explicit category/subcategory pairs; transfer/voucher exclusion has priority; debt_payment is expense; refund is negative expense in refund month/original category. Unknown unresolved blocks approval; asset conditional flags cannot override it. KRW only/no FX. Source JSON freezes transaction values/classification/policy/decision and selected asset evidence/provenance. Six equations are validated against same-run details; excluded/duplicate/unknown amount do not sum. Header, both detail areas, evidence, completed draft linkage/response commit atomically; rollback all on failure. Runs are immutable; corrections append after latest-parent/single-child validation.