feat(15-02): add trend detection and alert evaluation scheduled tasks

- Create trend_detector.py: hourly 7d vs 14d signal comparison per active link
- Create alert_evaluator_site.py: 5-min evaluation of 4 rule types with hysteresis
- Wire both tasks into lifespan with non-fatal startup and cancel on shutdown

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jason Staack
2026-03-19 07:16:06 -05:00
parent d4cf36b200
commit c3ae48eb0c
3 changed files with 418 additions and 0 deletions

View File

@@ -340,11 +340,41 @@ async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
except Exception as exc:
logger.warning("winbox reconcile loop could not start (non-fatal)", error=str(exc))
# Start signal trend detection loop (hourly)
trend_task: Optional[asyncio.Task] = None # type: ignore[type-arg]
try:
from app.services.trend_detector import trend_detection_loop
trend_task = asyncio.create_task(trend_detection_loop())
except Exception as exc:
logger.warning("trend detection loop could not start (non-fatal)", error=str(exc))
# Start site alert evaluation loop (every 5 minutes)
alert_eval_task: Optional[asyncio.Task] = None # type: ignore[type-arg]
try:
from app.services.alert_evaluator_site import alert_evaluation_loop
alert_eval_task = asyncio.create_task(alert_evaluation_loop())
except Exception as exc:
logger.warning("alert evaluation loop could not start (non-fatal)", error=str(exc))
logger.info("startup complete, ready to serve requests")
yield
# Shutdown
logger.info("shutting down TOD API")
if trend_task and not trend_task.done():
trend_task.cancel()
try:
await trend_task
except asyncio.CancelledError:
pass
if alert_eval_task and not alert_eval_task.done():
alert_eval_task.cancel()
try:
await alert_eval_task
except asyncio.CancelledError:
pass
if winbox_reconcile_task and not winbox_reconcile_task.done():
winbox_reconcile_task.cancel()
try: