feat(core-B): 운영자 sync/verify CLI + cold-start onboarding (BAT-051, REQ-105/106, PR #32)
dcy-034 마지막 조각. sync(슬롯 qty←브로커+warning해제+OQ-A 전이엣지+FR-7 평단seed)/verify(C 재대조·강제해제없음). core-A QC minor 2건 정리 동봉. QC(opus) PASS, 683 passed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VHtM5Ws1HKCKQ4JGi6GRb5
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
"""portfolio-hub read HTTP adapter (REQ-027 AC-1/AC-4).
|
||||
"""portfolio-hub read HTTP adapter (REQ-027 AC-1/AC-4; REQ-105 FR-7 cost-basis).
|
||||
|
||||
Matches the live portfolio-hub HTTP contract:
|
||||
GET /v1/user_info -> UserInfoResponse {"daemon_id": "...", ...}
|
||||
@@ -119,3 +119,29 @@ class PortfolioHubReadAdapter:
|
||||
strategy_params=dict(raw.get("strategy_params") or {}),
|
||||
))
|
||||
return out
|
||||
|
||||
def get_cost_basis(self, portfolio_id: str) -> "Optional[float]":
|
||||
"""REQ-105 FR-7: GET /v1/portfolios/{id}/holdings/cost-basis -> avg_cost (WAC).
|
||||
|
||||
Returns None on any error (graceful degrade — caller logs WARN and skips
|
||||
avg_price seed). portfolio-hub REQ-043 must be live for this to succeed.
|
||||
"""
|
||||
url = f"{self._base_url}/v1/portfolios/{portfolio_id}/holdings/cost-basis"
|
||||
try:
|
||||
resp = self._get(url)
|
||||
except Exception: # noqa: BLE001 — network/timeout — degrade
|
||||
return None
|
||||
if not (200 <= resp.status_code < 300):
|
||||
return None
|
||||
try:
|
||||
body = resp.json()
|
||||
except ValueError:
|
||||
return None
|
||||
if isinstance(body, dict):
|
||||
val = body.get("avg_cost") or body.get("wac") or body.get("cost_basis")
|
||||
if val is not None:
|
||||
try:
|
||||
return float(val)
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
return None
|
||||
|
||||
@@ -67,3 +67,22 @@ class PostgreSQLReconcileWarningStore:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute("SELECT slot_id FROM slot_reconcile_warning")
|
||||
return {row[0] for row in cur.fetchall()}
|
||||
|
||||
def get_warning_info(self, slot_id: str) -> dict | None:
|
||||
"""Return {"portfolio_id", "case_code", "actor", "reason"} or None (REQ-105/106)."""
|
||||
with self._psycopg.connect(self._dsn) as conn:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"SELECT portfolio_id, case_code, actor, reason "
|
||||
"FROM slot_reconcile_warning WHERE slot_id = %s LIMIT 1",
|
||||
(slot_id,),
|
||||
)
|
||||
row = cur.fetchone()
|
||||
if row is None:
|
||||
return None
|
||||
return {
|
||||
"portfolio_id": row[0],
|
||||
"case_code": row[1],
|
||||
"actor": row[2],
|
||||
"reason": row[3],
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user