from __future__ import annotations from typing import Literal from sqlalchemy import func, select from sqlalchemy.orm import Session from app.core.config import get_settings from app.models.outbox import OutboxEvent from app.schemas import N8nIntegrationStatus settings = get_settings() def derive_n8n_status(db: Session) -> N8nIntegrationStatus: counts: dict[str, int] = dict( db.execute( select(OutboxEvent.delivery_status, func.count()).group_by(OutboxEvent.delivery_status) ).all() # type: ignore[arg-type] ) pending = counts.get("pending", 0) delivering = counts.get("delivering", 0) failed = counts.get("failed", 0) succeeded = counts.get("succeeded", 0) latest_success_at = db.scalar( select(func.max(OutboxEvent.updated_at)).where(OutboxEvent.delivery_status == "succeeded") ) latest_failure_at = db.scalar( select(func.max(OutboxEvent.updated_at)).where(OutboxEvent.delivery_status == "failed") ) state: Literal["disabled", "unavailable", "degraded", "operational", "no_evidence"] if not settings.n8n_dispatch_enabled: state = "disabled" elif failed > 0 and succeeded == 0: state = "unavailable" elif failed > 0: state = "degraded" elif succeeded > 0 or pending > 0 or delivering > 0: state = "operational" else: state = "no_evidence" return N8nIntegrationStatus( configured=bool(settings.n8n_webhook_url), dispatch_enabled=settings.n8n_dispatch_enabled, state=state, pending=pending, delivering=delivering, failed=failed, succeeded=succeeded, latest_success_at=latest_success_at, latest_failure_at=latest_failure_at, )