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:
@@ -1,7 +1,8 @@
|
||||
#!/usr/bin/env python3
|
||||
"""매월 1일 04:30 — 본인 계좌 잔액을 골디 inbox에 envelope로 떨어뜨린다.
|
||||
"""매월 1일 04:30 — 본인+가희 계좌 잔액을 골디 inbox에 envelope로 떨어뜨린다.
|
||||
|
||||
가희 계좌는 제외 (label prefix '가희_'). 본인 계좌별 평가액(kt00018)·예수금(kt00001)·총자산을 집계.
|
||||
소유자별(본인 self / 가희 gahee, label prefix '가희_'로 판별)로 평가액(kt00018)·예수금(kt00001)·
|
||||
총자산을 집계해 payload.by_owner에 담는다. 골디는 self→증권(효원), gahee→증권(가희)로 reconcile.
|
||||
LLM 불필요 — launchd로 실행. 실패 시 레이 텔레그램으로 자가 알림.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
@@ -26,7 +27,7 @@ INBOX_DIR = Path('/Users/snowoyh/.openclaw/agents/budget/inbox/incoming')
|
||||
CONFIG_PATH = Path('/Users/snowoyh/.openclaw/openclaw.json')
|
||||
TELEGRAM_ACCOUNT = 'stock'
|
||||
TOPIC = 'securities_balance'
|
||||
SCHEMA_VERSION = 1
|
||||
SCHEMA_VERSION = 2
|
||||
GAHEE_PREFIX = '가희_'
|
||||
|
||||
|
||||
@@ -60,13 +61,12 @@ def send_telegram(text: str) -> bool:
|
||||
return ok
|
||||
|
||||
|
||||
def collect_owner_accounts() -> list[dict]:
|
||||
def collect_accounts() -> list[dict]:
|
||||
accounts = kw.list_accounts()
|
||||
out = []
|
||||
for a in accounts:
|
||||
label = a['label']
|
||||
if label.startswith(GAHEE_PREFIX):
|
||||
continue
|
||||
owner = 'gahee' if label.startswith(GAHEE_PREFIX) else 'self'
|
||||
balance = kw.get_balance(label)
|
||||
positions = kw.get_positions(label)
|
||||
eval_amount = sum(p.get('evlt_amt', 0) for p in positions)
|
||||
@@ -75,6 +75,7 @@ def collect_owner_accounts() -> list[dict]:
|
||||
deposit = balance.get('d2_entra', 0)
|
||||
out.append({
|
||||
'label': label,
|
||||
'owner': owner,
|
||||
'account_no': a.get('account_no', ''),
|
||||
'deposit': deposit,
|
||||
'eval_amount': eval_amount,
|
||||
@@ -86,6 +87,12 @@ def collect_owner_accounts() -> list[dict]:
|
||||
|
||||
def build_message(accounts: list[dict]) -> dict:
|
||||
now = datetime.now(KST)
|
||||
by_owner: dict[str, dict] = {}
|
||||
for a in accounts:
|
||||
o = by_owner.setdefault(a['owner'], {'deposit': 0, 'eval_amount': 0, 'total': 0})
|
||||
o['deposit'] += a['deposit']
|
||||
o['eval_amount'] += a['eval_amount']
|
||||
o['total'] += a['total']
|
||||
return {
|
||||
'message_id': str(uuid.uuid4()),
|
||||
'from': 'stock',
|
||||
@@ -95,8 +102,9 @@ def build_message(accounts: list[dict]) -> dict:
|
||||
'schema_version': SCHEMA_VERSION,
|
||||
'payload': {
|
||||
'as_of': now.strftime('%Y-%m-%d'),
|
||||
'owner_scope': 'self_only',
|
||||
'owner_scope': 'self_and_gahee',
|
||||
'accounts': accounts,
|
||||
'by_owner': by_owner,
|
||||
'totals': {
|
||||
'deposit': sum(a['deposit'] for a in accounts),
|
||||
'eval_amount': sum(a['eval_amount'] for a in accounts),
|
||||
@@ -117,9 +125,9 @@ def write_message(msg: dict) -> Path:
|
||||
|
||||
def main() -> int:
|
||||
try:
|
||||
accounts = collect_owner_accounts()
|
||||
accounts = collect_accounts()
|
||||
if not accounts:
|
||||
raise RuntimeError('본인 계좌 0개 — 자격증명 또는 prefix 필터 점검 필요')
|
||||
raise RuntimeError('계좌 0개 — 자격증명 점검 필요')
|
||||
msg = build_message(accounts)
|
||||
path = write_message(msg)
|
||||
total = msg['payload']['totals']['total']
|
||||
|
||||
Reference in New Issue
Block a user