-- dd0c/portal analytics helpers + scanner constraint updates -- Update scan_history scanner check to include new scanner types ALTER TABLE scan_history DROP CONSTRAINT IF EXISTS scan_history_scanner_check; ALTER TABLE scan_history ADD CONSTRAINT scan_history_scanner_check CHECK (scanner IN ('aws', 'github', 'cloudformation', 'apigateway')); -- Update staged_updates source check to include new sources ALTER TABLE staged_updates DROP CONSTRAINT IF EXISTS staged_updates_source_check; ALTER TABLE staged_updates ADD CONSTRAINT staged_updates_source_check CHECK (source IN ('aws', 'github', 'cloudformation', 'apigateway', 'manual')); -- Materialized view: ownership coverage summary CREATE MATERIALIZED VIEW IF NOT EXISTS mv_ownership_coverage AS SELECT tenant_id, COUNT(*)::int AS total_services, COUNT(*) FILTER (WHERE owner != 'unknown')::int AS owned_services, COUNT(*) FILTER (WHERE owner = 'unknown')::int AS unowned_services, ROUND( (COUNT(*) FILTER (WHERE owner != 'unknown')::numeric / NULLIF(COUNT(*), 0)) * 100, 1 ) AS coverage_pct FROM services WHERE lifecycle = 'active' GROUP BY tenant_id; CREATE UNIQUE INDEX IF NOT EXISTS idx_mv_ownership_tenant ON mv_ownership_coverage(tenant_id); -- Materialized view: health scorecards by tier CREATE MATERIALIZED VIEW IF NOT EXISTS mv_health_by_tier AS SELECT tenant_id, tier, COUNT(*)::int AS count, COUNT(*) FILTER (WHERE last_discovered_at < NOW() - INTERVAL '7 days')::int AS stale_count FROM services WHERE lifecycle = 'active' GROUP BY tenant_id, tier; CREATE UNIQUE INDEX IF NOT EXISTS idx_mv_health_tier ON mv_health_by_tier(tenant_id, tier); -- Materialized view: tech debt indicators CREATE MATERIALIZED VIEW IF NOT EXISTS mv_tech_debt AS SELECT tenant_id, COUNT(*)::int AS total_active, COUNT(*) FILTER (WHERE description IS NULL OR description = '')::int AS missing_description, COUNT(*) FILTER (WHERE owner = 'unknown')::int AS missing_owner, COUNT(*) FILTER (WHERE owner_source = 'heuristic' AND owner != 'unknown')::int AS heuristic_ownership, COUNT(*) FILTER (WHERE links = '{}' OR links IS NULL)::int AS missing_links, COUNT(*) FILTER (WHERE last_discovered_at IS NULL)::int AS never_discovered FROM services WHERE lifecycle = 'active' GROUP BY tenant_id; CREATE UNIQUE INDEX IF NOT EXISTS idx_mv_tech_debt_tenant ON mv_tech_debt(tenant_id); -- Helper function to refresh all analytics materialized views CREATE OR REPLACE FUNCTION refresh_analytics_views() RETURNS void AS $$ BEGIN REFRESH MATERIALIZED VIEW CONCURRENTLY mv_ownership_coverage; REFRESH MATERIALIZED VIEW CONCURRENTLY mv_health_by_tier; REFRESH MATERIALIZED VIEW CONCURRENTLY mv_tech_debt; END; $$ LANGUAGE plpgsql;