new-site/scripts/alert.py
justin f8cd37ac8c Initial commit — Performance West telecom compliance platform
Includes: API (Express/TypeScript), Astro site, Python workers,
document generators, FCC compliance tools, Canada CRTC formation,
Ansible infrastructure, and deployment scripts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-27 06:54:22 -05:00

140 lines
4.4 KiB
Python

"""
alert.py — Shared alerting module for Performance West monitor scripts.
Creates an ERPNext Issue when a posting account is broken or automation fails.
Import and call: alert_account_broken(monitor, platform, error)
"""
import json
import urllib.request
import urllib.parse
import os
from pathlib import Path
# ERPNext config — reads from env
ERPNEXT_URL = os.environ.get("ERPNEXT_URL", "http://erpnext:8080")
ERPNEXT_API_KEY = os.environ.get("ERPNEXT_API_KEY", "")
ERPNEXT_API_SECRET = os.environ.get("ERPNEXT_API_SECRET", "")
# Fallback: use Express API to create issues if ERPNext is unreachable
API_URL = os.environ.get("PW_API_URL", "http://api:3001")
WEBHOOK_SECRET = os.environ.get("WEBHOOK_SECRET", "")
# Only create one alert per monitor+platform per day
_ALERT_STATE_FILE = Path.home() / ".monitor-alert-state.json"
def _load_alert_state():
if _ALERT_STATE_FILE.exists():
try:
return json.loads(_ALERT_STATE_FILE.read_text())
except Exception:
pass
return {}
def _save_alert_state(state):
_ALERT_STATE_FILE.write_text(json.dumps(state, indent=2))
def alert_account_broken(monitor: str, platform: str, error: str, detail: str = ""):
"""
Create an ERPNext Issue alerting that a posting account is broken.
Args:
monitor: Script name e.g. "reddit-monitor", "formation-worker"
platform: Platform name e.g. "Reddit", "Wyoming SOS Portal"
error: Short error description
detail: Optional longer detail / stack trace
"""
from datetime import datetime, timezone
today = datetime.now(timezone.utc).strftime("%Y-%m-%d")
key = f"{monitor}:{platform}:{today}"
state = _load_alert_state()
if state.get(key):
return
subject = f"[Monitor Alert] {platform}{monitor}: {error[:80]}"
description = (
f"The **{monitor}** script detected a failure on **{platform}**.\n\n"
f"**Error:** {error}\n\n"
f"**Detail:**\n```\n{detail or 'No additional detail'}\n```\n\n"
f"**Action required:** Check the account credentials / API key for {platform} "
f"and update the configuration.\n\n"
f"**Date:** {today}"
)
issue_name = None
# Try ERPNext first
if ERPNEXT_API_KEY and ERPNEXT_API_SECRET:
issue_name = _create_erpnext_issue(subject, description)
# Fallback: Express API internal endpoint
if not issue_name:
issue_name = _create_api_issue(subject, description)
if issue_name:
print(f"[alert] ERPNext Issue '{issue_name}' created for {platform} failure")
state[key] = {"issue_name": issue_name, "error": error}
_save_alert_state(state)
else:
print(f"[alert] Failed to create alert for {platform} failure: {error}")
def _create_erpnext_issue(subject: str, description: str) -> str | None:
"""Create an Issue in ERPNext directly via REST API."""
payload = json.dumps({
"data": json.dumps({
"doctype": "Issue",
"subject": subject,
"description": description,
"issue_type": "Bug",
"priority": "High",
})
}).encode()
req = urllib.request.Request(
f"{ERPNEXT_URL}/api/resource/Issue",
data=payload,
headers={
"Authorization": f"token {ERPNEXT_API_KEY}:{ERPNEXT_API_SECRET}",
"Content-Type": "application/json",
},
method="POST",
)
try:
with urllib.request.urlopen(req, timeout=10) as r:
resp = json.loads(r.read())
return resp.get("data", {}).get("name", "")
except Exception as e:
print(f"[alert] ERPNext Issue creation failed: {e}")
return None
def _create_api_issue(subject: str, description: str) -> str | None:
"""Fallback: create a ticket via our Express API."""
payload = json.dumps({
"category": "issue",
"subject": subject,
"message": description,
"email": "alerts@performancewest.net",
"name": "System Monitor",
}).encode()
req = urllib.request.Request(
f"{API_URL}/api/v1/tickets",
data=payload,
headers={"Content-Type": "application/json"},
method="POST",
)
try:
with urllib.request.urlopen(req, timeout=10) as r:
resp = json.loads(r.read())
return resp.get("ticket_id", "")
except Exception as e:
print(f"[alert] Express API ticket creation failed: {e}")
return None