File indexing completed on 2026-07-22 09:42:01
0001 """Helpers over the epicprod action stream for alarm detectors.
0002
0003 The action stream is the structured record of epicprod actions in
0004 ``swf_applog`` (``app_name='epicprod'``, structured fields in ``extra_data``)
0005 — see swf-monitor docs/EPICPROD_ACTION_STREAM.md. Detectors reach it through
0006 ``client.db_conn``, the engine's psycopg connection attached in run.py.
0007 """
0008 from __future__ import annotations
0009
0010
0011 def latest_action(conn, action: str, *, outcome: str | None = None):
0012 """Newest action record as a dict (timestamp, extra_data), or None."""
0013 sql = (
0014 "SELECT timestamp, extra_data FROM swf_applog "
0015 "WHERE app_name = 'epicprod' AND extra_data->>'action' = %s"
0016 )
0017 params = [action]
0018 if outcome is not None:
0019 sql += " AND extra_data->>'outcome' = %s"
0020 params.append(outcome)
0021 sql += " ORDER BY id DESC LIMIT 1"
0022 with conn.cursor() as cur:
0023 cur.execute(sql, params)
0024 return cur.fetchone()
0025
0026
0027 def action_count(conn, action: str, *, minutes: int) -> int:
0028 """Count of action records in the trailing window."""
0029 with conn.cursor() as cur:
0030 cur.execute(
0031 "SELECT count(*) AS n FROM swf_applog "
0032 "WHERE app_name = 'epicprod' AND extra_data->>'action' = %s "
0033 "AND timestamp >= now() - make_interval(mins => %s)",
0034 [action, minutes])
0035 row = cur.fetchone()
0036 return int(row["n"]) if row else 0