# MediaRouter Analytics & Insights ## Status The current working tree implements the bounded Analytics service at `app/analytics/`, exposes `/v1/analytics` routes, and provides frontend, TypeScript/Python SDK, MCP, and n8n transport surfaces. PostgreSQL schema, RLS certification, Docker/Hugging Face runtime verification, and provider certification remain deferred until all planned product phases are complete. ## Architecture Phase 10 extends the authoritative Social analytics boundary. Provider calls remain behind `SocialProviderAdapter.get_metrics`, OAuth execution remains inside `OAuthService`/`TokenService`, and credentials never enter analytics records or transports. The flow is: `REST/MCP/SDK/n8n -> AnalyticsDomainService -> AnalyticsRepository -> Social AnalyticsService -> provider adapter -> normalized snapshots` Workspace and project dashboards read durable normalized snapshots. They never call providers directly and never synthesize missing metrics. ## Database Migration `app/social/migrations/0010_analytics_insights.sql` is additive and creates: - `analytics_sync_runs` - `analytics_metric_snapshots` - `analytics_post_metrics` - `analytics_platform_metrics` Common values are normalized into typed columns. JSON dimensions are bounded provider-specific metadata, not complete provider responses. Snapshot uniqueness is workspace + provider + external object + metric + bucket. All four tables enable and force PostgreSQL RLS using the transaction-local `app.workspace_id`. Social post, target, and account foreign keys enforce authoritative publishing relationships. Project references are verified through `ProjectService`; project and Social databases may be independently configured, so an unsafe cross-database foreign key is not introduced. ## Permissions and rate limits - `analytics:read` reads dashboards, posts, capabilities, and sync status. - `analytics:sync` queues or cancels synchronization. - `analytics:export` is reserved for a future explicitly bounded export API. Viewer roles receive read access. Operator and developer roles receive sync access. Analytics reads use the established analytics rate-limit category; sync mutations use a stricter bounded category. SDK, MCP, and n8n calls pass through the same authentication, authorization, and rate limits. ## Metrics and capabilities The common metric vocabulary currently includes views, impressions, likes, comments, shares, and engagement rate. A provider advertises these controls only when its existing registered analytics capability is enabled. Missing values stay `null`. Unsupported and unsynchronized states are distinct from zero. Provider limitations remain explicit: - YouTube channel analytics requires a separate authorized product flow. - Facebook and Instagram analytics require explicit analytics consent/scopes. - TikTok metrics require a resolvable public video identity. - X is limited to authorized public metrics. - LinkedIn support depends on member/organization authorization. - Telegram and WhatsApp analytics remain unsupported unless their registered adapters acquire verified provider contracts. No provider certification claim is made by this phase. ## Synchronization `POST /v1/analytics/sync` creates a durable idempotent run and returns `202`. The worker atomically claims bounded runs and follows: `queued -> running -> succeeded | partial | failed | cancelled` Retries use bounded exponential backoff with jitter. A run is partial when some targets return authoritative metrics and others are unavailable. Existing external post IDs connect MediaRouter publishing targets to provider metrics. No manual or heuristic post mapping is used. Audit events are bounded: - `analytics.sync_requested` - `analytics.sync_started` - `analytics.sync_completed` - `analytics.sync_failed` - `analytics.sync_cancelled` They contain IDs, status, provider, and counts only. ## API - `GET /v1/analytics/capabilities` - `GET /v1/analytics/overview` - `GET /v1/analytics/timeseries` - `GET /v1/analytics/platforms` - `GET /v1/analytics/platforms/{provider}` - `GET /v1/analytics/posts` - `GET /v1/analytics/posts/{post_id}` - `GET /v1/analytics/projects/{project_id}` - `GET /v1/analytics/sync-runs` - `GET /v1/analytics/sync-runs/{run_id}` - `POST /v1/analytics/sync-runs/{run_id}/cancel` - `POST /v1/analytics/sync` Date ranges are limited to 366 days, pagination is bounded, sorting uses a closed enum, and timezones must be valid IANA names. UTC is returned explicitly when no authoritative workspace timezone is supplied. ## Frontend `/analytics` and `/projects/[projectId]/analytics` use the Workspace shell, TanStack Query, authenticated BFF, existing cards and chart containers. UI controls are capability-driven. Empty, unavailable, unsupported, error, loading, partial, and freshness states are represented without zero-filling. Search can deep-link authoritative analytics post results. Commands include Open Analytics, View Top Content, and permission-gated Sync Analytics. ## SDK, MCP, and n8n The TypeScript and Python clients expose `client.analytics` with capabilities, overview, time series, platforms, posts, project analytics, sync, sync status, sync listing, and cancellation. MCP exposes narrow typed analytics tools rather than arbitrary query or provider execution. The official n8n Social node uses the SDK for overview, time series, platform, project, top-content, sync, and sync-status operations. ## Runtime status Provider live certification, PostgreSQL/RLS runtime verification, Docker, Hugging Face startup, production OpenAPI verification, and final production certification are intentionally **DEFERRED** until all planned MediaRouter product phases are complete.