From fa8adaebcf0b53cbb1ca71904ce91a522be3206c Mon Sep 17 00:00:00 2001 From: ericsocrat Date: Tue, 17 Mar 2026 18:27:44 +0100 Subject: [PATCH] feat(scanner): cross-country analytics views (#932) --- db/qa/QA__view_consistency.sql | 27 ++++++++ ...21000700_cross_country_analytics_views.sql | 65 +++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 supabase/migrations/20260321000700_cross_country_analytics_views.sql diff --git a/db/qa/QA__view_consistency.sql b/db/qa/QA__view_consistency.sql index 8e69452c..0aba77a1 100644 --- a/db/qa/QA__view_consistency.sql +++ b/db/qa/QA__view_consistency.sql @@ -173,3 +173,30 @@ SELECT '13. v_master has expected column count (58)' AS check_name, FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'v_master'; + +-- ═══════════════════════════════════════════════════════════════════════════ +-- 14. v_cross_country_scan_analytics view exists and has 7 columns +-- ═══════════════════════════════════════════════════════════════════════════ +SELECT '14. v_cross_country_scan_analytics has 7 columns' AS check_name, + ABS(7 - COUNT(*)) AS violations +FROM information_schema.columns +WHERE table_schema = 'public' + AND table_name = 'v_cross_country_scan_analytics'; + +-- ═══════════════════════════════════════════════════════════════════════════ +-- 15. v_cross_country_ean_candidates view exists and has 6 columns +-- ═══════════════════════════════════════════════════════════════════════════ +SELECT '15. v_cross_country_ean_candidates has 6 columns' AS check_name, + ABS(6 - COUNT(*)) AS violations +FROM information_schema.columns +WHERE table_schema = 'public' + AND table_name = 'v_cross_country_ean_candidates'; + +-- ═══════════════════════════════════════════════════════════════════════════ +-- 16. v_submission_country_analytics view exists and has 7 columns +-- ═══════════════════════════════════════════════════════════════════════════ +SELECT '16. v_submission_country_analytics has 7 columns' AS check_name, + ABS(7 - COUNT(*)) AS violations +FROM information_schema.columns +WHERE table_schema = 'public' + AND table_name = 'v_submission_country_analytics'; diff --git a/supabase/migrations/20260321000700_cross_country_analytics_views.sql b/supabase/migrations/20260321000700_cross_country_analytics_views.sql new file mode 100644 index 00000000..c0adb54c --- /dev/null +++ b/supabase/migrations/20260321000700_cross_country_analytics_views.sql @@ -0,0 +1,65 @@ +-- Migration: Cross-country scan and submission analytics views +-- Issue: #932 +-- Rollback: DROP VIEW IF EXISTS v_cross_country_scan_analytics, v_cross_country_ean_candidates, v_submission_country_analytics; +-- Idempotency: CREATE OR REPLACE VIEW + +-- ═══════════════════════════════════════════════════════════════════════════ +-- 1. Per-country scan metrics +-- ═══════════════════════════════════════════════════════════════════════════ +CREATE OR REPLACE VIEW public.v_cross_country_scan_analytics AS +SELECT + scan_country, + count(*) AS total_scans, + count(*) FILTER (WHERE found = true) AS found_scans, + count(*) FILTER (WHERE found = false) AS missed_scans, + round(100.0 * count(*) FILTER (WHERE found = false) + / NULLIF(count(*), 0), 1) AS miss_rate_pct, + count(DISTINCT ean) AS unique_eans_scanned, + count(DISTINCT ean) FILTER (WHERE found = false) AS unique_eans_missed +FROM public.scan_history +WHERE scan_country IS NOT NULL +GROUP BY scan_country; + +COMMENT ON VIEW public.v_cross_country_scan_analytics IS + 'Per-country scan metrics: total, found, missed, miss rate, unique EANs. Excludes rows with NULL scan_country. Issue #932.'; + +-- ═══════════════════════════════════════════════════════════════════════════ +-- 2. Cross-country EAN candidates (scanned in >1 country) +-- ═══════════════════════════════════════════════════════════════════════════ +CREATE OR REPLACE VIEW public.v_cross_country_ean_candidates AS +SELECT + ean, + array_agg(DISTINCT scan_country ORDER BY scan_country) AS scanned_in_countries, + count(DISTINCT scan_country) AS country_count, + min(scanned_at) AS first_scanned, + max(scanned_at) AS last_scanned, + count(*) AS total_scans +FROM public.scan_history +WHERE scan_country IS NOT NULL + AND ean IS NOT NULL +GROUP BY ean +HAVING count(DISTINCT scan_country) > 1 +ORDER BY total_scans DESC; + +COMMENT ON VIEW public.v_cross_country_ean_candidates IS + 'EANs scanned in more than one country — candidates for product_links. Excludes NULL scan_country/ean. Issue #932.'; + +-- ═══════════════════════════════════════════════════════════════════════════ +-- 3. Per-country submission metrics +-- ═══════════════════════════════════════════════════════════════════════════ +CREATE OR REPLACE VIEW public.v_submission_country_analytics AS +SELECT + suggested_country, + count(*) AS total_submissions, + count(*) FILTER (WHERE status = 'pending') AS pending, + count(*) FILTER (WHERE status = 'approved') AS approved, + count(*) FILTER (WHERE status = 'rejected') AS rejected, + count(*) FILTER (WHERE status = 'merged') AS merged, + round(100.0 * count(*) FILTER (WHERE status = 'approved' OR status = 'merged') + / NULLIF(count(*), 0), 1) AS acceptance_rate_pct +FROM public.product_submissions +WHERE suggested_country IS NOT NULL +GROUP BY suggested_country; + +COMMENT ON VIEW public.v_submission_country_analytics IS + 'Per-country submission metrics: total, by status, acceptance rate. Excludes rows with NULL suggested_country. Issue #932.';