feat: implement asset snapshot and manual asset read model

This commit is contained in:
Jarca
2026-06-30 22:04:52 +09:00
parent 7fd1b45bd5
commit beddff7661
20 changed files with 1232 additions and 61 deletions
@@ -0,0 +1,40 @@
# BAT-008 Design
## Schema and persistence design
- Add `manual_assets` as a first-class table for non-transaction-derived assets/liabilities.
- Add `asset_snapshots` as an append-only history table keyed by basis date plus evaluated item identity.
- Migrations remain additive/idempotent; existing ledger/account flows must continue to operate when both tables are empty.
## Authority-source and valuation design
- Account-backed assets use `accounts.last_balance` as the live authority source.
- Non-transaction assets/liabilities use `manual_assets` as the live authority source.
- `asset_snapshots` is a historical record layer, not a competing primary input source.
- Net worth formula remains: asset buckets (`cash`, `investment`, `deposit`, `real_estate`, `other`) minus `loan`; missing valuations are excluded, not inferred.
## Read model design
- Service layer should expose:
- manual assets list/report helpers
- basis-date asset snapshot read helpers
- net-worth timeseries helpers
- shared valuation/fallback helpers consumed by both MCP and dashboard
- Snapshot read responses must expose basis date, bucket totals, liabilities, net worth, and inclusion/exclusion explanation fields.
- Manual assets read responses must expose item name, value, basis date, `liquidity_bucket`, and memo/source metadata.
## Dashboard alignment design
- `dashboard/app.py` should consume the same valuation helpers as the MCP read tools.
- If no manual assets/snapshots exist, dashboard behavior must safely fall back to REQ-005 account-based display.
- Excluded/unvalued items should be explainable instead of silently counted.
## Verification design
- Add targeted regression tests for:
- additive migration creation
- manual asset persistence and retrieval
- snapshot accumulation/read behavior
- net-worth formula / exclusion policy
- dashboard/MCP shared fallback behavior
- Prefer synthetic SQLite fixtures and temp DBs only.
@@ -0,0 +1,104 @@
# BAT-008 Implementation
## Plan summary
BAT-008 implemented the new valuation authority model in `service.py`/`db.py`, exposed read-only MCP tools in `server.py`, aligned the Streamlit overview with the shared valuation helper, and updated baseline docs to match the confirmed Discovery/REQ scope.
## Changed files
- `jarca_ledger_mcp/db.py`
- `SCHEMA_VERSION` → `9`
- additive/idempotent tables: `manual_assets`, `asset_snapshots`
- migration path updated so existing DBs create the new tables safely
- `jarca_ledger_mcp/service.py`
- shared valuation helpers for account/manual-asset/snapshot authority sources
- new service APIs: `create_manual_asset`, `list_manual_assets`, `create_asset_snapshot`, `get_asset_snapshot_report`, `get_net_worth_timeseries`
- `get_asset_liquidity_report` aligned to the same valuation policy while preserving REQ-005 fallback semantics
- `jarca_ledger_mcp/server.py`
- new read MCP tools: `list_manual_assets`, `get_asset_snapshot_report`, `get_net_worth_timeseries`
- `get_asset_liquidity_report` contract text updated for shared valuation source behavior
- `dashboard/app.py`
- overview asset section now explains the valuation authority source and shows `manual_assets` rows when present
- existing account-only fallback remains when new tables are empty
- Documentation
- `docs/data-model.md`
- `docs/interface-contract.md`
- `docs/runtime-flows.md`
- `docs/project-structure.md`
- Tests
- new: `tests/test_asset_snapshots.py`
- adjusted: `tests/test_service.py`
## Execution log
- 2026-06-30 21:26 KST: BAT-008 standard batch scaffold created from approved REQ-009/REQ-010.
- 2026-06-30 21:3x~21:4x KST: implementation landed in `db.py`, `service.py`, `server.py`, `dashboard/app.py`, docs, and regression tests via `jarcaledgerdev` handoff + local verification orchestration.
- 2026-06-30 21:40 KST: targeted compile/test validation passed.
- 2026-06-30 21:41 KST: MCP stdio contract smoke check passed and discovered the three new read tools.
- 2026-06-30 21:42 KST: synthetic end-to-end service probe confirmed live-authority fallback, snapshot read behavior, and net-worth timeseries output.
## Validation
### Command 1
```bash
cd /root/projects/jarca-ledger-mcp
source .venv/bin/activate
python -m py_compile jarca_ledger_mcp/*.py dashboard/app.py
pytest tests/test_asset_snapshots.py tests/test_service.py tests/test_contract_regression.py -q
```
Result:
- `................. [100%]`
- exit code `0`
### Command 2
```bash
cd /root/projects/jarca-ledger-mcp
source .venv/bin/activate
hermes mcp test jarca-ledger
```
Result:
- stdio connection success
- tools discovered: `51`
- new BAT-008 read tools discovered:
- `list_manual_assets`
- `get_asset_snapshot_report`
- `get_net_worth_timeseries`
### Command 3
```bash
cd /root/projects/jarca-ledger-mcp
source .venv/bin/activate
python - <<'PY'
from tempfile import TemporaryDirectory
from pathlib import Path
from jarca_ledger_mcp import service
with TemporaryDirectory() as td:
db_path = str(Path(td) / 'ledger.sqlite3')
service.create_ledger_account('현금', last_balance=1000000, last_synced_at='2026-06-30', liquidity_bucket='cash', db_path=db_path)
service.create_ledger_account('투자', type='investment', last_balance=2000000, liquidity_bucket='investment', db_path=db_path)
service.create_manual_asset('전세보증금', 300000000, '2026-06-30', 'real_estate', source_note='synthetic appraisal', db_path=db_path)
service.create_manual_asset('주담대', 120000000, '2026-06-30', 'loan', source_note='synthetic loan balance', db_path=db_path)
print(service.list_manual_assets(db_path=db_path))
print(service.get_asset_snapshot_report('2026-06-30', db_path=db_path))
service.create_asset_snapshot('2026-05-31', 'account', 'acct-a', '현금', 1000000, 'cash', source_note='synthetic', db_path=db_path)
service.create_asset_snapshot('2026-05-31', 'manual_asset', 'manual-home', '부동산', 200000000, 'real_estate', source_note='synthetic', db_path=db_path)
service.create_asset_snapshot('2026-05-31', 'manual_asset', 'manual-loan', '대출', 80000000, 'loan', source_note='synthetic', db_path=db_path)
print(service.get_asset_snapshot_report('2026-05-31', db_path=db_path))
print(service.get_net_worth_timeseries(start_date='2026-05-01', end_date='2026-06-30', db_path=db_path))
PY
```
Key result excerpts:
- `list_manual_assets` returned `count=2` and `authority_source='manual_assets'`
- live fallback snapshot report returned `source='live_authority'`, `total_assets=303000000`, `total_liabilities=120000000`, `net_worth=183000000`
- stored snapshot report returned `source='asset_snapshots'`, `snapshot_count=3`, `net_worth=121000000`
- timeseries returned `points[0].basis_date='2026-05-31'`, `points[0].net_worth=121000000`
## Remaining risks / follow-up notes
- Snapshot creation is intentionally service-only in BAT-008. MCP mutation exposure remains future scope by Discovery decision.
- Manual-asset edit/delete workflows and month-end operator UX are still procedural/document-driven rather than exposed as first-class CRUD flows.
- QC gate still needs to confirm REQ Implemented promotion before commit-stage handoff.
@@ -0,0 +1,36 @@
# BAT-008 Asset snapshots / manual assets / read MCP
- Profile: standard
- Status: release-candidate
- Included REQ: REQ-009, REQ-010
- Discovery: dcy-008
- Owner: 렛저장인
- Date (KST): 2026-06-30
## 목표
REQ-009/REQ-010에 따라 `asset_snapshots`/`manual_assets` 저장 모델을 additive/idempotent하게 도입하고, 월말 수동 스냅샷 정책을 수용하는 집계 계층과 조회성 MCP(기준일 스냅샷/순자산 시계열/수기 자산 조회)를 구현한다. 동시에 대시보드 자산/순자산 표시가 동일 권위 소스와 동일 산식을 사용하도록 정렬한다.
## 산출물
- `asset_snapshots`, `manual_assets` additive/idempotent schema migration
- 수기 자산 저장/조회, 스냅샷 누적, 순자산 시계열 집계 service 함수
- 조회성 MCP tool/응답 계약: 기준일 자산 스냅샷, 순자산 시계열, 수기 자산 조회
- `dashboard/app.py`의 자산/순자산 표시가 신규 모델과 안전 폴백을 함께 지원하도록 정렬
- `docs/data-model.md`, `docs/interface-contract.md`, `docs/project-structure.md`, `docs/runtime-flows.md` baseline 갱신
- BAT-008 planning/design/implementation/verification 문서와 synthetic-only 검증 evidence
## 범위 제한
- 외부 시세/부동산 자동 평가 연동
- 스냅샷 생성 mutation MCP
- 수기 자산 생성/수정/삭제 mutation MCP
- 투자 조언/리밸런싱/세금/수익률 계산
- 운영 DB 실데이터 기반 evidence
## 문서
- `planning.md`: delivery plan, dependency, risk, validation entry criteria
- `design.md`: schema/authority-source/fallback/read-contract design
- `implementation.md`: changed files, execution log, validation commands
- `verification.md`: AC mapping, PASS/FAIL, REQ Implemented decision
@@ -0,0 +1,37 @@
# BAT-008 Planning
## Scope
- Implement REQ-009: `asset_snapshots`/`manual_assets` valuation model, authority-source rule, month-end manual snapshot policy support.
- Implement REQ-010: asset snapshot read MCP, net-worth timeseries read MCP, manual assets read MCP, dashboard/MCP alignment.
- Preserve REQ-005 safety fallback when new tables are empty.
- Use synthetic/temp data only for tests and verification evidence.
## Dependencies
- dcy-008 confirmed Discovery and approved REQ-009/REQ-010.
- Existing REQ-005 dashboard liquidity/net-worth flow and `accounts.last_balance` authority-source baseline.
- Existing MCP server/service pattern in `jarca_ledger_mcp/server.py`, `jarca_ledger_mcp/service.py`, `jarca_ledger_mcp/db.py`.
- Additive/idempotent SQLite migration discipline and synthetic-only evidence policy.
## Work plan
1. Create BAT-008 standard batch docs and register entry.
2. Add schema/model support for `manual_assets` and `asset_snapshots` with safe empty-table fallback.
3. Add service-layer valuation helpers, snapshot accumulation/report functions, and dashboard alignment logic.
4. Add read-only MCP tool endpoints/contracts for snapshot read, net-worth timeseries, and manual assets read.
5. Update baseline docs (`docs/data-model.md`, `docs/interface-contract.md`, `docs/project-structure.md`, `docs/runtime-flows.md`).
6. Run narrow validation first (targeted tests / lint / smoke as applicable), then broader regression if narrow gates pass.
7. If evidence satisfies ACs, move BAT-008 to release-candidate and REQ-009/REQ-010 to Implemented.
## Design Gate
- Result: standard batch 유지.
- Reason: schema, service, MCP contract, dashboard alignment, baseline docs를 함께 변경하며 interface/data/fallback 설계가 필요하다.
## Risks and mitigations
- Authority-source drift between dashboard and MCP: mitigate by centralizing valuation helpers and writing shared regression tests.
- Empty new tables breaking existing REQ-005 flows: mitigate by explicit fallback tests and additive migrations only.
- Double counting between account-backed assets and manual assets: mitigate by clearly separating source responsibility and documenting inclusion/exclusion rules.
- Snapshot history ambiguity: mitigate by storing basis date, source type, and memo for every snapshot row.
@@ -0,0 +1,64 @@
# BAT-008 Verification
## Verification summary
Status: PASS
Evidence collected against synthetic/temp data only. No production ledger DB mutation or real financial files were used.
## Acceptance criteria mapping
### REQ-009
- AC-1: **PASS**
- Evidence: `tests/test_asset_snapshots.py::test_asset_snapshot_schema_is_additive_and_idempotent`
- `db.py` adds `manual_assets` and `asset_snapshots` via additive/idempotent schema + migration path.
- AC-2: **PASS**
- Evidence: `create_manual_asset`/`list_manual_assets` service path + synthetic probe output includes `name`, `valuation_amount`, `valuation_date`, `liquidity_bucket`, `source_note`.
- AC-3: **PASS**
- Evidence: `create_asset_snapshot` stores basis date/source target/amount/bucket/source note; `test_snapshot_rows_are_append_only_and_timeseries_uses_same_formula` proves append-only readback.
- AC-4: **PASS**
- Evidence: `valuation_policy` and `authority_source` fields distinguish `accounts.last_balance`, `manual_assets`, and `asset_snapshots`; docs updated in `docs/data-model.md` and `docs/runtime-flows.md`.
- AC-5: **PASS**
- Evidence: valuation helper formula is `asset_buckets - loan`; synthetic probe returned `303000000 - 120000000 = 183000000`; unvalued items are excluded rather than inferred.
- AC-6: **PASS**
- Evidence: `tests/test_asset_snapshots.py::test_asset_liquidity_report_empty_new_tables_preserves_req005_fallback` confirms empty new tables preserve REQ-005 path with `source='accounts.last_balance'`.
- AC-7: **PASS**
- Evidence: `docs/data-model.md`, `docs/runtime-flows.md`, and `docs/project-structure.md` now document the new tables, authority-source rule, and month-end snapshot flow.
### REQ-010
- AC-1: **PASS**
- Evidence: `server.py` exposes `get_asset_snapshot_report`; synthetic probe returned `basis_date`, per-bucket totals, liabilities, net worth, included/excluded items.
- AC-2: **PASS**
- Evidence: `server.py` exposes `get_net_worth_timeseries`; service/test coverage proves identical formula per basis date.
- AC-3: **PASS**
- Evidence: `server.py` exposes `list_manual_assets`; synthetic probe returned name/value/date/bucket/source note payloads.
- AC-4: **PASS**
- Evidence: `dashboard/app.py` caption explicitly states shared valuation helper; `service.py` centralizes calculation for dashboard + MCP.
- AC-5: **PASS**
- Evidence: same fallback regression test as REQ-009 AC-6 plus `get_asset_liquidity_report` preserves existing keys while adding optional explanatory fields.
- AC-6: **PASS**
- Evidence: `docs/interface-contract.md`, `docs/runtime-flows.md`, and `docs/project-structure.md` updated for read MCP contract and dashboard/MCP alignment.
## Requirement state decision
- REQ-009: Implemented promotion confirmed.
- REQ-010: Implemented promotion confirmed.
## Validation evidence
1. `python -m py_compile jarca_ledger_mcp/*.py dashboard/app.py` → pass
2. `pytest tests/test_asset_snapshots.py tests/test_service.py tests/test_contract_regression.py -q` → pass (`................. [100%]`)
3. `hermes mcp test jarca-ledger` → pass; stdio connected, 51 tools discovered, including the three new BAT-008 read tools
4. synthetic service probe → pass; confirmed live-authority fallback, stored-snapshot read path, and net-worth timeseries output
## Remaining issues
### Hard blockers
- None identified from the collected implementation evidence.
### Non-blocking follow-up
- jarcaledgerqc final re-check returned: `PASS, REQ-009/REQ-010은 이제 Implemented로 전환해도 됩니다.`
- fresh Streamlit browser smoke on `http://127.0.0.1:8511` rendered `schema v9`, `자산 유동성 현황`, and produced no browser console JS errors.
- Snapshot creation/manual-asset CRUD MCP mutations remain intentionally out of scope for BAT-008.
+1
View File
@@ -15,3 +15,4 @@
| BAT-005 | standard | release-candidate | REQ-005 | dcy-005 | [bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow](bat-005_20260629_dashboard-operations-hub-liquidity-monthly-cashflow/) |
| BAT-006 | standard | release-candidate | REQ-006 | dcy-006 | [bat-006_20260630_pytest-apptest-smoke-ci-automation](bat-006_20260630_pytest-apptest-smoke-ci-automation/) |
| BAT-007 | standard | release-candidate | REQ-007, REQ-008 | dcy-007 | [bat-007_20260630_mcp-response-contract-ci-versioning](bat-007_20260630_mcp-response-contract-ci-versioning/) |
| BAT-008 | standard | release-candidate | REQ-009, REQ-010 | dcy-008 | [bat-008_20260630_asset-snapshots-manual-assets-read-mcp](bat-008_20260630_asset-snapshots-manual-assets-read-mcp/) |