-- ===================== -- Analytics (date-filtered) -- ===================== -- Shared optional params (nullable = all-time): -- range_start, range_end (exclusive upper bound) -- Required chart params: -- series_start, series_end (inclusive dates) -- Relative window anchor: -- ref_date (inclusive date used for new_today/week/month) -- ===================== -- User Analytics -- ===================== -- name: AnalyticsUsersSummary :one SELECT COUNT(*)::bigint AS total, COUNT(*) FILTER (WHERE u.created_at::date = sqlc.arg('ref_date')::date)::bigint AS new_today, COUNT(*) FILTER ( WHERE u.created_at::date >= sqlc.arg('ref_date')::date - INTERVAL '6 days' AND u.created_at::date <= sqlc.arg('ref_date')::date )::bigint AS new_this_week, COUNT(*) FILTER ( WHERE u.created_at::date >= sqlc.arg('ref_date')::date - INTERVAL '29 days' AND u.created_at::date <= sqlc.arg('ref_date')::date )::bigint AS new_this_month FROM users u WHERE (sqlc.narg('range_start')::timestamptz IS NULL OR u.created_at >= sqlc.narg('range_start')::timestamptz) AND (sqlc.narg('range_end')::timestamptz IS NULL OR u.created_at < sqlc.narg('range_end')::timestamptz); -- name: AnalyticsUsersByRole :many SELECT COALESCE(u.role, 'unknown') AS role, COUNT(*)::bigint AS count FROM users u WHERE (sqlc.narg('range_start')::timestamptz IS NULL OR u.created_at >= sqlc.narg('range_start')::timestamptz) AND (sqlc.narg('range_end')::timestamptz IS NULL OR u.created_at < sqlc.narg('range_end')::timestamptz) GROUP BY u.role ORDER BY count DESC; -- name: AnalyticsUsersByStatus :many SELECT COALESCE(u.status, 'unknown') AS status, COUNT(*)::bigint AS count FROM users u WHERE (sqlc.narg('range_start')::timestamptz IS NULL OR u.created_at >= sqlc.narg('range_start')::timestamptz) AND (sqlc.narg('range_end')::timestamptz IS NULL OR u.created_at < sqlc.narg('range_end')::timestamptz) GROUP BY u.status ORDER BY count DESC; -- name: AnalyticsUsersByAgeGroup :many SELECT COALESCE(u.age_group, 'unknown') AS age_group, COUNT(*)::bigint AS count FROM users u WHERE (sqlc.narg('range_start')::timestamptz IS NULL OR u.created_at >= sqlc.narg('range_start')::timestamptz) AND (sqlc.narg('range_end')::timestamptz IS NULL OR u.created_at < sqlc.narg('range_end')::timestamptz) GROUP BY u.age_group ORDER BY count DESC; -- name: AnalyticsUsersByKnowledgeLevel :many SELECT COALESCE(u.knowledge_level, 'unknown') AS knowledge_level, COUNT(*)::bigint AS count FROM users u WHERE (sqlc.narg('range_start')::timestamptz IS NULL OR u.created_at >= sqlc.narg('range_start')::timestamptz) AND (sqlc.narg('range_end')::timestamptz IS NULL OR u.created_at < sqlc.narg('range_end')::timestamptz) GROUP BY u.knowledge_level ORDER BY count DESC; -- name: AnalyticsUsersByEducationLevel :many SELECT COALESCE(NULLIF(TRIM(u.education_level), ''), 'unknown')::text AS education_level, COUNT(*)::bigint AS count FROM users u WHERE (sqlc.narg('range_start')::timestamptz IS NULL OR u.created_at >= sqlc.narg('range_start')::timestamptz) AND (sqlc.narg('range_end')::timestamptz IS NULL OR u.created_at < sqlc.narg('range_end')::timestamptz) GROUP BY COALESCE(NULLIF(TRIM(u.education_level), ''), 'unknown') ORDER BY count DESC; -- name: AnalyticsUsersByOccupation :many SELECT COALESCE(NULLIF(TRIM(u.occupation), ''), 'unknown')::text AS occupation, COUNT(*)::bigint AS count FROM users u WHERE (sqlc.narg('range_start')::timestamptz IS NULL OR u.created_at >= sqlc.narg('range_start')::timestamptz) AND (sqlc.narg('range_end')::timestamptz IS NULL OR u.created_at < sqlc.narg('range_end')::timestamptz) GROUP BY COALESCE(NULLIF(TRIM(u.occupation), ''), 'unknown') ORDER BY count DESC; -- name: AnalyticsUsersByLearningGoal :many SELECT COALESCE(NULLIF(TRIM(u.learning_goal), ''), 'unknown')::text AS learning_goal, COUNT(*)::bigint AS count FROM users u WHERE (sqlc.narg('range_start')::timestamptz IS NULL OR u.created_at >= sqlc.narg('range_start')::timestamptz) AND (sqlc.narg('range_end')::timestamptz IS NULL OR u.created_at < sqlc.narg('range_end')::timestamptz) GROUP BY COALESCE(NULLIF(TRIM(u.learning_goal), ''), 'unknown') ORDER BY count DESC; -- name: AnalyticsUsersByLanguageChallange :many SELECT COALESCE(NULLIF(TRIM(u.language_challange), ''), 'unknown')::text AS language_challange, COUNT(*)::bigint AS count FROM users u WHERE (sqlc.narg('range_start')::timestamptz IS NULL OR u.created_at >= sqlc.narg('range_start')::timestamptz) AND (sqlc.narg('range_end')::timestamptz IS NULL OR u.created_at < sqlc.narg('range_end')::timestamptz) GROUP BY COALESCE(NULLIF(TRIM(u.language_challange), ''), 'unknown') ORDER BY count DESC; -- name: AnalyticsUsersByCountry :many SELECT COALESCE(NULLIF(TRIM(u.country), ''), 'unknown')::text AS country, COUNT(*)::bigint AS count FROM users u WHERE (sqlc.narg('range_start')::timestamptz IS NULL OR u.created_at >= sqlc.narg('range_start')::timestamptz) AND (sqlc.narg('range_end')::timestamptz IS NULL OR u.created_at < sqlc.narg('range_end')::timestamptz) GROUP BY COALESCE(NULLIF(TRIM(u.country), ''), 'unknown') ORDER BY count DESC; -- name: AnalyticsUsersByRegion :many SELECT COALESCE(u.region, 'unknown') AS region, COUNT(*)::bigint AS count FROM users u WHERE (sqlc.narg('range_start')::timestamptz IS NULL OR u.created_at >= sqlc.narg('range_start')::timestamptz) AND (sqlc.narg('range_end')::timestamptz IS NULL OR u.created_at < sqlc.narg('range_end')::timestamptz) GROUP BY u.region ORDER BY count DESC; -- name: AnalyticsUserRegistrationsLast30Days :many SELECT d.date, COUNT(u.id)::bigint AS count FROM generate_series( sqlc.arg('series_start')::date, sqlc.arg('series_end')::date, INTERVAL '1 day' ) AS d(date) LEFT JOIN users u ON u.created_at::date = d.date AND (sqlc.narg('range_start')::timestamptz IS NULL OR u.created_at >= sqlc.narg('range_start')::timestamptz) AND (sqlc.narg('range_end')::timestamptz IS NULL OR u.created_at < sqlc.narg('range_end')::timestamptz) GROUP BY d.date ORDER BY d.date; -- ===================== -- Subscription Analytics -- ===================== -- name: AnalyticsSubscriptionsSummary :one SELECT COUNT(*)::bigint AS total, COUNT(*) FILTER (WHERE us.status = 'ACTIVE')::bigint AS active, COUNT(*) FILTER (WHERE us.created_at::date = sqlc.arg('ref_date')::date)::bigint AS new_today, COUNT(*) FILTER ( WHERE us.created_at::date >= sqlc.arg('ref_date')::date - INTERVAL '6 days' AND us.created_at::date <= sqlc.arg('ref_date')::date )::bigint AS new_this_week, COUNT(*) FILTER ( WHERE us.created_at::date >= sqlc.arg('ref_date')::date - INTERVAL '29 days' AND us.created_at::date <= sqlc.arg('ref_date')::date )::bigint AS new_this_month FROM user_subscriptions us WHERE (sqlc.narg('range_start')::timestamptz IS NULL OR us.created_at >= sqlc.narg('range_start')::timestamptz) AND (sqlc.narg('range_end')::timestamptz IS NULL OR us.created_at < sqlc.narg('range_end')::timestamptz); -- name: AnalyticsSubscriptionsByStatus :many SELECT COALESCE(us.status, 'unknown') AS status, COUNT(*)::bigint AS count FROM user_subscriptions us WHERE (sqlc.narg('range_start')::timestamptz IS NULL OR us.created_at >= sqlc.narg('range_start')::timestamptz) AND (sqlc.narg('range_end')::timestamptz IS NULL OR us.created_at < sqlc.narg('range_end')::timestamptz) GROUP BY us.status ORDER BY count DESC; -- name: AnalyticsRevenueByPlan :many SELECT sp.name AS plan_name, sp.currency, COUNT(p.id)::bigint AS total_payments, COALESCE(SUM(p.amount), 0)::float8 AS total_revenue FROM payments p JOIN subscription_plans sp ON sp.id = p.plan_id WHERE p.status = 'SUCCESS' AND (sqlc.narg('range_start')::timestamptz IS NULL OR COALESCE(p.paid_at, p.created_at) >= sqlc.narg('range_start')::timestamptz) AND (sqlc.narg('range_end')::timestamptz IS NULL OR COALESCE(p.paid_at, p.created_at) < sqlc.narg('range_end')::timestamptz) GROUP BY sp.name, sp.currency ORDER BY total_revenue DESC; -- name: AnalyticsNewSubscriptionsLast30Days :many SELECT d.date, COUNT(us.id)::bigint AS count FROM generate_series( sqlc.arg('series_start')::date, sqlc.arg('series_end')::date, INTERVAL '1 day' ) AS d(date) LEFT JOIN user_subscriptions us ON us.created_at::date = d.date AND (sqlc.narg('range_start')::timestamptz IS NULL OR us.created_at >= sqlc.narg('range_start')::timestamptz) AND (sqlc.narg('range_end')::timestamptz IS NULL OR us.created_at < sqlc.narg('range_end')::timestamptz) GROUP BY d.date ORDER BY d.date; -- ===================== -- Payment Analytics -- ===================== -- name: AnalyticsPaymentsSummary :one SELECT COALESCE(SUM(p.amount) FILTER (WHERE p.status = 'SUCCESS'), 0)::float8 AS total_revenue, COALESCE(AVG(p.amount) FILTER (WHERE p.status = 'SUCCESS'), 0)::float8 AS avg_value, COUNT(*)::bigint AS total_payments, COUNT(*) FILTER (WHERE p.status = 'SUCCESS')::bigint AS successful_payments FROM payments p WHERE (sqlc.narg('range_start')::timestamptz IS NULL OR COALESCE(p.paid_at, p.created_at) >= sqlc.narg('range_start')::timestamptz) AND (sqlc.narg('range_end')::timestamptz IS NULL OR COALESCE(p.paid_at, p.created_at) < sqlc.narg('range_end')::timestamptz); -- name: AnalyticsPaymentsByStatus :many SELECT COALESCE(p.status, 'unknown') AS status, COUNT(*)::bigint AS count, COALESCE(SUM(p.amount), 0)::float8 AS total_amount FROM payments p WHERE (sqlc.narg('range_start')::timestamptz IS NULL OR COALESCE(p.paid_at, p.created_at) >= sqlc.narg('range_start')::timestamptz) AND (sqlc.narg('range_end')::timestamptz IS NULL OR COALESCE(p.paid_at, p.created_at) < sqlc.narg('range_end')::timestamptz) GROUP BY p.status ORDER BY count DESC; -- name: AnalyticsPaymentsByMethod :many SELECT COALESCE(p.payment_method, 'unknown') AS payment_method, COUNT(*)::bigint AS count, COALESCE(SUM(p.amount), 0)::float8 AS total_amount FROM payments p WHERE p.status = 'SUCCESS' AND (sqlc.narg('range_start')::timestamptz IS NULL OR COALESCE(p.paid_at, p.created_at) >= sqlc.narg('range_start')::timestamptz) AND (sqlc.narg('range_end')::timestamptz IS NULL OR COALESCE(p.paid_at, p.created_at) < sqlc.narg('range_end')::timestamptz) GROUP BY p.payment_method ORDER BY count DESC; -- name: AnalyticsRevenueLast30Days :many SELECT d.date, COALESCE(SUM(p.amount), 0)::float8 AS total_revenue FROM generate_series( sqlc.arg('series_start')::date, sqlc.arg('series_end')::date, INTERVAL '1 day' ) AS d(date) LEFT JOIN payments p ON COALESCE(p.paid_at, p.created_at)::date = d.date AND p.status = 'SUCCESS' AND (sqlc.narg('range_start')::timestamptz IS NULL OR COALESCE(p.paid_at, p.created_at) >= sqlc.narg('range_start')::timestamptz) AND (sqlc.narg('range_end')::timestamptz IS NULL OR COALESCE(p.paid_at, p.created_at) < sqlc.narg('range_end')::timestamptz) GROUP BY d.date ORDER BY d.date; -- Monthly successful revenue for a calendar year (UTC buckets). Months with multiple currencies emit one row each; months with no revenue emit one row (currency ETB, revenue 0). -- name: AnalyticsRevenueMonthlyByYear :many WITH months AS ( SELECT bucket FROM generate_series( make_timestamptz(sqlc.arg('report_year')::int, 1, 1, 0, 0, 0, 'UTC'), make_timestamptz(sqlc.arg('report_year')::int, 12, 1, 0, 0, 0, 'UTC'), INTERVAL '1 month' ) AS gs(bucket) ), by_month_currency AS ( SELECT date_trunc('month', COALESCE(p.paid_at, p.created_at) AT TIME ZONE 'UTC') AS ym, p.currency, SUM(p.amount)::float8 AS total_revenue FROM payments p WHERE p.status = 'SUCCESS' AND EXTRACT(YEAR FROM COALESCE(p.paid_at, p.created_at) AT TIME ZONE 'UTC')::int = sqlc.arg('report_year')::int GROUP BY 1, 2 ) SELECT (EXTRACT(MONTH FROM m.bucket AT TIME ZONE 'UTC'))::int AS month, date_trunc('month', m.bucket AT TIME ZONE 'UTC')::date AS month_start, COALESCE(b.currency, 'ETB'::varchar) AS currency, COALESCE(b.total_revenue, 0)::float8 AS total_revenue FROM months m LEFT JOIN by_month_currency b ON b.ym = date_trunc('month', m.bucket AT TIME ZONE 'UTC') ORDER BY m.bucket, COALESCE(b.currency, ''::varchar); -- ===================== -- Course Analytics -- ===================== -- LMS: programs -> courses -> modules -> lessons; lms_practices attach to course, module, or lesson. -- Exam prep: exam_prep.catalog_courses -> units -> unit_modules -> unit_module_lessons; exam_prep.lesson_practices. -- Legacy dashboard fields map to: programs, courses, modules, and video-bearing lessons (LMS + exam prep). -- name: AnalyticsCourseCounts :one SELECT (SELECT COUNT(*)::bigint FROM programs) AS total_categories, (SELECT COUNT(*)::bigint FROM courses) AS total_courses, (SELECT COUNT(*)::bigint FROM modules) AS total_sub_courses, ( COALESCE((SELECT COUNT(*)::bigint FROM lessons WHERE NULLIF(BTRIM(video_url), '') IS NOT NULL), 0::bigint) + COALESCE((SELECT COUNT(*)::bigint FROM exam_prep.unit_module_lessons WHERE NULLIF(BTRIM(video_url), '') IS NOT NULL), 0::bigint) )::bigint AS total_videos, (SELECT COUNT(*)::bigint FROM programs) AS lms_programs, (SELECT COUNT(*)::bigint FROM courses) AS lms_courses, (SELECT COUNT(*)::bigint FROM modules) AS lms_modules, (SELECT COUNT(*)::bigint FROM lessons) AS lms_lessons, (SELECT COUNT(*)::bigint FROM lessons WHERE NULLIF(BTRIM(video_url), '') IS NOT NULL) AS lms_lessons_with_video, (SELECT COUNT(*)::bigint FROM lms_practices) AS lms_practices, (SELECT COUNT(*)::bigint FROM lms_practices WHERE course_id IS NOT NULL) AS lms_practices_at_course, (SELECT COUNT(*)::bigint FROM lms_practices WHERE module_id IS NOT NULL) AS lms_practices_at_module, (SELECT COUNT(*)::bigint FROM lms_practices WHERE lesson_id IS NOT NULL) AS lms_practices_at_lesson, (SELECT COUNT(*)::bigint FROM exam_prep.catalog_courses) AS exam_prep_catalog_courses, (SELECT COUNT(*)::bigint FROM exam_prep.units) AS exam_prep_units, (SELECT COUNT(*)::bigint FROM exam_prep.unit_modules) AS exam_prep_unit_modules, (SELECT COUNT(*)::bigint FROM exam_prep.unit_module_lessons) AS exam_prep_lessons, (SELECT COUNT(*)::bigint FROM exam_prep.unit_module_lessons WHERE NULLIF(BTRIM(video_url), '') IS NOT NULL) AS exam_prep_lessons_with_video, (SELECT COUNT(*)::bigint FROM exam_prep.lesson_practices) AS exam_prep_lesson_practices; -- ===================== -- Content Analytics -- ===================== -- name: AnalyticsQuestionsCounts :one SELECT ( SELECT COUNT(*)::bigint FROM questions q WHERE (sqlc.narg('range_start')::timestamptz IS NULL OR q.created_at >= sqlc.narg('range_start')::timestamptz) AND (sqlc.narg('range_end')::timestamptz IS NULL OR q.created_at < sqlc.narg('range_end')::timestamptz) ) AS total_questions, ( SELECT COUNT(*)::bigint FROM question_sets qs WHERE (sqlc.narg('range_start')::timestamptz IS NULL OR qs.created_at >= sqlc.narg('range_start')::timestamptz) AND (sqlc.narg('range_end')::timestamptz IS NULL OR qs.created_at < sqlc.narg('range_end')::timestamptz) ) AS total_question_sets; -- name: AnalyticsQuestionsByType :many SELECT COALESCE(q.question_type, 'unknown') AS question_type, COUNT(*)::bigint AS count FROM questions q WHERE (sqlc.narg('range_start')::timestamptz IS NULL OR q.created_at >= sqlc.narg('range_start')::timestamptz) AND (sqlc.narg('range_end')::timestamptz IS NULL OR q.created_at < sqlc.narg('range_end')::timestamptz) GROUP BY q.question_type ORDER BY count DESC; -- name: AnalyticsQuestionSetsByType :many SELECT COALESCE(qs.set_type, 'unknown') AS set_type, COUNT(*)::bigint AS count FROM question_sets qs WHERE (sqlc.narg('range_start')::timestamptz IS NULL OR qs.created_at >= sqlc.narg('range_start')::timestamptz) AND (sqlc.narg('range_end')::timestamptz IS NULL OR qs.created_at < sqlc.narg('range_end')::timestamptz) GROUP BY qs.set_type ORDER BY count DESC; -- ===================== -- Notification Analytics -- ===================== -- name: AnalyticsNotificationsSummary :one SELECT COUNT(*)::bigint AS total, COUNT(*) FILTER (WHERE n.is_read = TRUE)::bigint AS read, COUNT(*) FILTER (WHERE n.is_read = FALSE)::bigint AS unread FROM notifications n WHERE (sqlc.narg('range_start')::timestamptz IS NULL OR n.created_at >= sqlc.narg('range_start')::timestamptz) AND (sqlc.narg('range_end')::timestamptz IS NULL OR n.created_at < sqlc.narg('range_end')::timestamptz); -- name: AnalyticsNotificationsByChannel :many SELECT COALESCE(n.channel, 'unknown') AS channel, COUNT(*)::bigint AS count FROM notifications n WHERE (sqlc.narg('range_start')::timestamptz IS NULL OR n.created_at >= sqlc.narg('range_start')::timestamptz) AND (sqlc.narg('range_end')::timestamptz IS NULL OR n.created_at < sqlc.narg('range_end')::timestamptz) GROUP BY n.channel ORDER BY count DESC; -- name: AnalyticsNotificationsByType :many SELECT COALESCE(n.type, 'unknown') AS type, COUNT(*)::bigint AS count FROM notifications n WHERE (sqlc.narg('range_start')::timestamptz IS NULL OR n.created_at >= sqlc.narg('range_start')::timestamptz) AND (sqlc.narg('range_end')::timestamptz IS NULL OR n.created_at < sqlc.narg('range_end')::timestamptz) GROUP BY n.type ORDER BY count DESC; -- ===================== -- Issue Analytics -- ===================== -- name: AnalyticsIssuesSummary :one SELECT COUNT(*)::bigint AS total, COUNT(*) FILTER (WHERE ri.status = 'resolved')::bigint AS resolved, CASE WHEN COUNT(*) > 0 THEN (COUNT(*) FILTER (WHERE ri.status = 'resolved')::float8 / COUNT(*)::float8) ELSE 0::float8 END AS resolution_rate FROM reported_issues ri WHERE (sqlc.narg('range_start')::timestamptz IS NULL OR ri.created_at >= sqlc.narg('range_start')::timestamptz) AND (sqlc.narg('range_end')::timestamptz IS NULL OR ri.created_at < sqlc.narg('range_end')::timestamptz); -- name: AnalyticsIssuesByStatus :many SELECT COALESCE(ri.status, 'unknown') AS status, COUNT(*)::bigint AS count FROM reported_issues ri WHERE (sqlc.narg('range_start')::timestamptz IS NULL OR ri.created_at >= sqlc.narg('range_start')::timestamptz) AND (sqlc.narg('range_end')::timestamptz IS NULL OR ri.created_at < sqlc.narg('range_end')::timestamptz) GROUP BY ri.status ORDER BY count DESC; -- name: AnalyticsIssuesByType :many SELECT COALESCE(ri.issue_type, 'unknown') AS issue_type, COUNT(*)::bigint AS count FROM reported_issues ri WHERE (sqlc.narg('range_start')::timestamptz IS NULL OR ri.created_at >= sqlc.narg('range_start')::timestamptz) AND (sqlc.narg('range_end')::timestamptz IS NULL OR ri.created_at < sqlc.narg('range_end')::timestamptz) GROUP BY ri.issue_type ORDER BY count DESC; -- ===================== -- Team Analytics -- ===================== -- name: AnalyticsTeamSummary :one SELECT COUNT(*)::bigint AS total_members FROM team_members tm WHERE (sqlc.narg('range_start')::timestamptz IS NULL OR tm.created_at >= sqlc.narg('range_start')::timestamptz) AND (sqlc.narg('range_end')::timestamptz IS NULL OR tm.created_at < sqlc.narg('range_end')::timestamptz); -- name: AnalyticsTeamByRole :many SELECT COALESCE(tm.team_role, 'unknown') AS team_role, COUNT(*)::bigint AS count FROM team_members tm WHERE (sqlc.narg('range_start')::timestamptz IS NULL OR tm.created_at >= sqlc.narg('range_start')::timestamptz) AND (sqlc.narg('range_end')::timestamptz IS NULL OR tm.created_at < sqlc.narg('range_end')::timestamptz) GROUP BY tm.team_role ORDER BY count DESC; -- name: AnalyticsTeamByStatus :many SELECT COALESCE(tm.status, 'unknown') AS status, COUNT(*)::bigint AS count FROM team_members tm WHERE (sqlc.narg('range_start')::timestamptz IS NULL OR tm.created_at >= sqlc.narg('range_start')::timestamptz) AND (sqlc.narg('range_end')::timestamptz IS NULL OR tm.created_at < sqlc.narg('range_end')::timestamptz) GROUP BY tm.status ORDER BY count DESC;