petstore-backend/db/queries/pilot_metrics.sql
2026-08-02 02:20:02 +08:00

172 lines
7.7 KiB
SQL
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- Petstore Pilot Metrics v1MySQL 5.7+/8.x只读
-- 默认统计最近 30 天;发布/试点负责人可在当前 session 覆盖三个变量后逐段执行。
-- 输出仅含 store_id、计数、比率和时效不含客户/员工 PII。
SET @pilot_to = DATE_ADD(CURRENT_DATE, INTERVAL 1 DAY);
SET @pilot_from = DATE_SUB(@pilot_to, INTERVAL 30 DAY);
SET @pilot_store_id = NULL;
-- 1. 核心服务与报告漏斗。[from, to),各事实按稳定 aggregate_id 去重。
SELECT
e.store_id,
COUNT(DISTINCT CASE WHEN e.event_type = 'appointment_created' THEN e.aggregate_id END) AS appointment_created_count,
COUNT(DISTINCT CASE WHEN e.event_type = 'service_started' THEN e.aggregate_id END) AS service_started_count,
COUNT(DISTINCT CASE WHEN e.event_type = 'service_completed' THEN e.aggregate_id END) AS complete_service_count,
COUNT(DISTINCT CASE WHEN e.event_type = 'report_submitted' THEN e.aggregate_id END) AS report_submitted_count,
COUNT(DISTINCT CASE WHEN e.event_type = 'report_sent' THEN e.aggregate_id END) AS report_sent_count,
COUNT(DISTINCT CASE WHEN e.event_type IN ('report_opened', 'report_reopened') THEN e.aggregate_id END) AS report_opened_count,
COUNT(DISTINCT CASE WHEN e.event_type = 'lead_submitted' THEN e.aggregate_id END) AS lead_submitted_count,
COUNT(DISTINCT CASE WHEN e.event_type = 'rebook_created' THEN e.aggregate_id END) AS rebook_created_count,
ROUND(
100 * COUNT(DISTINCT CASE WHEN e.event_type = 'service_completed' THEN e.aggregate_id END)
/ NULLIF(COUNT(DISTINCT CASE WHEN e.event_type = 'service_started' THEN e.aggregate_id END), 0),
1
) AS service_completion_rate_pct,
ROUND(
100 * COUNT(DISTINCT CASE WHEN e.event_type = 'report_sent' THEN e.aggregate_id END)
/ NULLIF(COUNT(DISTINCT CASE WHEN e.event_type = 'report_submitted' THEN e.aggregate_id END), 0),
1
) AS report_sent_rate_pct,
ROUND(
100 * COUNT(DISTINCT CASE WHEN e.event_type IN ('report_opened', 'report_reopened') THEN e.aggregate_id END)
/ NULLIF(COUNT(DISTINCT CASE WHEN e.event_type = 'report_sent' THEN e.aggregate_id END), 0),
1
) AS report_open_rate_pct,
ROUND(
100 * COUNT(DISTINCT CASE WHEN e.event_type = 'lead_submitted' THEN e.aggregate_id END)
/ NULLIF(COUNT(DISTINCT CASE WHEN e.event_type IN ('report_opened', 'report_reopened') THEN e.aggregate_id END), 0),
1
) AS opened_to_lead_rate_pct,
ROUND(
100 * COUNT(DISTINCT CASE WHEN e.event_type = 'rebook_created' THEN e.aggregate_id END)
/ NULLIF(COUNT(DISTINCT CASE WHEN e.event_type = 'lead_submitted' THEN e.aggregate_id END), 0),
1
) AS lead_to_rebook_rate_pct
FROM t_business_event e
WHERE e.occurred_at >= @pilot_from
AND e.occurred_at < @pilot_to
AND (@pilot_store_id IS NULL OR e.store_id = @pilot_store_id)
GROUP BY e.store_id
ORDER BY e.store_id;
-- 2. 报告发送时效:提交后 24 小时内是否形成首次显式发送事实。
SELECT
submitted.store_id,
COUNT(*) AS submitted_report_count,
SUM(CASE
WHEN sent.occurred_at IS NOT NULL
AND sent.occurred_at < DATE_ADD(submitted.occurred_at, INTERVAL 24 HOUR)
THEN 1 ELSE 0
END) AS sent_within_24h_count,
ROUND(
100 * SUM(CASE
WHEN sent.occurred_at IS NOT NULL
AND sent.occurred_at < DATE_ADD(submitted.occurred_at, INTERVAL 24 HOUR)
THEN 1 ELSE 0
END) / NULLIF(COUNT(*), 0),
1
) AS sent_within_24h_rate_pct
FROM t_business_event submitted
LEFT JOIN t_business_event sent
ON sent.store_id = submitted.store_id
AND sent.event_type = 'report_sent'
AND sent.aggregate_type = 'report'
AND sent.aggregate_id = submitted.aggregate_id
WHERE submitted.event_type = 'report_submitted'
AND submitted.aggregate_type = 'report'
AND submitted.occurred_at >= @pilot_from
AND submitted.occurred_at < @pilot_to
AND (@pilot_store_id IS NULL OR submitted.store_id = @pilot_store_id)
GROUP BY submitted.store_id
ORDER BY submitted.store_id;
-- 3. 回访执行质量:以任务 due_date 落在窗口内为队列口径;退订不计员工完成。
SELECT
t.store_id,
COUNT(*) AS due_follow_up_count,
SUM(CASE WHEN t.status = 'completed' THEN 1 ELSE 0 END) AS staff_completed_count,
SUM(CASE WHEN t.status = 'canceled' AND t.outcome = 'unsubscribed' THEN 1 ELSE 0 END) AS unsubscribed_count,
SUM(CASE WHEN t.status IN ('pending', 'in_progress') THEN 1 ELSE 0 END) AS still_open_count,
SUM(CASE
WHEN t.status IN ('pending', 'in_progress') AND t.due_date < CURRENT_DATE
THEN 1 ELSE 0
END) AS overdue_open_count,
SUM(CASE
WHEN t.status = 'completed'
AND t.closed_at < DATE_ADD(t.due_date, INTERVAL 3 DAY)
THEN 1 ELSE 0
END) AS completed_by_due_plus_2d_count,
ROUND(
100 * SUM(CASE
WHEN t.status = 'completed'
AND t.closed_at < DATE_ADD(t.due_date, INTERVAL 3 DAY)
THEN 1 ELSE 0
END) / NULLIF(SUM(CASE WHEN t.status = 'completed' THEN 1 ELSE 0 END), 0),
1
) AS on_time_completion_rate_pct,
ROUND(AVG(t.attempt_count), 2) AS average_attempt_count,
SUM(CASE WHEN t.outcome = 'rebooked' THEN 1 ELSE 0 END) AS rebooked_task_count,
SUM(CASE WHEN t.outcome = 'not_interested' THEN 1 ELSE 0 END) AS not_interested_count,
SUM(CASE WHEN t.outcome = 'invalid_contact' THEN 1 ELSE 0 END) AS invalid_contact_count
FROM t_follow_up_task t
WHERE t.due_date >= DATE(@pilot_from)
AND t.due_date < DATE(@pilot_to)
AND (@pilot_store_id IS NULL OR t.store_id = @pilot_store_id)
GROUP BY t.store_id
ORDER BY t.store_id;
-- 4. 约 200 次完整服务样本闸门:全历史按店 + 最后一行全店合计。
SELECT
e.store_id,
COUNT(DISTINCT e.aggregate_id) AS complete_service_count,
MIN(e.occurred_at) AS first_complete_service_at,
MAX(e.occurred_at) AS latest_complete_service_at
FROM t_business_event e
WHERE e.event_type = 'service_completed'
AND e.aggregate_type = 'appointment'
AND (@pilot_store_id IS NULL OR e.store_id = @pilot_store_id)
GROUP BY e.store_id WITH ROLLUP;
-- 5. 指标可靠性反例:三项均须为 0完整发布还必须通过 31 项 production preflight。
SELECT COUNT(*) AS completed_services_without_submitted_report_event_count
FROM t_business_event completed
LEFT JOIN t_report r
ON r.appointment_id = completed.aggregate_id
AND r.store_id = completed.store_id
AND r.deleted = 0
LEFT JOIN t_business_event submitted
ON submitted.store_id = completed.store_id
AND submitted.event_type = 'report_submitted'
AND submitted.aggregate_type = 'report'
AND submitted.aggregate_id = r.id
WHERE completed.event_type = 'service_completed'
AND completed.aggregate_type = 'appointment'
AND (r.id IS NULL OR submitted.id IS NULL)
AND (@pilot_store_id IS NULL OR completed.store_id = @pilot_store_id);
SELECT COUNT(*) AS invalid_rebook_attribution_count
FROM t_follow_up_task t
LEFT JOIN t_appointment a
ON a.id = t.rebooked_appointment_id
AND a.store_id = t.store_id
AND a.deleted = 0
LEFT JOIN t_business_event e
ON e.idempotency_key = CONCAT('rebook_created:', t.rebooked_appointment_id)
AND e.event_type = 'rebook_created'
AND e.store_id = t.store_id
AND e.aggregate_type = 'appointment'
AND e.aggregate_id = t.rebooked_appointment_id
WHERE t.outcome = 'rebooked'
AND (a.id IS NULL OR e.id IS NULL)
AND (@pilot_store_id IS NULL OR t.store_id = @pilot_store_id);
SELECT COUNT(*) AS duplicate_open_follow_up_task_count
FROM (
SELECT t.store_id, t.source_lead_id
FROM t_follow_up_task t
WHERE t.status IN ('pending', 'in_progress')
AND (@pilot_store_id IS NULL OR t.store_id = @pilot_store_id)
GROUP BY t.store_id, t.source_lead_id
HAVING COUNT(*) > 1
) duplicated;