Files
momentum-system/telemetry/events.py
momentum devandClaude Opus 4.8 007cd95c79 Baseline before Postgres migration (WS0)
Snapshot before the DuckDB->PostgreSQL migration. data/, archive/, the 232MB
rebuild zip, the Windows .venv, and .env are excluded via .gitignore.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 23:39:29 -04:00

103 lines
2.8 KiB
Python

"""Telemetry event emission and querying for runtime observability."""
from __future__ import annotations
import json
import logging
import uuid
from enum import Enum
logger = logging.getLogger(__name__)
class TelemetryEventType(str, Enum):
SOURCE_DEGRADED = "SOURCE_DEGRADED"
SOURCE_RECOVERED = "SOURCE_RECOVERED"
SOURCE_COOLDOWN = "SOURCE_COOLDOWN"
WATCHLIST_ADD = "WATCHLIST_ADD"
WATCHLIST_DROP = "WATCHLIST_DROP"
CANDIDATE_STATE_CHANGE = "CANDIDATE_STATE_CHANGE"
FETCH_ATTEMPT = "FETCH_ATTEMPT"
ENRICHMENT_QUEUED = "ENRICHMENT_QUEUED"
ENRICHMENT_DONE = "ENRICHMENT_DONE"
ENRICHMENT_FAILED = "ENRICHMENT_FAILED"
def emit_event(
con,
event_type: TelemetryEventType | str,
session_id: str | None = None,
symbol: str | None = None,
source: str | None = None,
from_state: str | None = None,
to_state: str | None = None,
reason: str | None = None,
metadata: dict | None = None,
) -> None:
event_id = str(uuid.uuid4())
event_type_val = (
event_type.value
if isinstance(event_type, TelemetryEventType)
else str(event_type)
)
metadata_json = json.dumps(metadata) if metadata is not None else None
try:
con.execute(
"""
INSERT INTO telemetry_events
(id, event_type, session_id, symbol, source, from_state, to_state, reason, metadata_json)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
[
event_id,
event_type_val,
session_id,
symbol,
source,
from_state,
to_state,
reason,
metadata_json,
],
)
except Exception:
logger.exception(
"telemetry emit_event failed — event_type=%s symbol=%s",
event_type_val,
symbol,
)
def query_events(
con,
session_id: str | None = None,
symbol: str | None = None,
source: str | None = None,
limit: int = 100,
) -> list[dict]:
clauses: list[str] = []
params: list = []
if session_id is not None:
clauses.append("session_id = ?")
params.append(session_id)
if symbol is not None:
clauses.append("symbol = ?")
params.append(symbol)
if source is not None:
clauses.append("source = ?")
params.append(source)
where = ("WHERE " + " AND ".join(clauses)) if clauses else ""
sql = f"SELECT * FROM telemetry_events {where} ORDER BY created_at DESC LIMIT ?"
params.append(limit)
rows = con.execute(sql, params).fetchall()
cols = [
desc[0]
for desc in con.execute(
f"SELECT * FROM telemetry_events {where} LIMIT 0", params[:-1]
).description
]
return [dict(zip(cols, row)) for row in rows]