Overview
The analytics page loads data from a singleGET /analytics/dashboard endpoint that returns all Call-table-backed metrics (scalars + charts) in one request. A set of shared CTEs (Common Table Expressions) define base filtering once — all metric aggregations run against them.
8 additional endpoints serve data from non-Call-table sources (SMS, time saved, candidate satisfaction, campaigns launched, credits per candidate).
Key Files
| File | Purpose |
|---|---|
server/analytics/analytics_api.py | Dashboard endpoint + Pydantic response models |
server/analytics/analytics_filters.py | Shared filter parsing (ResolvedFilters) |
server/dao/call_dao/call_dao_analytics_base.py | 3 shared CTE builders |
server/dao/call_dao/call_dao_analytics_metrics.py | Metric DAO methods (counts, rates, charts) |
server/dao/call_dao/call_dao_analytics_passthrough.py | Passthrough rate + thumbs up/down DAO methods |
Shared CTEs
CallDaoAnalyticsBase provides 3 CTE builders that all analytics DAO methods inherit:
| CTE | Base Tables | Key Columns | Used By |
|---|---|---|---|
| Call | Call JOIN Campaign | call_id, candidate_id, campaign_id, call_status, call_length_sec, question_completion_rate, created | Total calls, answer rate, call completion, interviews, call length |
| CandidateInfo | CandidateCampaignUserReview JOIN Call JOIN Campaign | candidate_id, campaign_id, feedback, max_score | Passthrough rate, thumbs up/down, qualified candidates |
| Review | CandidateCampaignUserReview JOIN Call JOIN Campaign | candidate_id, feedback, call_last_reviewed_at | Thumbs up/down grouped by time |
org_id, date_range, campaign_ids, candidate_filter. All queries use the read-only replica via self.read_only_session().
Dashboard Endpoint
GET /analytics/dashboard returns an AnalyticsDashboardResponse containing:
- Scalar metrics: answer rate, call completion rate, total calls, interviews, people contacted, call length, avg interview length, avg time to first interview, opt-outs, qualified candidates, passthrough rate, thumbs up/down
- Chart data: 6 typed time-series charts (calls, candidates, interviews, thumbs up, thumbs down, passthrough rate) — each chart has its own Pydantic model (e.g.
CallsChartResponse,PassthroughRateChartResponse) - Withdrawal & accommodation: withdrawal rate, withdrawal reasons breakdown, accommodation rate
asyncio.gather, except qualified_candidates which runs sequentially (needs the passing score resolved first).
Per-Campaign Passing Scores
When filtering to a single campaign, the dashboard looks up that campaign’s custom passing score from Cosmos viacampaign_scripts_cosmos_dao.get_passing_scores(). This matches the behavior of the passthrough rate DAO, which also uses per-campaign scores internally.
Frontend
The frontend uses Orval-generated API client functions with Pydantic-backed TypeScript types. AdashboardRequestIdRef counter provides stale response protection — if a newer request is in flight, the older response is discarded.
Individual Endpoints
These 8 endpoints are separate from the dashboard because they use different data sources:| Endpoint | Data Source |
|---|---|
/analytics/sms_sent | SMS helper (Cosmos + external) |
/analytics/total_sms | SMS helper |
/analytics/time_saved | Calls + SMS + web calls + resume screening |
/analytics/total_time_saved | Same as above (scalar) |
/analytics/candidate_satisfaction | Call reviews (1-5 rating) |
/analytics/average_candidate_satisfaction | Same as above (scalar) |
/analytics/campaigns_launched_per_week | Campaign table (no filters) |
/analytics/credits_per_qualified_candidate | Credits + qualified candidates |
Adding a New Filter
A new filter only needs 3 touch-points — no per-metric changes:Parse in resolve_filters
Add the query param to
server/analytics/analytics_filters.py and include it in ResolvedFilters.Apply in one CTE builder
Add a
where clause in the relevant CTE method in server/dao/call_dao/call_dao_analytics_base.py. All metrics using that CTE now respect it automatically.