"""Alert evaluation and history persistence.""" from __future__ import annotations from typing import Mapping from app.models.alerts import AlertEvent, AlertHistoryRepository, AlertStatus from app.models.portfolio import PortfolioConfig def build_portfolio_alert_context( config: PortfolioConfig, *, spot_price: float, source: str, updated_at: str, ) -> dict[str, float | str]: gold_units = float(config.gold_ounces or 0.0) live_gold_value = gold_units * spot_price loan_amount = float(config.loan_amount) margin_call_ltv = float(config.margin_threshold) return { "spot_price": float(spot_price), "gold_units": gold_units, "gold_value": live_gold_value, "loan_amount": loan_amount, "ltv_ratio": loan_amount / live_gold_value if live_gold_value > 0 else 0.0, "net_equity": live_gold_value - loan_amount, "margin_call_ltv": margin_call_ltv, "margin_call_price": loan_amount / (margin_call_ltv * gold_units) if gold_units > 0 else 0.0, "quote_source": source, "quote_updated_at": updated_at, } class AlertService: def __init__(self, history_path=None) -> None: self.repository = AlertHistoryRepository(history_path=history_path) def evaluate( self, config: PortfolioConfig, portfolio: Mapping[str, object], *, persist: bool = True ) -> AlertStatus: history = self.repository.load() if persist else [] ltv_ratio = float(portfolio.get("ltv_ratio", 0.0)) spot_price = float(portfolio.get("spot_price", 0.0)) updated_at = str(portfolio.get("quote_updated_at", "")) if ltv_ratio >= float(config.margin_threshold): severity = "critical" message = ( f"Current LTV {ltv_ratio:.1%} is above the critical threshold of " f"{float(config.margin_threshold):.1%}." ) elif ltv_ratio >= float(config.ltv_warning): severity = "warning" message = ( f"Current LTV {ltv_ratio:.1%} is above the warning threshold of " f"{float(config.ltv_warning):.1%}." ) else: severity = "ok" message = "LTV is within configured thresholds." preview_history: list[AlertEvent] = [] if severity != "ok": event = AlertEvent( severity=severity, message=message, ltv_ratio=ltv_ratio, warning_threshold=float(config.ltv_warning), critical_threshold=float(config.margin_threshold), spot_price=spot_price, updated_at=updated_at, email_alerts_enabled=bool(config.email_alerts), ) if persist: if self._should_record(history, event): history.append(event) self.repository.save(history) else: preview_history = [event] return AlertStatus( severity=severity, message=message, ltv_ratio=ltv_ratio, warning_threshold=float(config.ltv_warning), critical_threshold=float(config.margin_threshold), email_alerts_enabled=bool(config.email_alerts), history=( preview_history if not persist else list(reversed(self.repository.load() if severity != "ok" else history)) ), ) @staticmethod def _should_record(history: list[AlertEvent], event: AlertEvent) -> bool: if not history: return True latest = history[-1] return latest.severity != event.severity