"""Server-side product analytics — OpenPanel Track API. Fire-and-forget business events (quote lifecycle, receipts, logins) so the OpenPanel dashboards show what actually happens inside the ERP, not just page views. Never raises: analytics must not break a business flow. Configuration (all optional — disabled when any is missing): PROCUREMENT_OPENPANEL_API_URL e.g. http://openpanel-api:3333 (self-hosted) PROCUREMENT_OPENPANEL_CLIENT_ID project client id (write mode) PROCUREMENT_OPENPANEL_CLIENT_SECRET project client secret Contract (OpenPanel Track API): POST {API_URL}/track with headers ``openpanel-client-id`` / ``openpanel-client-secret`` and a JSON body ``{"type": "track"|"identify", "profileId": ..., "event"/"properties": ...}``. """ import logging from typing import Any import httpx from app.config import settings logger = logging.getLogger("procurement.analytics") _TIMEOUT = httpx.Timeout(3.0) def _configured() -> bool: return bool( settings.OPENPANEL_API_URL and settings.OPENPANEL_CLIENT_ID and settings.OPENPANEL_CLIENT_SECRET ) async def track(event: str, properties: dict[str, Any] | None = None, profile_id: str | None = None) -> None: """Record a named event; swallows every error.""" if not _configured(): return body = { "type": "track", "event": event, "properties": properties or {}, } if profile_id: body["profileId"] = profile_id try: async with httpx.AsyncClient(timeout=_TIMEOUT) as client: await client.post( f"{settings.OPENPANEL_API_URL.rstrip('/')}/track", json=body, headers={ "openpanel-client-id": settings.OPENPANEL_CLIENT_ID, "openpanel-client-secret": settings.OPENPANEL_CLIENT_SECRET, }, ) except Exception as exc: # noqa: BLE001 — analytics must never raise logger.debug("analytics track failed (%s): %s", event, exc) async def identify(profile_id: str, properties: dict[str, Any] | None = None) -> None: """Create/update a user profile (e.g. on login).""" if not _configured(): return try: async with httpx.AsyncClient(timeout=_TIMEOUT) as client: await client.post( f"{settings.OPENPANEL_API_URL.rstrip('/')}/track", json={"type": "identify", "profileId": profile_id, "properties": properties or {}}, headers={ "openpanel-client-id": settings.OPENPANEL_CLIENT_ID, "openpanel-client-secret": settings.OPENPANEL_CLIENT_SECRET, }, ) except Exception as exc: # noqa: BLE001 logger.debug("analytics identify failed: %s", exc)