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>
This commit is contained in:
commit
f8cd37ac8c
1823 changed files with 145167 additions and 0 deletions
100
scripts/workers/cdr_presets/kazoo.py
Normal file
100
scripts/workers/cdr_presets/kazoo.py
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
"""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),
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue