[verified] Implement account valuation history for issue 12
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
# 계좌 평가 이력과 자산 마감 데이터 모델
|
||||
|
||||
## 목적과 범위
|
||||
|
||||
계좌 평가 증거, 수동 자산, 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_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
|
||||
- 식별자: `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 | 아니오 |
|
||||
|
||||
### 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만 추가한다. reconciliation은 calculated balance provenance와 `VALUATION_MANAGED` 상태를 보존하며 investment valuation 때문에 transaction을 생성하지 않는다. `schema_meta.schema_version`은 12다.
|
||||
|
||||
## 관계와 불변조건
|
||||
|
||||
### 관계
|
||||
|
||||
| 출발 엔터티 | 관계 | 대상 엔터티 | 카디널리티 | 필수 여부 | 소유권 | 삭제 규칙 |
|
||||
|---|---|---|---|---|---|---|
|
||||
| `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; idempotency index ensures one non-empty key per account.
|
||||
- only verified leaves are authority; unknown/future/unverified are excluded or warning evidence.
|
||||
- preview does not insert; close inserts run and rows atomically; latest run is report authority.
|
||||
- `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; no guessed migration or deletion
|
||||
- 하위 호환성: 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`
|
||||
@@ -259,3 +259,130 @@ Set-Cookie: __Host-jarca_dashboard_auth=; Max-Age=0; Secure; HttpOnly; SameSite=
|
||||
AppTest는 callback, query mapping, synthetic renderer output과 error/loading event를 검증하지만 실제 browser의 document navigation 부재, paint timing, focus/scroll, network request 또는 perceived shell continuity를 증명하지 않는다. 따라서 `REQ-002.03-AC01`은 authenticated real-browser evidence가 필수이며 AppTest는 companion evidence일 뿐이다. history/back-forward와 path URL은 계약에서 제외한다.
|
||||
|
||||
상세 acceptance target은 `docs/requirements/req-002_dashboard-menu-transition.md`의 `REQ-002.01-AC01`부터 `REQ-002.09-AC01`까지를 따른다.
|
||||
|
||||
## 계좌 평가·자산 마감 계약 — REQ-003
|
||||
|
||||
### valuation-service
|
||||
|
||||
- 책임: `register_account_valuation()`, `list_account_valuations()`, `select_account_valuation()`, `correct_account_valuation()`을 통해 exact/as_of verified leaf와 append-only history를 제공한다.
|
||||
- 종류·프로토콜: Python service function; `jarca_ledger_mcp.service` re-export
|
||||
- 호출 대상·operation: `jarca_ledger_mcp.application.account_valuation_service`
|
||||
- 버전: additive v1
|
||||
- 호출 주체: Dashboard accounts gateway, MCP tool, import/account write service
|
||||
- 인증 방식: service layer 자체 MCP auth는 주장하지 않음; Dashboard는 기존 `gate` 선행
|
||||
- 필요 권한: caller가 DB path와 approval context를 제공
|
||||
|
||||
#### 입력
|
||||
|
||||
| 필드 | 위치 | 타입 | 필수 여부 | 기본값 | 허용 값·형식 | 의미 | 민감정보 |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `account_id`/`basis_date`/`amount` | function/MCP | `string/string/int` | register 필수 | 없음 | account id, YYYY-MM-DD, exact integer | valuation identity/value | 아니오 |
|
||||
| `currency`/`valuation_kind`/`source_type`/`verification_status` | function/MCP | `string` | 선택 | KRW/balance/manual/unverified | actual enums in source | 아니오 |
|
||||
| `idempotency_key` | function/MCP | `string` | 선택 | `''` | stable caller key | retry identity | 아니오 |
|
||||
| `as_of_at`/`timezone`, optional metrics, loan definition | function/MCP | nullable string/int/float | 선택 | NULL | source validation; loan definition required | evidence metrics | 아니오 |
|
||||
|
||||
#### 출력
|
||||
|
||||
| 필드 | 위치 | 타입 | 필수 여부 | 허용 값·형식 | 의미 | 민감정보 |
|
||||
|---|---|---|---|---|---|---|
|
||||
| `success` | envelope | `bool` | 항상 | true/false | outcome | 아니오 |
|
||||
| `valuation`/`valuations` | result | object/list | 성공 시 | persisted rows | history/evidence | source_ref may be sensitive |
|
||||
| `reused`, `carried_forward`, `warning`, `unverified_candidates` | result | bool/string/list | 조건부 | additive | idempotency/selection evidence | 아니오 |
|
||||
|
||||
#### 오류
|
||||
|
||||
| 코드 | 분류 | 발생 조건 | 재시도 가능 여부 | 호출자 대응 | 실패 시 상태 변경 |
|
||||
|---|---|---|---|---|---|
|
||||
| `validation_error` | request | invalid date/type/currency/loan definition | 수정 후 가능 | payload 수정 | 없음 |
|
||||
| `not_found` | lookup | account/valuation missing | 수정 후 가능 | id 확인 | 없음 |
|
||||
| `idempotency_conflict` | conflict | same key different payload | 아니오 | new key or inspect | 없음 |
|
||||
| `correction_conflict`/`ambiguous_valuation` | conflict | second child/multiple leaves | 수동 검토 | correct evidence | 없음 |
|
||||
|
||||
### snapshot-close
|
||||
|
||||
- 책임: `preview_asset_snapshot_close()` is read-only; `close_asset_snapshot()` writes an immutable run and rows.
|
||||
- 종류·프로토콜: Python service, Streamlit `assets_gateway`, MCP tools
|
||||
- 호출 대상·operation: `jarca_ledger_mcp.application.assets_service`
|
||||
- 버전: additive v1
|
||||
- 호출 주체: authenticated Dashboard operator or MCP caller
|
||||
- 인증 방식: Dashboard existing auth gate; MCP authentication is outside this contract
|
||||
- 필요 권한: close conditional approval fields when warnings require it
|
||||
|
||||
#### 입력
|
||||
|
||||
| 필드 | 위치 | 타입 | 필수 여부 | 기본값 | 허용 값·형식 | 의미 | 민감정보 |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `basis_date`/`selection_policy` | function/MCP/form | `string/string` | basis optional | today/exact | YYYY-MM-DD, exact/as_of | close scope | 아니오 |
|
||||
| `idempotency_key`/`correction_reason` | function/MCP/form | `string` | conditional | empty | stable key; reason on re-close | retry/correction | 아니오 |
|
||||
| `allow_missing_exclusions`/`allow_as_of_carry_forward` | function/MCP | `bool` | optional | false | explicit flags | conditional approval need | 아니오 |
|
||||
| `approved_by`/`approval_reason`/`approval_reference` | function/MCP/form | `string` | conditional | empty | all required together | approval evidence | 아니오 |
|
||||
|
||||
#### 출력
|
||||
|
||||
| 필드 | 위치 | 타입 | 필수 여부 | 허용 값·형식 | 의미 | 민감정보 |
|
||||
|---|---|---|---|---|---|---|
|
||||
| `source`/`is_closed_snapshot`/`snapshot_run_id` | result | string/bool/string | success | live_authority/snapshot | preview vs close | 아니오 |
|
||||
| `close_status`/`selection_policy`/`warnings` | result | string/string/list | success | complete_exact/incomplete_draft/conditional | authority state | 아니오 |
|
||||
| `total_assets`/`total_liabilities`/`net_worth` | result | integer | success | exact integer | formula summary | 아니오 |
|
||||
|
||||
#### 오류
|
||||
|
||||
| 코드 | 분류 | 발생 조건 | 재시도 가능 여부 | 호출자 대응 | 실패 시 상태 변경 |
|
||||
|---|---|---|---|---|---|
|
||||
| `conditional_approval_required` | policy | unsafe exclusions/carry-forward without explicit approval | 수정 후 가능 | preview, approve explicitly | 없음 |
|
||||
| `idempotency_conflict` | conflict | close key payload differs | 아니오 | inspect run | 없음 |
|
||||
| `validation_error` | request | invalid policy or unreasoned correction | 수정 후 가능 | provide reason | no partial rows |
|
||||
|
||||
### shared contract and adapter limits
|
||||
|
||||
All new interfaces retain the existing `success`/`error_code` envelope. `preview_asset_snapshot_close()` and Dashboard render/report are read-only; only explicit register/update/close forms or MCP calls write. MCP tools are `register_account_valuation`, `list_account_valuations`, `select_account_valuation`, `correct_account_valuation`, `preview_asset_snapshot_close`, `close_asset_snapshot`, `get_asset_snapshot_report`, and `get_net_worth_timeseries`; actual decorators are in `jarca_ledger_mcp/adapters/mcp/server.py`. Dashboard uses `accounts_gateway.py`, `assets_gateway.py`, and `page_views/accounts.py`/`assets.py`.
|
||||
|
||||
## 호환성 추가 규칙 — REQ-003
|
||||
|
||||
- 유지할 기존 호출 방식: REQ-001 auth config/login/cleanup/logout/gate, REQ-002 exact 10 menu labels/order, `page` query sibling preservation, stable shell/background and browser evidence; existing MCP stdio/streamable-http runtime.
|
||||
- 버전 변경 정책: additive fields only; no rename/removal of existing response keys.
|
||||
- 폐기 정책: 없음; guessed migration, fake transactions and MCP auth claim are excluded.
|
||||
- 재시도 계약: same idempotency payload reuses; changed payload conflicts; DB conflict does not retry blindly.
|
||||
- 중복 처리 계약: valuation/run idempotency keys and single correction indexes.
|
||||
- 순서 보장: Dashboard auth → DB path → page/gateway; preview → explicit approval → atomic close; selection → report.
|
||||
|
||||
## 예시 추가
|
||||
|
||||
### example-valuation — synthetic verified valuation
|
||||
|
||||
- 대상 인터페이스: `valuation-service`
|
||||
|
||||
#### 호출
|
||||
|
||||
```text
|
||||
register_account_valuation(account_id="acct-synthetic", basis_date="2026-06-30", amount=1000000, verification_status="verified", idempotency_key="fixture-v1")
|
||||
```
|
||||
|
||||
#### 결과
|
||||
|
||||
```text
|
||||
{"success": true, "reused": false, "valuation": {"basis_date": "2026-06-30", "amount": 1000000, "verification_status": "verified"}}
|
||||
```
|
||||
|
||||
### example-preview — read-only close preview
|
||||
|
||||
- 대상 인터페이스: `snapshot-close`
|
||||
|
||||
#### 호출
|
||||
|
||||
```text
|
||||
preview_asset_snapshot_close(basis_date="2026-06-30", selection_policy="exact")
|
||||
```
|
||||
|
||||
#### 결과
|
||||
|
||||
```text
|
||||
{"success": true, "source": "live_authority", "is_closed_snapshot": false, "close_status": "complete_exact", "warnings": []}
|
||||
```
|
||||
|
||||
## 검증 추가
|
||||
|
||||
- 정상 계약: `tests/test_account_valuations.py`, `tests/test_asset_snapshots.py`, `tests/test_contract_regression.py`
|
||||
- 실패 계약: idempotency/correction/conditional approval tests and source validation
|
||||
- 경계 계약: exact/as_of, NULL/0, future/unverified candidate, correction/re-close
|
||||
- 구현·스키마·예시 정합성: `tests/test_requirement_docs.py`, actual service/MCP/gateway symbols
|
||||
|
||||
@@ -118,3 +118,51 @@ deploy/k8s/README.md
|
||||
|
||||
- 정적 분석 기대 결과: exit 0 with no diagnostics
|
||||
- 문서와 실제 트리의 정합성: every listed source/test/document path exists; dependency table has no forbidden reverse edge; REQ-002 symbols resolve to `dashboard/app.py`, `dashboard/common.py`, and named tests
|
||||
|
||||
## REQ-003 additive structure
|
||||
|
||||
### ledger-application — shared valuation and snapshot services
|
||||
|
||||
```text
|
||||
jarca_ledger_mcp/
|
||||
├── application/account_valuation_service.py
|
||||
├── application/assets_service.py
|
||||
├── application/accounts_write_services.py
|
||||
├── application/imports_service.py
|
||||
├── application/reconciliation_service.py
|
||||
├── infrastructure/sqlite/migrations.py
|
||||
├── adapters/mcp/server.py
|
||||
└── adapters/streamlit/{accounts_gateway.py,assets_gateway.py}
|
||||
```
|
||||
|
||||
| 경로 | 종류 | 책임 | 공개 진입점 | 내부 구현 경계 | 편집 정책 | 생성 주체 |
|
||||
|---|---|---|---|---|---|---|
|
||||
| `jarca_ledger_mcp/application/account_valuation_service.py` | source | validation, selection, idempotency, correction | named valuation functions | SQLite through shared db | source review + focused tests | developer |
|
||||
| `jarca_ledger_mcp/application/assets_service.py` | source | preview, formula, close/correction | `preview_asset_snapshot_close`, `close_asset_snapshot` | snapshot transaction | source review + focused tests | developer |
|
||||
| `jarca_ledger_mcp/infrastructure/sqlite/migrations.py` | source | schema 12 additive migration | `init_db`, `migrate_db` | no UI/MCP imports | migration review | developer |
|
||||
| `tests/test_account_valuations.py` | test | valuation contracts | pytest | synthetic DB | paired with source | developer |
|
||||
| `tests/test_asset_snapshots.py` | test | preview/close/report contracts | pytest | synthetic DB | paired with source | developer |
|
||||
| `docs/data-model.md` | canonical doc | entity/schema authority | document | no secrets | update with schema | developer |
|
||||
|
||||
### 의존 방향 추가
|
||||
|
||||
| 출발 영역 | 대상 영역 | 허용 여부 | 규칙 | 공유 계약 위치 |
|
||||
|---|---|---|---|---|
|
||||
| `dashboard.page_views` | `streamlit gateways` | 허용 | page → gateway only | `docs/interface-contract.md` |
|
||||
| `streamlit gateways` | `application services` | 허용 | adapter delegates; no SQL | `docs/runtime-flow.md` |
|
||||
| `MCP adapters` | `application services` | 허용 | tool delegates; no duplicate business logic | `docs/interface-contract.md` |
|
||||
| `application services` | `infrastructure.sqlite` | 허용 | service → shared db/schema | `docs/data-model.md` |
|
||||
| `application services` | `dashboard` | 금지 | no reverse UI dependency | `docs/project-structure.md` |
|
||||
|
||||
### 변경 규칙 추가
|
||||
|
||||
| 구분 | 규칙 |
|
||||
|---|---|
|
||||
| 새 모듈·파일 배치 | valuation/snapshot logic stays in application; adapters remain thin; no direct SQL in page/MCP |
|
||||
| 이름·패키지 | existing snake_case modules and service re-exports |
|
||||
| 소스·테스트 대응 | every valuation/snapshot change paired with named synthetic tests and docs validator |
|
||||
| 생성 산출물 | only redacted synthetic evidence; no credentials, cookie, token or actual account data |
|
||||
|
||||
## REQ-003 preservation inventory
|
||||
|
||||
REQ-001 auth gate, cookie/cleanup/logout and auth-before-DB; REQ-002 menu labels/order, query sibling preservation, stable shell/background and browser evidence remain in the preceding sections. Missing semantic items: 0. Ambiguous semantic items: 0.
|
||||
|
||||
@@ -0,0 +1,302 @@
|
||||
---
|
||||
{
|
||||
"schema": "gitea-agent.requirement/v1",
|
||||
"id": "REQ-003",
|
||||
"title": "계좌 기준일 평가 이력과 자산 마감 연동",
|
||||
"revision": "1.0",
|
||||
"status": "approved",
|
||||
"depends_on": [],
|
||||
"applies_to": ["ledger", "dashboard", "mcp"],
|
||||
"language": "ko",
|
||||
"issue": "#12",
|
||||
"implementation_pr": "미정"
|
||||
}
|
||||
---
|
||||
|
||||
# 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를 표시한다.
|
||||
- **대표 부채**: `valuation_kind`가 `loan`/`loan_balance`이고 `representative_liability_definition`이 `principal_only` 또는 `principal_interest_fees`인 하나의 대표 금액만 합산한다.
|
||||
- **정본 구현**: `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`다. correction은 원본을 갱신하지 않고 단일 child를 append하며 다른 child는 `correction_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를 분리한다. 늦게 도착한 historical row는 current를 과거로 되돌리지 않으며 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는 need-based explicit `approved_by`, `approval_reason`, `approval_reference`와 flags를 요구하며 immutable run evidence를 기록한다. unverified/future/unknown/ambiguous는 승인 없이 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을 만들지 않는다. regular ledger calculation은 `calculation_basis`/`calculation_basis_at` provenance를 유지한다. monthly settlement는 read-only로 동일 snapshot/report를 전파한다.
|
||||
|
||||
**영향 범위**
|
||||
- 인터페이스: `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
|
||||
|
||||
### 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`.
|
||||
|
||||
### 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()`.
|
||||
|
||||
### 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`.
|
||||
|
||||
### 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.
|
||||
|
||||
### 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`.
|
||||
|
||||
### 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()`.
|
||||
|
||||
### 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.
|
||||
|
||||
### 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`.
|
||||
|
||||
### 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.
|
||||
|
||||
### 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.
|
||||
|
||||
## 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: 미정
|
||||
- 관련 설계 문서: `docs/data-model.md`, `docs/interface-contract.md`, `docs/project-structure.md`, `docs/runtime-flow.md`
|
||||
- 관련 요구사항: `REQ-001`, `REQ-002`
|
||||
@@ -143,3 +143,76 @@ ASGI startup부터 login, token restoration, invalid-cookie cleanup, explicit lo
|
||||
### transition verification boundary
|
||||
|
||||
`tests/test_dashboard_menu_transition.py` provides exact AppTest/unit evidence for labels, query preservation, fallback/direct access, loading, local error, latest selection, and cache-clear. `tests/test_dashboard_app.py::test_streamlit_apptest_core_pages_use_synthetic_fixture_db` and `tests/test_dashboard_authentication_app.py::test_valid_cookie_allows_dashboard_and_is_not_stored_in_session_state` cover finance/auth regressions. AppTest is synthetic and cannot establish actual browser document continuity, paint timing, focus/scroll, or network behavior; `scripts/streamlit_browser_evidence.py --mode transition`의 authenticated HTTPS frame/DOM/document/shell evidence가 REQ-002.03-AC01에 필수다. `tests/test_streamlit_browser_evidence.py`는 이 manifest가 실제 teardown 이후에만 성공하고 document navigation, shell identity drift, 누락 관찰 또는 secret 포함 시 fail-closed하는지 검증한다.
|
||||
|
||||
## REQ-003 런타임 흐름
|
||||
|
||||
### valuation-register — register/select/correct
|
||||
|
||||
- 시작 이벤트: account form, MCP call, account/import write with dated evidence
|
||||
- 선행조건: DB initialized; Dashboard path passed auth gate; account exists
|
||||
- 실행 방식: synchronous SQLite transaction
|
||||
- 트랜잭션·영속성 경계: one valuation insert or no write on validation/conflict
|
||||
|
||||
| 단계 ID | 실행 주체 | 시작 조건·입력 | 처리 | 읽기·쓰기 상태 | 외부 호출 | 성공 시 다음 단계 | 실패 시 전이 |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `valuation-01` | adapter | typed request | delegate shared service | read account | none | `valuation-02` | `valuation-fail` |
|
||||
| `valuation-02` | account valuation service | basis/amount/scope | validate exact integer, date, purpose, loan definition | no write | none | `valuation-03` | `valuation-fail` |
|
||||
| `valuation-03` | SQLite | valid payload | reuse idempotent row or insert append-only row | account_valuations write | SQLite | `valuation-04` | `valuation-fail` |
|
||||
| `valuation-04` | selector/report | requested basis | select verified leaf exact/as_of; warn candidates | read only | none | `valuation-success` | `valuation-fail` |
|
||||
|
||||
### snapshot-preview-close — preview/conditional close
|
||||
|
||||
- 시작 이벤트: assets page or MCP preview/close
|
||||
- 선행조건: basis date; auth before Dashboard DB; explicit approval for conditional state
|
||||
- 실행 방식: preview synchronous read; close synchronous atomic write
|
||||
- 트랜잭션·영속성 경계: preview no write; close writes run and rows together
|
||||
|
||||
| 단계 ID | 실행 주체 | 시작 조건·입력 | 처리 | 읽기·쓰기 상태 | 외부 호출 | 성공 시 다음 단계 | 실패 시 전이 |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `close-01` | assets service | basis/policy | gather verified account/manual items and warnings | read accounts/valuations/manual_assets | none | `close-02` | `close-fail` |
|
||||
| `close-02` | Dashboard/MCP | preview request | return live authority and status | no write | none | `close-end` | `close-fail` |
|
||||
| `close-03` | operator/service | explicit close + approval if needed | enforce conditions and correction reason | transaction begins | none | `close-04` | `close-fail` |
|
||||
| `close-04` | SQLite | approved payload | insert run and immutable snapshot rows | runs/snapshots write | SQLite | `close-end` | `close-fail` |
|
||||
|
||||
### import-candidate-reconciliation-monthly-ui — propagation
|
||||
|
||||
- import updates transactions/account balance then records only dated unverified candidate; reconciliation uses valuation-managed investment without fake transaction; monthly/UI reads shared reports and explicit form actions only.
|
||||
|
||||
| 단계 ID | 실행 주체 | 시작 조건·입력 | 처리 | 읽기·쓰기 상태 | 외부 호출 | 성공 시 다음 단계 | 실패 시 전이 |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `prop-01` | import/account write | real dated balance | `_record_*_valuation_candidate` | account_valuations unverified insert | parser | `prop-02` | `prop-fail` |
|
||||
| `prop-02` | reconciliation service | account baseline | calculate signed ledger with provenance; investment verified => VALUATION_MANAGED | read only | none | `prop-03` | `prop-fail` |
|
||||
| `prop-03` | monthly/UI page | month/basis query | read snapshot/report and render | read only unless explicit button | Streamlit | `prop-end` | `prop-fail` |
|
||||
|
||||
## 실패와 복구 추가
|
||||
|
||||
| 대상 단계 | 실패 조건 | 추가 쓰기 중단 | 재시도 계약 | 중복 방지 | 보상·수동 복구 | 최종 상태 |
|
||||
|---|---|---|---|---|---|---|
|
||||
| `valuation-03` | idempotency/correction conflict | yes | same payload inspect/reuse; changed payload no blind retry | unique indexes | operator chooses new correction | `valuation-fail` |
|
||||
| `close-03` | missing/future/unverified/unknown or missing approval | yes | preview then explicit approval | close idempotency key | manual evidence review | `close-fail` |
|
||||
| `close-04` | SQLite exception/concurrency | transaction rollback | retry same key after lock resolution | unique run key | restore from backup if needed | `close-fail` |
|
||||
| `prop-01` | no real dated evidence | yes | no candidate write | source hash/date key | wait for evidence | `prop-end` without candidate |
|
||||
|
||||
### 동시성과 순서 추가
|
||||
|
||||
- 동시 실행 허용 여부: independent reads; SQLite writes serialized by transaction
|
||||
- 잠금·직렬화 단위: account valuation/idempotency and snapshot close transaction
|
||||
- 순서 보장: auth → DB → read/preview → explicit approval → close; latest run selected after correction
|
||||
- 충돌 검출·처리: unique idempotency/single correction indexes and explicit error codes
|
||||
|
||||
## 관찰 가능성 추가
|
||||
|
||||
| 신호 종류 | 이름 | 발생 단계·조건 | 상관 식별자 | 진단 속성 | 민감정보 처리 |
|
||||
|---|---|---|---|---|---|
|
||||
| result/event | `valuation_registered` | `valuation-03` success | request/idempotency key surrogate | basis/kind/status only | no source secret |
|
||||
| result/event | `snapshot_previewed` | `close-02` | request correlation | basis/status/warning codes | no account number |
|
||||
| result/event | `snapshot_closed` | `close-04` | snapshot_run_id | status/count/formula | evidence redacted |
|
||||
| result/event | `valuation_conflict` | validation/idempotency failure | correlation ID | error_code only | no payload secrets |
|
||||
|
||||
## REQ-003 검증
|
||||
|
||||
- 정상 흐름: `tests/test_account_valuations.py`, `tests/test_asset_snapshots.py`, `tests/test_contract_regression.py`
|
||||
- 실패 흐름: conflict, conditional approval, validation tests
|
||||
- 경계 흐름: exact/as_of, stale/carry-forward, correction/re-close, empty fallback
|
||||
- 동시성 흐름: SQLite unique/idempotency and transaction rollback review
|
||||
- 운영 진입점과 기대 결과: Dashboard authenticated `자산/마감`/`계좌`, MCP tools; preview leaves row count unchanged, explicit close returns run id
|
||||
|
||||
Reference in New Issue
Block a user