Files
call-center/scripts/track12_live_monitor.py
T

164 lines
5.0 KiB
Python

from __future__ import annotations
import json
import sqlite3
import time
from datetime import datetime, timezone
from pathlib import Path
from urllib.error import URLError
from urllib.request import Request, urlopen
ROOT = Path(__file__).resolve().parents[1]
DB_PATH = ROOT / ".data_local" / "mvp_cc.db"
OUT_DIR = ROOT / ".artifacts" / "track12-monitor"
OUT_DIR.mkdir(parents=True, exist_ok=True)
OUT_PATH = OUT_DIR / "latest.log"
BASE_URL = "http://127.0.0.1:8080"
AUTH_BASE_URL = "http://127.0.0.1:8001"
USERNAME = "operator"
PASSWORD = "op12345"
ROLE = "operator"
WINDOW_SECONDS = 180
POLL_SECONDS = 1.0
def iso_now() -> str:
return datetime.now(timezone.utc).astimezone().isoformat(timespec="seconds")
def write_line(handle, label: str, payload) -> None:
handle.write(f"[{iso_now()}] {label} {json.dumps(payload, ensure_ascii=False, sort_keys=True)}\n")
handle.flush()
def login_headers() -> dict[str, str]:
body = json.dumps(
{
"username": USERNAME,
"password": PASSWORD,
"role": ROLE,
}
).encode("utf-8")
req = Request(
f"{AUTH_BASE_URL}/auth/login",
headers={"Content-Type": "application/json"},
data=body,
method="POST",
)
with urlopen(req, timeout=10) as response:
payload = json.loads(response.read().decode("utf-8", errors="replace"))
token = str(payload.get("access_token") or "").strip()
if not token:
raise RuntimeError("auth/login did not return access_token")
return {"Authorization": f"Bearer {token}"}
def fetch_json(path: str, headers: dict[str, str]):
req = Request(
f"{BASE_URL}{path}",
headers=headers,
method="GET",
)
with urlopen(req, timeout=10) as response:
body = response.read().decode("utf-8", errors="replace")
return json.loads(body)
def latest_events(conn: sqlite3.Connection, limit: int = 6) -> list[dict]:
rows = conn.execute(
"""
select event_id, event_type, call_id, interaction_id, payload_json, created_at
from voice_events
order by id desc
limit ?
""",
(limit,),
).fetchall()
result = []
for row in rows:
payload = row["payload_json"]
try:
payload = json.loads(payload)
except Exception:
pass
result.append(
{
"event_id": row["event_id"],
"event_type": row["event_type"],
"call_id": row["call_id"],
"interaction_id": row["interaction_id"],
"payload": payload,
"created_at": row["created_at"],
}
)
return result
def latest_links(conn: sqlite3.Connection, limit: int = 4) -> list[dict]:
rows = conn.execute(
"""
select call_id, interaction_id, status, telephony_status, claimed_by_user,
operator_extension, started_at, connected_at, ended_at
from asterisk_call_links
order by id desc
limit ?
""",
(limit,),
).fetchall()
return [dict(row) for row in rows]
def main() -> int:
with OUT_PATH.open("w", encoding="utf-8") as handle:
write_line(handle, "monitor", {"status": "started", "window_seconds": WINDOW_SECONDS})
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
headers = login_headers()
write_line(handle, "auth", {"status": "ok", "user": USERNAME, "role": ROLE})
end_at = time.time() + WINDOW_SECONDS
seen = {
"live": None,
"recent": None,
"events": None,
"links": None,
}
try:
while time.time() < end_at:
try:
live = fetch_json("/proxy/asterisk-bridge/asterisk/live-calls", headers)
if live != seen["live"]:
seen["live"] = live
write_line(handle, "live-calls", live)
except URLError as exc:
write_line(handle, "live-calls-error", {"error": str(exc)})
try:
recent = fetch_json("/proxy/asterisk-bridge/asterisk/recent-calls", headers)
if recent != seen["recent"]:
seen["recent"] = recent
write_line(handle, "recent-calls", recent)
except URLError as exc:
write_line(handle, "recent-calls-error", {"error": str(exc)})
events = latest_events(conn)
if events != seen["events"]:
seen["events"] = events
write_line(handle, "voice-events", events)
links = latest_links(conn)
if links != seen["links"]:
seen["links"] = links
write_line(handle, "call-links", links)
time.sleep(POLL_SECONDS)
finally:
conn.close()
write_line(handle, "monitor", {"status": "finished"})
return 0
if __name__ == "__main__":
raise SystemExit(main())