Epic 15: Admin Statistics & Bottleneck Identification¶
Overview¶
Этот эпик не содержит отдельного введения — контент начинается сразу с фич. Смотрите описания фич ниже.
Business Value¶
Описание бизнес-ценности см. в разделе «Обзор».
Features and User Stories¶
15.1 Event Timestamp Infrastructure¶
User Story:
As a system I want to have a centralised event log in PostgreSQL So that every time-sensitive action on the platform generates a timestamped record that can be queried for analytics and alerting
Описание: This user story creates the data foundation for the entire epic. A new EventLog model records who did what, on which object, and when. A companion AlertThreshold model stores per-metric time limits that the product team can edit from Django Admin. Django signals call a shared log_event() helper at the right points in the application lifecycle. A backfill management command populates historical records from existing model data. Acceptance Criteria EventLog model Fields: event_type (CharField with choices), user (FK to AUTH_USER_MODEL, nullable), job (FK, nullable), application (FK, nullable), offer (FK, nullable), placement (FK, nullable), thread (FK, nullable), actor_role ('manager' or 'candidate'), metadata (JSONField for extra context), occurred_at (DateTimeField, indexed), created_at (auto_now_add) Composite indexes on (event_type, user, occurred_at), (event_type, job, occurred_at), and (event_type, application, occurred_at) Registered in Django Admin with list_display, list_filter on event_type and actor_role, and search by user email AlertThreshold model Fields: metric_key (unique CharField), label, warning_hours (nullable PositiveIntegerField), critical_hours (PositiveIntegerField), is_active (BooleanField, default True), notes, updated_at (auto_now) Registered in Django Admin with list_editable on warning_hours, critical_hours, and is_active - thresholds must be adjustable without a code deployment Seeded with initial values for all metrics listed in user story
Критерии приёмки: - EventLog model - Fields: event_type (CharField with choices), user (FK to AUTH_USER_MODEL, nullable), job (FK, nullable), application (FK, nullable), offer (FK, nullable), placement (FK, nullable), thread (FK, nullable), actor_role ('manager' or 'candidate'), metadata (JSONField for extra context), occurred_at (DateTimeField, indexed), created_at (auto_now_add) - Composite indexes on (event_type, user, occurred_at), (event_type, job, occurred_at), and (event_type, application, occurred_at) - Registered in Django Admin with list_display, list_filter on event_type and actor_role, and search by user email - AlertThreshold model - Fields: metric_key (unique CharField), label, warning_hours (nullable PositiveIntegerField), critical_hours (PositiveIntegerField), is_active (BooleanField, default True), notes, updated_at (auto_now) - Registered in Django Admin with list_editable on warning_hours, critical_hours, and is_active - thresholds must be adjustable without a code deployment - Seeded with initial values for all metrics listed in user story
15.2 Analytics Dashboards¶
User Story:
As a Cruits manager I want to view time-gap metrics across all recruitment funnel stages So that I can identify bottlenecks and act on them - either through Metabase without writing SQL, or through developer-maintained views when deeper analysis is needed
Описание: This feature exposes the EventLog data in a usable form. Two implementation paths coexist - they are not alternatives, they serve for different depths of analysis. Option A - Developer SQL Views: Engineers create PostgreSQL views (or materialised views for performance) that pre-calculate time gaps for each metric. These are the source for internal tooling, data exports, and any custom queries run outside Metabase. Option B - Metabase Self-Service: The product owner and operations team connect to the same database through Metabase using the read-only user. They build and iterate on dashboards using saved SQL questions or the Metabase GUI query builder, without engineering involvement for day-to-day changes Acceptance Criteria Common Requirements (both options) All dashboards and views are based on analytics_eventlog and analytics_alertthreshold, plus JOINs to core models (jobs, applications, offers, placements, users, chat threads) The metabase_reader user is the only connection used for reads, it never has write permissions Data freshness: dashboards reflect data no older than 60 minutes, near-real-time (5-minute) refresh is a nice-to-have Option A - Developer SQL Views Views are created and maintained in the analytics schema (or prefixed with v_analytics_ in the public schema) The following views are required at minimum: v_analytics_jobs_manager - first job posted gap, first contact gap, first application gap, job close duration v_analytics_candidate_onboarding - onboarding speed, profile completion speed v_analytics_recruitment_positive - application processing, offer sending, offer acceptance, placement confirmation speeds v_analytics_recruitment_negative - all decline, withdrawal, and discontinuation speeds v_analytics_inactivity - manager and candidate inactivity (
Критерии приёмки: - Common Requirements (both options) - All dashboards and views are based on analytics_eventlog and analytics_alertthreshold, plus JOINs to core models (jobs, applications, offers, placements, users, chat threads) - The metabase_reader user is the only connection used for reads, it never has write permissions - Data freshness: dashboards reflect data no older than 60 minutes, near-real-time (5-minute) refresh is a nice-to-have - Option A - Developer SQL Views - Views are created and maintained in the analytics schema (or prefixed with v_analytics_ in the public schema) - The following views are required at minimum: - v_analytics_jobs_manager - first job posted gap, first contact gap, first application gap, job close duration - v_analytics_candidate_onboarding - onboarding speed, profile completion speed - v_analytics_recruitment_positive - application processing, offer sending, offer acceptance, placement confirmation speeds - v_analytics_recruitment_negative - all decline, withdrawal, and discontinuation speeds - v_analytics_inactivity - manager and candidate inactivity (days since last login), jobs without applications, stale unreviewed applications - v_analytics_chat - average response times (by role), thread abandonment - v_analytics_victoria_id - registration speed, verification speed - v_analytics_post_placement - review submission speed (manager and worker), placement duration - Each view returns: entity identifiers, relevant user emails, the two event timestamps, and the gap in hours (rounded to 1 decimal) - Each view is documented with a comment block explaining the metric, the two event_types used, and any filter logic - Views are created and updated via Django migrations or a standalone SQL script checked into version control - Option B - Metabase Self-Service - Metabase is deployed via Docker or Metabase Cloud and connected to the Cruits database using metabase_reader - SMTP is configured in Metabase Admin to enable alert email delivery (see next user story) - The following dashboards are created as the starting set: - Manager Activation - first job posted, first contact, first application, job closed, AI first usage - Recruitment Funnel - application processing, offer sending, offer acceptance, placement confirmation speeds - Negative Flow - all decline, withdrawal, and discontinuation speeds - Candidate Journey - onboarding speed, profile completion, first search, search to application, first offer, first placement - Communication Health - average response times per role, first message read, thread abandonment - Inactivity & Re-engagement - manager and candidate inactivity, jobs without applications, stale applications, repost speed - Post-Placement - placement duration, review submission speeds, candidate and manager recovery after negative events - Victoria-ID - registration speed, verification speed - All saved is stored in a shared Cruits Analytics collection, not in individual user accounts (if multiple users to be) - Each dashboard has a text card at the top describing what it measures and the current alert thresholds - Full Metric Reference - Metric - Start Event - End Event - Dashboard - First Job Posted - manager_first_login - job_posted - Manager Activation - First Contact on Job - job_posted - first_message_received - Manager Activation - First Application on Job - job_posted - first_application_received - Manager Activation - Job Close Duration - job_posted - job_archived - Manager Activation - No (monitor) - AI First Usage - Manager - manager_first_login - ai_first_prompt (manager) - Manager Activation - No (monitor) - Onboarding Speed - candidate_registered - onboarding_finished - Candidate Journey - No (monitor) - Profile Completion Speed - profile_page_first_visit - profile_80_percent - Candidate Journey - No (monitor) - First Search Gap - candidate_first_login - first_job_search - Candidate Journey - No (monitor) - Search to Application - first_job_search - first_application_submitted - Candidate Journey - No (monitor) - First Application (candidate) - candidate_first_login - first_application_submitted - Candidate Journey - No (monitor) - First Offer - first_application_submitted - first_offer_received - Candidate Journey - No (monitor) - First Placement - first_offer_accepted - first_placement_confirmed - Candidate Journey - No (monitor) - AI First Usage - Candidate - candidate_first_login - ai_first_prompt (candidate) - Candidate Journey - No (monitor) - Reaction time on perfect match (nice to have) - perfect_match_push - application_received - Recruitment Funnel - No (monitor) - Application Processing Speed - application_received - candidate_reviewed - Recruitment Funnel - Offer Sending Speed - application_received - offer_sent - Recruitment Funnel - Offer Acceptance Speed - offer_sent - offer_accepted - Recruitment Funnel - Placement Confirmation Speed - offer_accepted - placement_confirmed - Recruitment Funnel - Application Decline Speed - application_received - application_declined - Negative Flow - Application Withdrawal Speed - application_received - application_withdrawn - Negative Flow - Offer Withdrawal Speed - offer_sent - offer_withdrawn - Negative Flow - Offer Decline Speed - offer_sent - offer_declined - Negative Flow - Placement Discontinue (Manager) - offer_accepted - placement_discontinued (manager) - Negative Flow - Placement Discontinue (Candidate) - offer_accepted - placement_discontinued (candidate) - Negative Flow - Avg Response Time - Manager - message_sent (candidate) - message_sent (manager) - Communication Health - No (monitor) - Avg Response Time - Candidate - message_sent (manager) - message_sent (candidate) - Communication Health - No (monitor) - First Message Read - message_sent - message_read - Communication Health - Thread Abandonment - last message_sent - no reply after X days - Communication Health - Manager Inactivity - last login - Inactivity - Candidate Inactivity - last login - Inactivity - Job Without Applications - job_posted - NOW() (no application) - Inactivity - Stale Unreviewed Applications - application_received - NOW() (still unprocessed) - Inactivity - Time to First Hire - job_posted - first placement_confirmed - Hiring Funnel - No (monitor) - Job Repost Speed - job_archived - next job_posted (same manager) - Hiring Funnel - No (monitor) - Victoria-ID Registration Speed - placement_confirmed - victoria_id_registered - Victoria-ID - Verification Speed - victoria_id_registered - victoria_id_signed - Victoria-ID - Placement Duration - placement start date - placement end date - Post-Placement - No (monitor) - Review Submission - Manager - placement_ended - review_submitted (manager) - Post-Placement - Review Submission - Worker - placement_ended - review_submitted (worker) - Post-Placement - Candidate Recovery - application_declined or offer_withdrawn - next application_submitted - Post-Placement - No (monitor) - Manager Recovery - offer_declined - next offer_sent (same job) - Post-Placement - No (monitor) - “No (monitor)” means the metric appears on the dashboard for visibility but does not trigger an alert when a threshold is exceeded
15.2.1 Other Dashboards Metabase requiremens All dashboards and views are based on analytics_eventlog and analytics_alertthresh¶
15.3 Automated Time-Gap Alerts (не MVP)¶
User Story:
As a Cruits manager I want to receive an email alert when a time gap between two platform events exceeds a defined threshold So that I can intervene before the delay affects a candidate or placement outcome.
Описание: Alerts are implemented as Metabase saved questions. Each question is written to return rows only when a threshold is breached. Metabase checks the question on a schedule and sends an email when the result set is non-empty (at least 1 row). Thresholds are stored in AlertThreshold and editable via Django Admin. For MVP, when a threshold is changed in Django Admin, the corresponding Metabase question is updated manually to match. Two alert levels exist per metric: Warning (lower urgency) and Critical (requires immediate action). Each level is a separate Metabase question with a separate email. Acceptance Criteria 1. Alert infrastructure SMTP configured in Metabase Admin, alerts sent from a designated Cruits address ( noreply@cruits.com / management@cruits.com ) ? Each alert question uses the condition “These results have at least 1 row” Schedule: every hour for active recruitment metrics, daily at 08:00 for inactivity and post-placement metrics (configurable) Email subject format: “[WARNING] Metric Name - N breaches” or “[CRITICAL] Metric Name - N breaches” Email body includes: metric name, threshold breached, a list of affected entities (job title or candidate email and hours elapsed), and a direct link to the relevant Metabase dashboard 2. Initial threshold values (editable in Django Admin) Metric Warning, h Critical, h Schedule First Job Posted 48 72 Daily 08:00 First Contact on Job 24 48 Hourly First Application on Job 48 72 Daily 08:00 Application Processing Speed 24 48 Hourly Offer Sending Speed 24 48 Hourly Offer Acceptance Speed 48 72 Hourly Placement Confirmation Speed 24 48 Hourly Application Decline Speed 48 72 Daily 08:00 Application Withdrawal Speed 48 72 Daily 08:00 Offer Withdrawal Speed 48 72 Daily 08:00 Offer Decline Speed 48 72 Daily 08:00 Placement Disco
Критерии приёмки: - Alert infrastructure - SMTP configured in Metabase Admin, alerts sent from a designated Cruits address ( noreply@cruits.com / management@cruits.com ) ? - Each alert question uses the condition “These results have at least 1 row” - Schedule: every hour for active recruitment metrics, daily at 08:00 for inactivity and post-placement metrics (configurable) - Email subject format: “[WARNING] Metric Name - N breaches” or “[CRITICAL] Metric Name - N breaches” - Email body includes: metric name, threshold breached, a list of affected entities (job title or candidate email and hours elapsed), and a direct link to the relevant Metabase dashboard - Initial threshold values (editable in Django Admin) - Metric - Warning, h - Critical, h - Schedule - First Job Posted - Daily 08:00 - First Contact on Job - Hourly - First Application on Job - Daily 08:00 - Application Processing Speed - Hourly - Offer Sending Speed - Hourly - Offer Acceptance Speed - Hourly - Placement Confirmation Speed - Hourly - Application Decline Speed - Daily 08:00 - Application Withdrawal Speed - Daily 08:00 - Offer Withdrawal Speed - Daily 08:00 - Offer Decline Speed - Daily 08:00 - Placement Discontinue (Manager) - Daily 08:00 - Placement Discontinue (Candidate) - Daily 08:00 - First Message Read - Hourly - Thread Abandonment - Daily 08:00 - Manager Inactivity - 120 (5 days) - 168 (7 days) - Daily 08:00 - Candidate Inactivity - 120 (5 days) - 168 (7 days) - Daily 08:00 - Job Without Applications - Daily 08:00 - Stale Unreviewed Applications - Hourly - Victoria-ID Registration Speed - Hourly - Victoria-ID Verification Speed - Hourly - Review Submission - Manager - Daily 08:00 - Review Submission - Worker - Daily 08:00 - These are starting estimates. - Review the actual distribution of gaps after a few weeks of live data and adjust thresholds to reduce false positives before expanding alert coverage to all categories - Alert query pattern - Each alert query is a variation of its dashboard counterpart, with an added WHERE clause filtering to rows where the gap exceeds the threshold - Inactivity alerts (measuring gap to NOW()) use a HAVING clause on the aggregated last-event timestamp - Warning alerts use warning_hours, critical alerts use critical_hours - Metrics marked "No (monitor)" in US
15.4 Contentsquare Behaviour Analytics¶
User Story:
As a Cruits manager I want to see where managers click, scroll, and drop off on desktop and where candidates lose momentum on mobile S o that I can make evidence-based UX decisions without writing SQL
Описание: Contentsquare's free tier is embedded on the Cruits web app using a JavaScript snippet. It captures anonymous interaction data - clicks, scroll depth, cursor movements - and presents them as visual overlays in the Contentsquare dashboard. No backend changes are required. No personally identifiable information is sent to Contentsquare. Within the free tier limits, the team gets access to heatmaps, scroll maps, click maps, zone-based analytics, and basic funnel analysis. This provides qualitative context that complements the quantitative event data from MetaBase. What is Included in the Contentsquare Free Tier Feature Available in Free Tier Comments Click maps Yes Shows where users click on each page Scroll maps Yes Shows how far down users scroll before leaving Hover / move maps Yes (desktop only) Shows cursor movement patterns on desktop Zone-based analytics Yes Click rate and engagement rate per page zone Conversion funnels Yes (basic) Multi-step funnels with drop-off percentage at each step Session replays Limited quota Small number of replays per month, useful for diagnosing specific drop-off points identified by funnels Segment filtering Limited Filter by device type and URL, no custom audience segments AI insights and recommendations No Paid feature only Advanced audience segmentation No Paid feature only API access No Paid feature only Unlimited data retention No Free tier retains data for a limited period Multiple domains No One domain per free account Acceptance Criteria Installation The Contentsquare JavaScript snippet is added to the base Django HTML template, placed before the closing tag The snippet loads only in production, it is disabled in local development and staging ? No user PII (name, email, user ID) is passed to Contentsquare. Any existing a
Критерии приёмки: - Installation - The Contentsquare JavaScript snippet is added to the base Django HTML template, placed before the closing tag - The snippet loads only in production, it is disabled in local development and staging ? - No user PII (name, email, user ID) is passed to Contentsquare. - Any existing analytics scripts that send user attributes must be reviewed before the snippet goes live - Page load impact is checked with Sentry. - Time to Interactive must not increase by more than 200ms ? - Core Web Vitals scores (LCP, FID, CLS) must not regress significantly after the snippet is deployed - Manager desktop - pages to track - Dashboard - heatmap to understand widget engagement (to be created once dashboard is updated) - Job posting flow - conversion funnel to measure drop-off between each step - Recruitment pages (applications, offers and placements) - click map and moves map to understand which filters and action buttons are used - Notifications page - scroll map and click map to understand how managers engage with the notification list - Candidates page - scroll map to see how far managers scroll while looking through candidate list - Rage click (all pages) - heat map to understand the rage click distribution for managers - Candidate mobile - pages to track - Оnboarding flow (all steps) - conversion funnel: drop-off here directly correlates with the onboarding_finished event in
15.1 Job search and discovery pages - click map and scroll map to understand how candidates interact with search results on m¶
Описание: is read before applying or leaving Application flow (all steps) - conversion funnel to identify abandonment between steps Profile completion pages - scroll map and zone click rate to identify which sections candidates skip Offer response page - click map to confirm that accept and decline actions are clearly visible and used on small screens Possible Use Cases for Presentation: Dashboard - heatmap and scroll map to understand widget engagement and content visibility below the fold (scroll map to be created once dashboard is updated with more widgets to scroll to) Job posting flow - conversion funnel to measure drop-off between each step Recruitment pages (applications, offers and placements) - click map and moves map to understand which filters and action buttons are used See if Notifications page is used, if it brings value considering there is no push / email notifications - scroll map and click map Candidates page - scroll map to see how far managers scroll while looking through candidate list Rage click (all pages) - heat map to understand the rage click distribution for managers