new-site/scripts/workers/cdr_presets/kazoo.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

100 lines
3.5 KiB
Python

"""2600Hz Kazoo preset — REST API pull.
Kazoo exposes ``/v2/accounts/{account_id}/cdrs`` with bearer-token auth.
We page through and materialize as NDJSON; the generic_csv adapter with
a Kazoo-specific column map reads it back.
"""
from __future__ import annotations
import json
import logging
import urllib.request
from datetime import datetime, timedelta
from typing import Iterable, Optional
from .base import BasePreset, CredentialField, FetchedFile
logger = logging.getLogger(__name__)
class KazooPreset(BasePreset):
PRESET_SLUG = "kazoo"
LABEL = "2600Hz Kazoo"
CDR_FORMAT = "generic_csv"
TRANSPORT_METHOD = "api"
DEFAULT_CRON = "0 2 * * *"
CREDENTIAL_FIELDS = (
CredentialField("api_host", "Kazoo API host", "text",
help="e.g. https://api.example.com:8443"),
CredentialField("account_id", "Kazoo account ID", "text"),
CredentialField("auth_token", "Kazoo auth token", "password", sensitive=True,
help="Generated via the Kazoo user-auth API."),
)
FORMAT_CONFIG = {
"start_time": "timestamp",
"caller_number": "caller_id_number",
"called_number": "callee_id_number",
"duration_sec": "duration_seconds",
"billed_amount": "billing_seconds", # Kazoo doesn't bill here by default
"call_id": "call_id",
"trunk_group": "request",
}
def _request(self, url: str, token: str) -> bytes:
req = urllib.request.Request(
url, headers={"X-Auth-Token": token, "Accept": "application/json"},
)
with urllib.request.urlopen(req, timeout=30) as resp:
return resp.read()
def validate(self, profile_config: dict, secrets: dict) -> tuple[bool, str]:
try:
base = profile_config["api_host"].rstrip("/")
acct = profile_config["account_id"]
self._request(
f"{base}/v2/accounts/{acct}",
token=secrets["auth_token"],
)
return True, "Account endpoint reachable"
except Exception as exc:
return False, f"Kazoo validate failed: {exc}"
def fetch(
self,
profile_config: dict,
secrets: dict,
since: Optional[datetime],
) -> Iterable[FetchedFile]:
base = profile_config["api_host"].rstrip("/")
acct = profile_config["account_id"]
token = secrets["auth_token"]
since = since or (datetime.utcnow() - timedelta(days=1))
end = datetime.utcnow()
start_epoch = int(since.timestamp()) + 62167219200 # Kazoo uses gregorian epoch
end_epoch = int(end.timestamp()) + 62167219200
records: list[dict] = []
page_key: Optional[str] = None
while True:
qs = f"created_from={start_epoch}&created_to={end_epoch}&paginate=true&page_size=500"
if page_key:
qs += f"&start_key={page_key}"
body = self._request(f"{base}/v2/accounts/{acct}/cdrs?{qs}", token)
payload = json.loads(body)
items = payload.get("data", [])
records.extend(items)
page_key = payload.get("next_start_key")
if not page_key or not items:
break
if not records:
return
ndjson = "\n".join(json.dumps(r) for r in records).encode("utf-8")
yield FetchedFile(
remote_path=f"kazoo_cdrs_{end:%Y%m%dT%H%M%SZ}.ndjson",
mtime=end, content=ndjson, size_bytes=len(ndjson),
)