feat(budget): 가희 증권 자산 후잉·결산 반영 (securities_balance v2)

- send_balance_to_budget.py: 본인+가희 계좌 소유자별(by_owner) 집계, schema v1→v2
- inbox_handler.py: OWNER_ASSET 맵으로 소유자별 reconcile(self→증권(효원)/gahee→증권(가희)),
  asset_name 기반 출력, v2 payload 검증 추가(v1 하위호환 유지)
- monthly_settlement.py: 텔레그램 reconcile 라인 소유자별 라벨
- INBOX_TOPICS.md / CLAUDE.md: v2 스키마 문서화

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
hyowons
2026-07-02 18:51:15 +09:00
parent 213c0e0797
commit 300a4c9ca9
5 changed files with 148 additions and 80 deletions
+30 -20
View File
@@ -11,44 +11,53 @@ OpenClaw 에이전트 간 파일 기반 통신의 topic 카탈로그. envelope
### `securities_balance`
- **방향:** `stock``budget`
- **목적:** 본인 증권 계좌별 잔액(평가액·예수금·총자산)을 골디에게 전달, 월간 결산 입력으로 사용
- **목적:** 본인+가희 증권 계좌별 잔액(평가액·예수금·총자산)을 소유자별로 골디에게 전달, 월간 결산 입력으로 사용
- **트리거:** macOS launchd `ai.openclaw.stock.send-balance` — 매월 1일 04:30 KST. 골디 결산 cron(05:00) 30분 전. (`inbox_handler.py``as_of` 가드는 1·10·20일을 허용 — 과거 운영 호환 안전망)
- **schema_version:** 1
- **payload 스키마:**
- **schema_version:** 2 (v1 하위호환 유지 — 수신자 `SUPPORTED_SCHEMA={1,2}`)
- **payload 스키마 (v2):**
```json
{
"as_of": "2026-05-01",
"owner_scope": "self_only",
"as_of": "2026-07-01",
"owner_scope": "self_and_gahee",
"accounts": [
{
"label": "일반",
"owner": "self",
"account_no": "",
"deposit": 118743,
"eval_amount": 63177225,
"total": 63295968,
"position_count": 4
"deposit": 56032,
"eval_amount": 114294705,
"total": 114350737,
"position_count": 7
},
{ "label": "ISA", ... }
{ "label": "ISA", "owner": "self", ... },
{ "label": "가희_일반", "owner": "gahee", ... },
{ "label": "가희_ISA", "owner": "gahee", ... }
],
"by_owner": {
"self": { "deposit": 146362, "eval_amount": 188964561, "total": 189110923 },
"gahee": { "deposit": 138469, "eval_amount": 22587331, "total": 22725800 }
},
"totals": {
"deposit": 843705,
"eval_amount": 114519255,
"total": 115362960
"deposit": 284831,
"eval_amount": 211551892,
"total": 211836723
}
}
```
- **owner_scope:** 현재 `self_only`만 발행 (가희 계좌 제외 — `가희_` prefix 라벨 자동 필터). 향후 가희 포함 버전 필요해지면 `with_gahee` 등 새 값 도입
- **owner_scope:** v2는 `self_and_gahee` — 본인·가희 모두 발행. `accounts[].owner``self`|`gahee` (`가희_` prefix 라벨이 gahee). 소유자 집계는 `by_owner`. `totals` 는 참고용 grand total. (v1은 `self_only`·`by_owner` 없음, 단일 소유자로 처리)
- **수신자(골디) 처리 동작:**
- 월간결산 cron 진입부에서 `inbox_handler.process_inbox()` 호출 (fetch_balance 이전 — 분개가 후잉 잔액에 반영되어 결산이 분개 후 스냅샷을 보도록)
- payload의 `totals.total` 과 후잉 자산 `증권(효원)` 의 차액을 계산:
- 차액 > 0 → 차변 `증권(효원)` / 대변 `주식평가수익` 자동 분개
- 차액 < 0 → 차변 `주식평가손실` / 대변 `증권(효원)` 자동 분개
- `by_owner` 의 각 소유자 total 과 후잉 자산(`self``증권(효원)`, `gahee``증권(가희)`)의 차액을 소유자별로 reconcile:
- 차액 > 0 → 차변 `증권(효원|가희)` / 대변 `주식평가수익` 자동 분개 (손익 계정은 소유자 공용)
- 차액 < 0 → 차변 `주식평가손실` / 대변 `증권(효원|가희)` 자동 분개
- `|차액| < 1만원` → 노이즈로 간주, 분개 skip (processed 처리)
- `|차액| > 1억원` → 안전 가드 발동, 분개 거부 + 텔레그램 alert + `failed/` 이동
- 처리 후 `processed/`로 이동, `state/inbox_state.json``processed[]` 에 message_id 누적 (idempotency, 최근 1000개)
- 결산 메일 본문에 `## 인박스 reconcile` 섹션, 텔레그램에 한 줄 요약 추가
- 후잉에 해당 자산 항목 없으면 `skipped` (분개 안 함)
- 소유자 하나라도 rejected/journal_failed 면 `failed/` 이동·미처리. 재실행 시 이미 분개된 소유자는 fresh 조회로 차액≈0 aligned → 중복분개 없음 (reconcile는 목표값으로 맞추기라 자연 idempotent)
- 정상 처리 후 `processed/`로 이동, `state/inbox_state.json``processed[]` 에 message_id 누적 (idempotency, 최근 1000개)
- 결산 메일 본문에 `## 인박스` reconcile 섹션(소유자별 라인), 텔레그램에 한 줄 요약 추가. 메일 "자산 계정별 변동"은 후잉 자산 전수 순회라 `증권(가희)`도 자동 표시
- **GC / 적체 정책 (월간결산 cron 진입부에서 매월 자동 수행):**
- `processed/` 의 mtime 30일 초과 envelope 자동 삭제 (`gc_processed`)
- `failed/` 적체가 5건 이상이면 결산 메일·텔레그램에 ⚠️ alert 한 줄 추가 — 사람이 검토 후 수동 삭제 (자동 삭제 안 함, CLAUDE.md 원칙 준수)
@@ -57,8 +66,9 @@ OpenClaw 에이전트 간 파일 기반 통신의 topic 카탈로그. envelope
- envelope 키 누락 / `to != budget` / 미등록 topic / 미지원 `schema_version`
- payload 키 누락 (`as_of`, `accounts`, `totals`, `owner_scope`)
- `as_of` 가 매월 1·10·20일이 아님
- `totals.{deposit,eval_amount,total}` 음수 또는 100억 초과
- `totals`/`by_owner.*``{deposit,eval_amount,total}` 음수 또는 100억 초과
- `accounts[].total` 합계가 `totals.total` 과 불일치
- `by_owner` 에 미등록 소유자 (self/gahee 외) 또는 구조 오류
- 차액이 1억원 초과 (분개 거부)
- 후잉 webhook 분개 실패
- **관련 스크립트:**