Poll Alert Contacts — technical
Poll Alert Contacts — technical
app/im2.py: GET /settings/poll-contacts, POST /api/poll-contacts/add, POST /api/poll-contacts/{id}, POST /api/poll-contacts/route/{alert_key} (admin only; audit_log source poll_contacts_admin, sku POLL_CONTACT:{id} / POLL_ROUTE:{key}). Template templates/poll_contacts.html.
Tables (im2 database)
`` poll_alert_contact (contact_id pk, name, slack_user_id unique, active bool [legacy = any alert ticked], alert_fp, alert_bt, alert_qb_im2, alert_im2_fp bool, updated_by, updated_at) poll_alert_route (alert_key pk: FP|BT|QB_IM2|IM2_FP, slack_channel text '' = DM, updated_by, updated_at) ` Migration 2026-10-23: columns added, all four flags = old active; route rows seeded blank. Channel id validated ^[CG][A-Z0-9]{8,12}$ (accepts <#C…|name>` paste).
Consumers
skills/im2_app/scripts/alert_router.py — route(key) reads ticked ids + channel; send(key, text) posts to the channel with <@id> tags, falls back to DMs if the channel post fails, silent if nobody is ticked and no channel. Fallback to Dave when the table is unreadable.
ro_poll_runner.py—notify_sweep/alert→ keysFP/BT.im2_app/scripts/qb_refresh.py→QB_IM2(viapoll_log.notify); success, exceptions, crash.qb_fp_item_sync/scripts/sync_qb_items_to_fp.py→IM2_FP; success, failures, crash.check_auto_po.pyprintsPOLL_CONTACTS= ids with FP or BT ticked (DMs, stale watchdog).