"""Database persistence for signal engine outputs. Persists ``SignalOutput`` instances to the ``signal_engine_outputs`` table. Persistence failures are logged and swallowed — they never block signal emission to the trading queue. Requirements: 15.1, 15.4 """ from __future__ import annotations import json import logging import asyncpg from services.signal_engine.models import SignalOutput logger = logging.getLogger(__name__) # INSERT statement for the signal_engine_outputs table. _INSERT_SQL = """ INSERT INTO signal_engine_outputs ( id, ticker, evaluated_at, price, heuristic_verdict, heuristic_confidence, heuristic_s_total, probabilistic_verdict, probabilistic_p_up, probabilistic_entropy, probabilistic_ev_r, delta_agreement, delta_confidence_delta, delta_reasons, trade_plan, full_output, exit_signals, pipeline_mode, shadow_mode ) VALUES ( $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19 ) """ async def persist_signal_output( pool: asyncpg.Pool, output: SignalOutput, ) -> None: """Persist a SignalOutput to the signal_engine_outputs table. Logs and continues on database errors (non-blocking). Requirements: 15.1, 15.4 """ try: trade_plan_json: str | None = None if output.trade_plan is not None: trade_plan_json = json.dumps(output.trade_plan.model_dump()) exit_signals_json = json.dumps( [e.model_dump() for e in output.exit_signals] ) delta_reasons_json = json.dumps(output.delta_reasons) full_output_json = output.model_dump_json() await pool.execute( _INSERT_SQL, output.output_id, # $1 id output.ticker, # $2 ticker output.timestamp, # $3 evaluated_at output.price, # $4 price output.heuristic_verdict, # $5 heuristic_verdict output.heuristic_confidence, # $6 heuristic_confidence output.heuristic_s_total, # $7 heuristic_s_total output.probabilistic_verdict, # $8 probabilistic_verdict output.probabilistic_p_up, # $9 probabilistic_p_up output.probabilistic_entropy, # $10 probabilistic_entropy output.probabilistic_ev_r, # $11 probabilistic_ev_r output.delta_agreement, # $12 delta_agreement output.delta_confidence_delta, # $13 delta_confidence_delta delta_reasons_json, # $14 delta_reasons (JSONB) trade_plan_json, # $15 trade_plan (JSONB, nullable) full_output_json, # $16 full_output (JSONB) exit_signals_json, # $17 exit_signals (JSONB) output.pipeline_mode, # $18 pipeline_mode output.shadow_mode, # $19 shadow_mode ) logger.debug( "Persisted signal output %s for %s", output.output_id, output.ticker, ) except Exception: logger.error( "Failed to persist signal output %s for %s — continuing", output.output_id, output.ticker, exc_info=True, )