mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-28 08:27:00 -05:00
Compare commits
5 Commits
e2ee11b9f5
...
888ef0224a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
888ef0224a | ||
|
|
78e29f9e49 | ||
|
|
842861af8c | ||
|
|
348ab23d26 | ||
|
|
b58a0a7741 |
@@ -6,12 +6,15 @@
|
||||
*/
|
||||
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { supabase } from '@/integrations/supabase/client';
|
||||
import { useEffect } from 'react';
|
||||
import type { CompletenessAnalysis, CompletenessFilters } from '@/types/data-completeness';
|
||||
import { handleError } from '@/lib/errorHandler';
|
||||
|
||||
export function useDataCompleteness(filters: CompletenessFilters = {}) {
|
||||
const location = useLocation();
|
||||
const isAdminPage = location.pathname.startsWith('/admin');
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const query = useQuery({
|
||||
@@ -40,6 +43,7 @@ export function useDataCompleteness(filters: CompletenessFilters = {}) {
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
enabled: isAdminPage, // Only run on admin pages
|
||||
staleTime: 5 * 60 * 1000, // Cache for 5 minutes
|
||||
refetchOnWindowFocus: false,
|
||||
});
|
||||
|
||||
@@ -6816,6 +6816,7 @@ export type Database = {
|
||||
Returns: string
|
||||
}
|
||||
extract_cf_image_id: { Args: { url: string }; Returns: string }
|
||||
filter_jsonb_array_nulls: { Args: { arr: Json }; Returns: Json }
|
||||
generate_deletion_confirmation_code: { Args: never; Returns: string }
|
||||
generate_incident_number: { Args: never; Returns: string }
|
||||
generate_notification_idempotency_key: {
|
||||
|
||||
@@ -368,7 +368,7 @@ export async function fetchSystemActivities(
|
||||
}
|
||||
|
||||
// Fetch submission reviews (approved/rejected submissions)
|
||||
// Note: Content is now in submission_metadata table, but entity_name is cached in view
|
||||
// Note: Content is now in submission_metadata table - need to join and filter properly
|
||||
const { data: submissions, error: submissionsError } = await supabase
|
||||
.from('content_submissions')
|
||||
.select(`
|
||||
@@ -377,8 +377,9 @@ export async function fetchSystemActivities(
|
||||
status,
|
||||
reviewer_id,
|
||||
reviewed_at,
|
||||
submission_metadata(name)
|
||||
submission_metadata!inner(metadata_value)
|
||||
`)
|
||||
.eq('submission_metadata.metadata_key', 'name')
|
||||
.not('reviewed_at', 'is', null)
|
||||
.in('status', ['approved', 'rejected', 'partially_approved'])
|
||||
.order('reviewed_at', { ascending: false })
|
||||
@@ -415,10 +416,10 @@ export async function fetchSystemActivities(
|
||||
);
|
||||
|
||||
for (const submission of submissions) {
|
||||
// Get name from submission_metadata
|
||||
// Get name from submission_metadata - extract metadata_value from the joined result
|
||||
const metadata = submission.submission_metadata as any;
|
||||
const entityName = Array.isArray(metadata) && metadata.length > 0
|
||||
? metadata[0]?.name
|
||||
? metadata[0]?.metadata_value
|
||||
: undefined;
|
||||
|
||||
const submissionItem = itemsMap.get(submission.id);
|
||||
|
||||
@@ -291,8 +291,9 @@ export default function Profile() {
|
||||
submission_type,
|
||||
status,
|
||||
created_at,
|
||||
submission_metadata(name)
|
||||
submission_metadata!inner(metadata_value)
|
||||
`)
|
||||
.eq('submission_metadata.metadata_key', 'name')
|
||||
.eq('user_id', userId)
|
||||
.order('created_at', { ascending: false })
|
||||
.limit(10);
|
||||
@@ -310,10 +311,10 @@ export default function Profile() {
|
||||
const enrichedSubmissions = await Promise.all((submissions || []).map(async (sub) => {
|
||||
const enriched: any = { ...sub };
|
||||
|
||||
// Get name from submission_metadata
|
||||
// Get name from submission_metadata - extract metadata_value from the joined result
|
||||
const metadata = sub.submission_metadata as any;
|
||||
enriched.name = Array.isArray(metadata) && metadata.length > 0
|
||||
? metadata[0]?.name
|
||||
? metadata[0]?.metadata_value
|
||||
: undefined;
|
||||
|
||||
// For photo submissions, get photo count and preview
|
||||
|
||||
@@ -0,0 +1,412 @@
|
||||
-- Fix JSONB array filtering in analyze_data_completeness function
|
||||
-- Replace invalid '- null::jsonb' operations with proper array filtering
|
||||
|
||||
-- Helper function to filter null values from JSONB arrays
|
||||
CREATE OR REPLACE FUNCTION filter_jsonb_array_nulls(arr JSONB)
|
||||
RETURNS JSONB
|
||||
LANGUAGE SQL
|
||||
IMMUTABLE
|
||||
AS $$
|
||||
SELECT COALESCE(
|
||||
jsonb_agg(element),
|
||||
'[]'::jsonb
|
||||
)
|
||||
FROM jsonb_array_elements_text(arr) element
|
||||
WHERE element != 'null'
|
||||
$$;
|
||||
|
||||
-- Replace analyze_data_completeness with fixed JSONB array handling
|
||||
CREATE OR REPLACE FUNCTION analyze_data_completeness(
|
||||
p_entity_type TEXT DEFAULT NULL,
|
||||
p_min_score NUMERIC DEFAULT NULL,
|
||||
p_max_score NUMERIC DEFAULT NULL,
|
||||
p_missing_category TEXT DEFAULT NULL,
|
||||
p_limit INTEGER DEFAULT 100,
|
||||
p_offset INTEGER DEFAULT 0
|
||||
)
|
||||
RETURNS JSONB
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
SET search_path = public
|
||||
AS $$
|
||||
DECLARE
|
||||
v_result JSONB;
|
||||
v_parks JSONB;
|
||||
v_rides JSONB;
|
||||
v_companies JSONB;
|
||||
v_ride_models JSONB;
|
||||
v_locations JSONB;
|
||||
v_timeline_events JSONB;
|
||||
v_summary JSONB;
|
||||
BEGIN
|
||||
-- Parks Analysis (including historical)
|
||||
WITH park_analysis AS (
|
||||
SELECT
|
||||
p.id,
|
||||
p.name,
|
||||
p.slug,
|
||||
'park' as entity_type,
|
||||
p.updated_at,
|
||||
-- Calculate completeness score (weighted)
|
||||
(
|
||||
-- Critical fields (10 points each) = 30 points
|
||||
(CASE WHEN p.park_type IS NOT NULL THEN 10 ELSE 0 END) +
|
||||
(CASE WHEN p.status IS NOT NULL THEN 10 ELSE 0 END) +
|
||||
(CASE WHEN p.location_id IS NOT NULL THEN 10 ELSE 0 END) +
|
||||
|
||||
-- Important fields (7 points each) = 35 points
|
||||
(CASE WHEN p.description IS NOT NULL AND length(p.description) > 50 THEN 7 ELSE 0 END) +
|
||||
(CASE WHEN p.operator_id IS NOT NULL THEN 7 ELSE 0 END) +
|
||||
(CASE WHEN p.banner_image_id IS NOT NULL THEN 7 ELSE 0 END) +
|
||||
(CASE WHEN p.card_image_id IS NOT NULL THEN 7 ELSE 0 END) +
|
||||
(CASE WHEN p.property_owner_id IS NOT NULL THEN 7 ELSE 0 END) +
|
||||
|
||||
-- Valuable fields (5 points each) = 20 points
|
||||
(CASE WHEN p.opening_date IS NOT NULL THEN 5 ELSE 0 END) +
|
||||
(CASE WHEN p.opening_date_precision IS NOT NULL THEN 5 ELSE 0 END) +
|
||||
(CASE WHEN p.website_url IS NOT NULL THEN 5 ELSE 0 END) +
|
||||
(CASE WHEN p.phone IS NOT NULL THEN 5 ELSE 0 END) +
|
||||
|
||||
-- Supplementary fields (3 points each) = 9 points
|
||||
(CASE WHEN p.email IS NOT NULL THEN 3 ELSE 0 END) +
|
||||
(CASE WHEN p.closing_date IS NOT NULL AND p.status = 'closed' THEN 3 ELSE 0 END) +
|
||||
(CASE WHEN EXISTS(SELECT 1 FROM entity_timeline_events WHERE entity_id = p.id AND entity_type = 'park') THEN 3 ELSE 0 END) +
|
||||
|
||||
-- Nice-to-have fields (1 point each) = 6 points
|
||||
(CASE WHEN EXISTS(SELECT 1 FROM locations WHERE id = p.location_id AND latitude IS NOT NULL AND longitude IS NOT NULL) THEN 1 ELSE 0 END) +
|
||||
(CASE WHEN p.closing_date_precision IS NOT NULL AND p.status = 'closed' THEN 1 ELSE 0 END)
|
||||
)::NUMERIC / 100.0 * 100 as completeness_score,
|
||||
|
||||
-- Missing fields tracking (using helper function)
|
||||
jsonb_build_object(
|
||||
'critical', filter_jsonb_array_nulls(jsonb_build_array(
|
||||
CASE WHEN p.park_type IS NULL THEN 'park_type' END,
|
||||
CASE WHEN p.status IS NULL THEN 'status' END,
|
||||
CASE WHEN p.location_id IS NULL THEN 'location_id' END
|
||||
)),
|
||||
'important', filter_jsonb_array_nulls(jsonb_build_array(
|
||||
CASE WHEN p.description IS NULL OR length(p.description) <= 50 THEN 'description' END,
|
||||
CASE WHEN p.operator_id IS NULL THEN 'operator_id' END,
|
||||
CASE WHEN p.banner_image_id IS NULL THEN 'banner_image' END,
|
||||
CASE WHEN p.card_image_id IS NULL THEN 'card_image' END,
|
||||
CASE WHEN p.property_owner_id IS NULL THEN 'property_owner_id' END
|
||||
)),
|
||||
'valuable', filter_jsonb_array_nulls(jsonb_build_array(
|
||||
CASE WHEN p.opening_date IS NULL THEN 'opening_date' END,
|
||||
CASE WHEN p.opening_date_precision IS NULL THEN 'opening_date_precision' END,
|
||||
CASE WHEN p.website_url IS NULL THEN 'website_url' END,
|
||||
CASE WHEN p.phone IS NULL THEN 'phone' END
|
||||
)),
|
||||
'supplementary', filter_jsonb_array_nulls(jsonb_build_array(
|
||||
CASE WHEN p.email IS NULL THEN 'email' END,
|
||||
CASE WHEN p.closing_date IS NULL AND p.status = 'closed' THEN 'closing_date' END
|
||||
))
|
||||
) as missing_fields
|
||||
FROM parks p
|
||||
WHERE (p_entity_type IS NULL OR p_entity_type = 'park')
|
||||
)
|
||||
SELECT jsonb_agg(
|
||||
jsonb_build_object(
|
||||
'id', id,
|
||||
'name', name,
|
||||
'slug', slug,
|
||||
'entity_type', entity_type,
|
||||
'updated_at', updated_at,
|
||||
'completeness_score', completeness_score,
|
||||
'missing_fields', missing_fields
|
||||
) ORDER BY completeness_score ASC, name ASC
|
||||
)
|
||||
INTO v_parks
|
||||
FROM park_analysis
|
||||
WHERE (p_min_score IS NULL OR completeness_score >= p_min_score)
|
||||
AND (p_max_score IS NULL OR completeness_score <= p_max_score)
|
||||
LIMIT p_limit OFFSET p_offset;
|
||||
|
||||
-- Rides Analysis
|
||||
WITH ride_analysis AS (
|
||||
SELECT
|
||||
r.id,
|
||||
r.name,
|
||||
r.slug,
|
||||
'ride' as entity_type,
|
||||
r.updated_at,
|
||||
-- Calculate completeness score (weighted)
|
||||
(
|
||||
-- Critical fields (10 points each) = 30 points
|
||||
(CASE WHEN r.park_id IS NOT NULL THEN 10 ELSE 0 END) +
|
||||
(CASE WHEN r.category IS NOT NULL THEN 10 ELSE 0 END) +
|
||||
(CASE WHEN r.status IS NOT NULL THEN 10 ELSE 0 END) +
|
||||
|
||||
-- Important fields (7 points each) = 42 points
|
||||
(CASE WHEN r.description IS NOT NULL AND length(r.description) > 50 THEN 7 ELSE 0 END) +
|
||||
(CASE WHEN r.manufacturer_id IS NOT NULL THEN 7 ELSE 0 END) +
|
||||
(CASE WHEN r.banner_image_id IS NOT NULL THEN 7 ELSE 0 END) +
|
||||
(CASE WHEN r.card_image_id IS NOT NULL THEN 7 ELSE 0 END) +
|
||||
(CASE WHEN r.ride_model_id IS NOT NULL THEN 7 ELSE 0 END) +
|
||||
(CASE WHEN r.designer_id IS NOT NULL THEN 7 ELSE 0 END) +
|
||||
|
||||
-- Valuable fields (5 points each) = 15 points
|
||||
(CASE WHEN r.opening_date IS NOT NULL THEN 5 ELSE 0 END) +
|
||||
(CASE WHEN r.opening_date_precision IS NOT NULL THEN 5 ELSE 0 END) +
|
||||
(CASE WHEN r.ride_sub_type IS NOT NULL THEN 5 ELSE 0 END) +
|
||||
|
||||
-- Category-specific technical data (5 points each) = up to 10 points
|
||||
(CASE
|
||||
WHEN r.category = 'Roller Coaster' THEN
|
||||
(CASE WHEN r.coaster_type IS NOT NULL THEN 5 ELSE 0 END) +
|
||||
(CASE WHEN r.max_speed_kmh IS NOT NULL THEN 5 ELSE 0 END)
|
||||
WHEN r.category = 'Water Ride' THEN
|
||||
(CASE WHEN r.flume_type IS NOT NULL THEN 5 ELSE 0 END) +
|
||||
(CASE WHEN r.wetness_level IS NOT NULL THEN 5 ELSE 0 END)
|
||||
WHEN r.category = 'Dark Ride' THEN
|
||||
(CASE WHEN r.theme_name IS NOT NULL THEN 5 ELSE 0 END) +
|
||||
(CASE WHEN r.ride_system IS NOT NULL THEN 5 ELSE 0 END)
|
||||
ELSE 0
|
||||
END) +
|
||||
|
||||
-- Supplementary fields (3 points each) = 9 points
|
||||
(CASE WHEN r.max_height_meters IS NOT NULL THEN 3 ELSE 0 END) +
|
||||
(CASE WHEN r.length_meters IS NOT NULL THEN 3 ELSE 0 END) +
|
||||
(CASE WHEN r.capacity_per_hour IS NOT NULL THEN 3 ELSE 0 END)
|
||||
)::NUMERIC / 100.0 * 100 as completeness_score,
|
||||
|
||||
-- Missing fields tracking (using helper function)
|
||||
jsonb_build_object(
|
||||
'critical', filter_jsonb_array_nulls(jsonb_build_array(
|
||||
CASE WHEN r.park_id IS NULL THEN 'park_id' END,
|
||||
CASE WHEN r.category IS NULL THEN 'category' END,
|
||||
CASE WHEN r.status IS NULL THEN 'status' END
|
||||
)),
|
||||
'important', filter_jsonb_array_nulls(jsonb_build_array(
|
||||
CASE WHEN r.description IS NULL OR length(r.description) <= 50 THEN 'description' END,
|
||||
CASE WHEN r.manufacturer_id IS NULL THEN 'manufacturer_id' END,
|
||||
CASE WHEN r.banner_image_id IS NULL THEN 'banner_image' END,
|
||||
CASE WHEN r.card_image_id IS NULL THEN 'card_image' END,
|
||||
CASE WHEN r.ride_model_id IS NULL THEN 'ride_model_id' END,
|
||||
CASE WHEN r.designer_id IS NULL THEN 'designer_id' END
|
||||
)),
|
||||
'valuable', filter_jsonb_array_nulls(jsonb_build_array(
|
||||
CASE WHEN r.opening_date IS NULL THEN 'opening_date' END,
|
||||
CASE WHEN r.opening_date_precision IS NULL THEN 'opening_date_precision' END,
|
||||
CASE WHEN r.ride_sub_type IS NULL THEN 'ride_sub_type' END
|
||||
))
|
||||
) as missing_fields
|
||||
FROM rides r
|
||||
WHERE (p_entity_type IS NULL OR p_entity_type = 'ride')
|
||||
)
|
||||
SELECT jsonb_agg(
|
||||
jsonb_build_object(
|
||||
'id', id,
|
||||
'name', name,
|
||||
'slug', slug,
|
||||
'entity_type', entity_type,
|
||||
'updated_at', updated_at,
|
||||
'completeness_score', completeness_score,
|
||||
'missing_fields', missing_fields
|
||||
) ORDER BY completeness_score ASC, name ASC
|
||||
)
|
||||
INTO v_rides
|
||||
FROM ride_analysis
|
||||
WHERE (p_min_score IS NULL OR completeness_score >= p_min_score)
|
||||
AND (p_max_score IS NULL OR completeness_score <= p_max_score)
|
||||
LIMIT p_limit OFFSET p_offset;
|
||||
|
||||
-- Companies Analysis
|
||||
WITH company_analysis AS (
|
||||
SELECT
|
||||
c.id,
|
||||
c.name,
|
||||
c.slug,
|
||||
'company' as entity_type,
|
||||
c.updated_at,
|
||||
-- Calculate completeness score (weighted)
|
||||
(
|
||||
-- Critical fields (10 points each) = 20 points
|
||||
(CASE WHEN c.company_type IS NOT NULL THEN 10 ELSE 0 END) +
|
||||
(CASE WHEN c.person_type IS NOT NULL THEN 10 ELSE 0 END) +
|
||||
|
||||
-- Important fields (7 points each) = 28 points
|
||||
(CASE WHEN c.description IS NOT NULL AND length(c.description) > 50 THEN 7 ELSE 0 END) +
|
||||
(CASE WHEN c.logo_url IS NOT NULL THEN 7 ELSE 0 END) +
|
||||
(CASE WHEN c.banner_image_id IS NOT NULL THEN 7 ELSE 0 END) +
|
||||
(CASE WHEN c.card_image_id IS NOT NULL THEN 7 ELSE 0 END) +
|
||||
|
||||
-- Valuable fields (5 points each) = 20 points
|
||||
(CASE WHEN c.founded_year IS NOT NULL THEN 5 ELSE 0 END) +
|
||||
(CASE WHEN c.founded_date IS NOT NULL THEN 5 ELSE 0 END) +
|
||||
(CASE WHEN c.website_url IS NOT NULL THEN 5 ELSE 0 END) +
|
||||
(CASE WHEN c.headquarters_location IS NOT NULL THEN 5 ELSE 0 END) +
|
||||
|
||||
-- Supplementary fields (3 points each) = 6 points
|
||||
(CASE WHEN c.founded_date_precision IS NOT NULL THEN 3 ELSE 0 END) +
|
||||
(CASE WHEN c.company_type IN ('manufacturer', 'operator') AND EXISTS(SELECT 1 FROM parks WHERE operator_id = c.id OR property_owner_id = c.id LIMIT 1) THEN 3 ELSE 0 END)
|
||||
)::NUMERIC / 100.0 * 100 as completeness_score,
|
||||
|
||||
-- Missing fields tracking (using helper function)
|
||||
jsonb_build_object(
|
||||
'critical', filter_jsonb_array_nulls(jsonb_build_array(
|
||||
CASE WHEN c.company_type IS NULL THEN 'company_type' END,
|
||||
CASE WHEN c.person_type IS NULL THEN 'person_type' END
|
||||
)),
|
||||
'important', filter_jsonb_array_nulls(jsonb_build_array(
|
||||
CASE WHEN c.description IS NULL OR length(c.description) <= 50 THEN 'description' END,
|
||||
CASE WHEN c.logo_url IS NULL THEN 'logo_url' END,
|
||||
CASE WHEN c.banner_image_id IS NULL THEN 'banner_image' END,
|
||||
CASE WHEN c.card_image_id IS NULL THEN 'card_image' END
|
||||
)),
|
||||
'valuable', filter_jsonb_array_nulls(jsonb_build_array(
|
||||
CASE WHEN c.founded_year IS NULL THEN 'founded_year' END,
|
||||
CASE WHEN c.founded_date IS NULL THEN 'founded_date' END,
|
||||
CASE WHEN c.website_url IS NULL THEN 'website_url' END,
|
||||
CASE WHEN c.headquarters_location IS NULL THEN 'headquarters_location' END
|
||||
))
|
||||
) as missing_fields
|
||||
FROM companies c
|
||||
WHERE (p_entity_type IS NULL OR p_entity_type = 'company')
|
||||
)
|
||||
SELECT jsonb_agg(
|
||||
jsonb_build_object(
|
||||
'id', id,
|
||||
'name', name,
|
||||
'slug', slug,
|
||||
'entity_type', entity_type,
|
||||
'updated_at', updated_at,
|
||||
'completeness_score', completeness_score,
|
||||
'missing_fields', missing_fields
|
||||
) ORDER BY completeness_score ASC, name ASC
|
||||
)
|
||||
INTO v_companies
|
||||
FROM company_analysis
|
||||
WHERE (p_min_score IS NULL OR completeness_score >= p_min_score)
|
||||
AND (p_max_score IS NULL OR completeness_score <= p_max_score)
|
||||
LIMIT p_limit OFFSET p_offset;
|
||||
|
||||
-- Ride Models Analysis
|
||||
WITH model_analysis AS (
|
||||
SELECT
|
||||
rm.id,
|
||||
rm.name,
|
||||
rm.slug,
|
||||
'ride_model' as entity_type,
|
||||
rm.updated_at,
|
||||
-- Calculate completeness score (weighted)
|
||||
(
|
||||
-- Critical fields (10 points each) = 30 points
|
||||
(CASE WHEN rm.manufacturer_id IS NOT NULL THEN 10 ELSE 0 END) +
|
||||
(CASE WHEN rm.category IS NOT NULL THEN 10 ELSE 0 END) +
|
||||
(CASE WHEN rm.ride_type IS NOT NULL THEN 10 ELSE 0 END) +
|
||||
|
||||
-- Important fields (7 points each) = 21 points
|
||||
(CASE WHEN rm.description IS NOT NULL AND length(rm.description) > 50 THEN 7 ELSE 0 END) +
|
||||
(CASE WHEN rm.banner_image_id IS NOT NULL THEN 7 ELSE 0 END) +
|
||||
(CASE WHEN rm.card_image_id IS NOT NULL THEN 7 ELSE 0 END) +
|
||||
|
||||
-- Valuable fields (5 points each) = 10 points
|
||||
(CASE WHEN EXISTS(SELECT 1 FROM rides WHERE ride_model_id = rm.id LIMIT 1) THEN 5 ELSE 0 END) +
|
||||
(CASE WHEN rm.introduction_year IS NOT NULL THEN 5 ELSE 0 END)
|
||||
)::NUMERIC / 100.0 * 100 as completeness_score,
|
||||
|
||||
-- Missing fields tracking (using helper function)
|
||||
jsonb_build_object(
|
||||
'critical', filter_jsonb_array_nulls(jsonb_build_array(
|
||||
CASE WHEN rm.manufacturer_id IS NULL THEN 'manufacturer_id' END,
|
||||
CASE WHEN rm.category IS NULL THEN 'category' END,
|
||||
CASE WHEN rm.ride_type IS NULL THEN 'ride_type' END
|
||||
)),
|
||||
'important', filter_jsonb_array_nulls(jsonb_build_array(
|
||||
CASE WHEN rm.description IS NULL OR length(rm.description) <= 50 THEN 'description' END,
|
||||
CASE WHEN rm.banner_image_id IS NULL THEN 'banner_image' END,
|
||||
CASE WHEN rm.card_image_id IS NULL THEN 'card_image' END
|
||||
)),
|
||||
'valuable', filter_jsonb_array_nulls(jsonb_build_array(
|
||||
CASE WHEN rm.introduction_year IS NULL THEN 'introduction_year' END
|
||||
))
|
||||
) as missing_fields
|
||||
FROM ride_models rm
|
||||
WHERE (p_entity_type IS NULL OR p_entity_type = 'ride_model')
|
||||
)
|
||||
SELECT jsonb_agg(
|
||||
jsonb_build_object(
|
||||
'id', id,
|
||||
'name', name,
|
||||
'slug', slug,
|
||||
'entity_type', entity_type,
|
||||
'updated_at', updated_at,
|
||||
'completeness_score', completeness_score,
|
||||
'missing_fields', missing_fields
|
||||
) ORDER BY completeness_score ASC, name ASC
|
||||
)
|
||||
INTO v_ride_models
|
||||
FROM model_analysis
|
||||
WHERE (p_min_score IS NULL OR completeness_score >= p_min_score)
|
||||
AND (p_max_score IS NULL OR completeness_score <= p_max_score)
|
||||
LIMIT p_limit OFFSET p_offset;
|
||||
|
||||
-- Generate Summary
|
||||
v_summary := jsonb_build_object(
|
||||
'total_entities', (
|
||||
SELECT COUNT(*)::INTEGER FROM (
|
||||
SELECT id FROM parks WHERE (p_entity_type IS NULL OR p_entity_type = 'park')
|
||||
UNION ALL
|
||||
SELECT id FROM rides WHERE (p_entity_type IS NULL OR p_entity_type = 'ride')
|
||||
UNION ALL
|
||||
SELECT id FROM companies WHERE (p_entity_type IS NULL OR p_entity_type = 'company')
|
||||
UNION ALL
|
||||
SELECT id FROM ride_models WHERE (p_entity_type IS NULL OR p_entity_type = 'ride_model')
|
||||
) all_entities
|
||||
),
|
||||
'avg_completeness_score', (
|
||||
SELECT ROUND(AVG(score)::NUMERIC, 2) FROM (
|
||||
SELECT ((10 + 10 + 10)::NUMERIC / 100.0 * 100) as score FROM parks WHERE park_type IS NOT NULL AND status IS NOT NULL AND location_id IS NOT NULL
|
||||
UNION ALL
|
||||
SELECT ((10 + 10 + 10)::NUMERIC / 100.0 * 100) as score FROM rides WHERE park_id IS NOT NULL AND category IS NOT NULL AND status IS NOT NULL
|
||||
UNION ALL
|
||||
SELECT ((10 + 10)::NUMERIC / 100.0 * 100) as score FROM companies WHERE company_type IS NOT NULL AND person_type IS NOT NULL
|
||||
UNION ALL
|
||||
SELECT ((10 + 10 + 10)::NUMERIC / 100.0 * 100) as score FROM ride_models WHERE manufacturer_id IS NOT NULL AND category IS NOT NULL AND ride_type IS NOT NULL
|
||||
) scores
|
||||
),
|
||||
'entities_below_50', (
|
||||
SELECT COUNT(*)::INTEGER FROM (
|
||||
SELECT id FROM parks WHERE (p_entity_type IS NULL OR p_entity_type = 'park')
|
||||
UNION ALL
|
||||
SELECT id FROM rides WHERE (p_entity_type IS NULL OR p_entity_type = 'ride')
|
||||
UNION ALL
|
||||
SELECT id FROM companies WHERE (p_entity_type IS NULL OR p_entity_type = 'company')
|
||||
UNION ALL
|
||||
SELECT id FROM ride_models WHERE (p_entity_type IS NULL OR p_entity_type = 'ride_model')
|
||||
) all_entities
|
||||
WHERE id IN (
|
||||
SELECT id FROM parks WHERE description IS NULL OR manufacturer_id IS NULL
|
||||
UNION
|
||||
SELECT id FROM rides WHERE description IS NULL OR manufacturer_id IS NULL
|
||||
UNION
|
||||
SELECT id FROM companies WHERE description IS NULL
|
||||
UNION
|
||||
SELECT id FROM ride_models WHERE description IS NULL
|
||||
)
|
||||
),
|
||||
'entities_100_complete', 0,
|
||||
'by_entity_type', jsonb_build_object(
|
||||
'parks', (SELECT COUNT(*)::INTEGER FROM parks WHERE (p_entity_type IS NULL OR p_entity_type = 'park')),
|
||||
'rides', (SELECT COUNT(*)::INTEGER FROM rides WHERE (p_entity_type IS NULL OR p_entity_type = 'ride')),
|
||||
'companies', (SELECT COUNT(*)::INTEGER FROM companies WHERE (p_entity_type IS NULL OR p_entity_type = 'company')),
|
||||
'ride_models', (SELECT COUNT(*)::INTEGER FROM ride_models WHERE (p_entity_type IS NULL OR p_entity_type = 'ride_model'))
|
||||
)
|
||||
);
|
||||
|
||||
-- Build final result
|
||||
v_result := jsonb_build_object(
|
||||
'summary', v_summary,
|
||||
'entities', jsonb_build_object(
|
||||
'parks', COALESCE(v_parks, '[]'::jsonb),
|
||||
'rides', COALESCE(v_rides, '[]'::jsonb),
|
||||
'companies', COALESCE(v_companies, '[]'::jsonb),
|
||||
'ride_models', COALESCE(v_ride_models, '[]'::jsonb)
|
||||
),
|
||||
'generated_at', now()
|
||||
);
|
||||
|
||||
RETURN v_result;
|
||||
END;
|
||||
$$;
|
||||
@@ -0,0 +1,14 @@
|
||||
-- Fix search_path security issue for filter_jsonb_array_nulls function
|
||||
CREATE OR REPLACE FUNCTION filter_jsonb_array_nulls(arr JSONB)
|
||||
RETURNS JSONB
|
||||
LANGUAGE SQL
|
||||
IMMUTABLE
|
||||
SET search_path = public
|
||||
AS $$
|
||||
SELECT COALESCE(
|
||||
jsonb_agg(element),
|
||||
'[]'::jsonb
|
||||
)
|
||||
FROM jsonb_array_elements_text(arr) element
|
||||
WHERE element != 'null'
|
||||
$$;
|
||||
@@ -0,0 +1,398 @@
|
||||
-- Fix analyze_data_completeness: Remove non-existent introduction_year column reference
|
||||
-- The ride_models table doesn't have an introduction_year column
|
||||
|
||||
CREATE OR REPLACE FUNCTION analyze_data_completeness(
|
||||
p_entity_type TEXT DEFAULT NULL,
|
||||
p_min_score NUMERIC DEFAULT NULL,
|
||||
p_max_score NUMERIC DEFAULT NULL,
|
||||
p_missing_category TEXT DEFAULT NULL,
|
||||
p_limit INTEGER DEFAULT 100,
|
||||
p_offset INTEGER DEFAULT 0
|
||||
)
|
||||
RETURNS JSONB
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
SET search_path = public
|
||||
AS $$
|
||||
DECLARE
|
||||
v_result JSONB;
|
||||
v_parks JSONB;
|
||||
v_rides JSONB;
|
||||
v_companies JSONB;
|
||||
v_ride_models JSONB;
|
||||
v_locations JSONB;
|
||||
v_timeline_events JSONB;
|
||||
v_summary JSONB;
|
||||
BEGIN
|
||||
-- Parks Analysis (including historical)
|
||||
WITH park_analysis AS (
|
||||
SELECT
|
||||
p.id,
|
||||
p.name,
|
||||
p.slug,
|
||||
'park' as entity_type,
|
||||
p.updated_at,
|
||||
-- Calculate completeness score (weighted)
|
||||
(
|
||||
-- Critical fields (10 points each) = 30 points
|
||||
(CASE WHEN p.park_type IS NOT NULL THEN 10 ELSE 0 END) +
|
||||
(CASE WHEN p.status IS NOT NULL THEN 10 ELSE 0 END) +
|
||||
(CASE WHEN p.location_id IS NOT NULL THEN 10 ELSE 0 END) +
|
||||
|
||||
-- Important fields (7 points each) = 35 points
|
||||
(CASE WHEN p.description IS NOT NULL AND length(p.description) > 50 THEN 7 ELSE 0 END) +
|
||||
(CASE WHEN p.operator_id IS NOT NULL THEN 7 ELSE 0 END) +
|
||||
(CASE WHEN p.banner_image_id IS NOT NULL THEN 7 ELSE 0 END) +
|
||||
(CASE WHEN p.card_image_id IS NOT NULL THEN 7 ELSE 0 END) +
|
||||
(CASE WHEN p.property_owner_id IS NOT NULL THEN 7 ELSE 0 END) +
|
||||
|
||||
-- Valuable fields (5 points each) = 20 points
|
||||
(CASE WHEN p.opening_date IS NOT NULL THEN 5 ELSE 0 END) +
|
||||
(CASE WHEN p.opening_date_precision IS NOT NULL THEN 5 ELSE 0 END) +
|
||||
(CASE WHEN p.website_url IS NOT NULL THEN 5 ELSE 0 END) +
|
||||
(CASE WHEN p.phone IS NOT NULL THEN 5 ELSE 0 END) +
|
||||
|
||||
-- Supplementary fields (3 points each) = 9 points
|
||||
(CASE WHEN p.email IS NOT NULL THEN 3 ELSE 0 END) +
|
||||
(CASE WHEN p.closing_date IS NOT NULL AND p.status = 'closed' THEN 3 ELSE 0 END) +
|
||||
(CASE WHEN EXISTS(SELECT 1 FROM entity_timeline_events WHERE entity_id = p.id AND entity_type = 'park') THEN 3 ELSE 0 END) +
|
||||
|
||||
-- Nice-to-have fields (1 point each) = 6 points
|
||||
(CASE WHEN EXISTS(SELECT 1 FROM locations WHERE id = p.location_id AND latitude IS NOT NULL AND longitude IS NOT NULL) THEN 1 ELSE 0 END) +
|
||||
(CASE WHEN p.closing_date_precision IS NOT NULL AND p.status = 'closed' THEN 1 ELSE 0 END)
|
||||
)::NUMERIC / 100.0 * 100 as completeness_score,
|
||||
|
||||
-- Missing fields tracking (using helper function)
|
||||
jsonb_build_object(
|
||||
'critical', filter_jsonb_array_nulls(jsonb_build_array(
|
||||
CASE WHEN p.park_type IS NULL THEN 'park_type' END,
|
||||
CASE WHEN p.status IS NULL THEN 'status' END,
|
||||
CASE WHEN p.location_id IS NULL THEN 'location_id' END
|
||||
)),
|
||||
'important', filter_jsonb_array_nulls(jsonb_build_array(
|
||||
CASE WHEN p.description IS NULL OR length(p.description) <= 50 THEN 'description' END,
|
||||
CASE WHEN p.operator_id IS NULL THEN 'operator_id' END,
|
||||
CASE WHEN p.banner_image_id IS NULL THEN 'banner_image' END,
|
||||
CASE WHEN p.card_image_id IS NULL THEN 'card_image' END,
|
||||
CASE WHEN p.property_owner_id IS NULL THEN 'property_owner_id' END
|
||||
)),
|
||||
'valuable', filter_jsonb_array_nulls(jsonb_build_array(
|
||||
CASE WHEN p.opening_date IS NULL THEN 'opening_date' END,
|
||||
CASE WHEN p.opening_date_precision IS NULL THEN 'opening_date_precision' END,
|
||||
CASE WHEN p.website_url IS NULL THEN 'website_url' END,
|
||||
CASE WHEN p.phone IS NULL THEN 'phone' END
|
||||
)),
|
||||
'supplementary', filter_jsonb_array_nulls(jsonb_build_array(
|
||||
CASE WHEN p.email IS NULL THEN 'email' END,
|
||||
CASE WHEN p.closing_date IS NULL AND p.status = 'closed' THEN 'closing_date' END
|
||||
))
|
||||
) as missing_fields
|
||||
FROM parks p
|
||||
WHERE (p_entity_type IS NULL OR p_entity_type = 'park')
|
||||
)
|
||||
SELECT jsonb_agg(
|
||||
jsonb_build_object(
|
||||
'id', id,
|
||||
'name', name,
|
||||
'slug', slug,
|
||||
'entity_type', entity_type,
|
||||
'updated_at', updated_at,
|
||||
'completeness_score', completeness_score,
|
||||
'missing_fields', missing_fields
|
||||
) ORDER BY completeness_score ASC, name ASC
|
||||
)
|
||||
INTO v_parks
|
||||
FROM park_analysis
|
||||
WHERE (p_min_score IS NULL OR completeness_score >= p_min_score)
|
||||
AND (p_max_score IS NULL OR completeness_score <= p_max_score)
|
||||
LIMIT p_limit OFFSET p_offset;
|
||||
|
||||
-- Rides Analysis
|
||||
WITH ride_analysis AS (
|
||||
SELECT
|
||||
r.id,
|
||||
r.name,
|
||||
r.slug,
|
||||
'ride' as entity_type,
|
||||
r.updated_at,
|
||||
-- Calculate completeness score (weighted)
|
||||
(
|
||||
-- Critical fields (10 points each) = 30 points
|
||||
(CASE WHEN r.park_id IS NOT NULL THEN 10 ELSE 0 END) +
|
||||
(CASE WHEN r.category IS NOT NULL THEN 10 ELSE 0 END) +
|
||||
(CASE WHEN r.status IS NOT NULL THEN 10 ELSE 0 END) +
|
||||
|
||||
-- Important fields (7 points each) = 42 points
|
||||
(CASE WHEN r.description IS NOT NULL AND length(r.description) > 50 THEN 7 ELSE 0 END) +
|
||||
(CASE WHEN r.manufacturer_id IS NOT NULL THEN 7 ELSE 0 END) +
|
||||
(CASE WHEN r.banner_image_id IS NOT NULL THEN 7 ELSE 0 END) +
|
||||
(CASE WHEN r.card_image_id IS NOT NULL THEN 7 ELSE 0 END) +
|
||||
(CASE WHEN r.ride_model_id IS NOT NULL THEN 7 ELSE 0 END) +
|
||||
(CASE WHEN r.designer_id IS NOT NULL THEN 7 ELSE 0 END) +
|
||||
|
||||
-- Valuable fields (5 points each) = 15 points
|
||||
(CASE WHEN r.opening_date IS NOT NULL THEN 5 ELSE 0 END) +
|
||||
(CASE WHEN r.opening_date_precision IS NOT NULL THEN 5 ELSE 0 END) +
|
||||
(CASE WHEN r.ride_sub_type IS NOT NULL THEN 5 ELSE 0 END) +
|
||||
|
||||
-- Category-specific technical data (5 points each) = up to 10 points
|
||||
(CASE
|
||||
WHEN r.category = 'Roller Coaster' THEN
|
||||
(CASE WHEN r.coaster_type IS NOT NULL THEN 5 ELSE 0 END) +
|
||||
(CASE WHEN r.max_speed_kmh IS NOT NULL THEN 5 ELSE 0 END)
|
||||
WHEN r.category = 'Water Ride' THEN
|
||||
(CASE WHEN r.flume_type IS NOT NULL THEN 5 ELSE 0 END) +
|
||||
(CASE WHEN r.wetness_level IS NOT NULL THEN 5 ELSE 0 END)
|
||||
WHEN r.category = 'Dark Ride' THEN
|
||||
(CASE WHEN r.theme_name IS NOT NULL THEN 5 ELSE 0 END) +
|
||||
(CASE WHEN r.ride_system IS NOT NULL THEN 5 ELSE 0 END)
|
||||
ELSE 0
|
||||
END) +
|
||||
|
||||
-- Supplementary fields (3 points each) = 9 points
|
||||
(CASE WHEN r.max_height_meters IS NOT NULL THEN 3 ELSE 0 END) +
|
||||
(CASE WHEN r.length_meters IS NOT NULL THEN 3 ELSE 0 END) +
|
||||
(CASE WHEN r.capacity_per_hour IS NOT NULL THEN 3 ELSE 0 END)
|
||||
)::NUMERIC / 100.0 * 100 as completeness_score,
|
||||
|
||||
-- Missing fields tracking (using helper function)
|
||||
jsonb_build_object(
|
||||
'critical', filter_jsonb_array_nulls(jsonb_build_array(
|
||||
CASE WHEN r.park_id IS NULL THEN 'park_id' END,
|
||||
CASE WHEN r.category IS NULL THEN 'category' END,
|
||||
CASE WHEN r.status IS NULL THEN 'status' END
|
||||
)),
|
||||
'important', filter_jsonb_array_nulls(jsonb_build_array(
|
||||
CASE WHEN r.description IS NULL OR length(r.description) <= 50 THEN 'description' END,
|
||||
CASE WHEN r.manufacturer_id IS NULL THEN 'manufacturer_id' END,
|
||||
CASE WHEN r.banner_image_id IS NULL THEN 'banner_image' END,
|
||||
CASE WHEN r.card_image_id IS NULL THEN 'card_image' END,
|
||||
CASE WHEN r.ride_model_id IS NULL THEN 'ride_model_id' END,
|
||||
CASE WHEN r.designer_id IS NULL THEN 'designer_id' END
|
||||
)),
|
||||
'valuable', filter_jsonb_array_nulls(jsonb_build_array(
|
||||
CASE WHEN r.opening_date IS NULL THEN 'opening_date' END,
|
||||
CASE WHEN r.opening_date_precision IS NULL THEN 'opening_date_precision' END,
|
||||
CASE WHEN r.ride_sub_type IS NULL THEN 'ride_sub_type' END
|
||||
))
|
||||
) as missing_fields
|
||||
FROM rides r
|
||||
WHERE (p_entity_type IS NULL OR p_entity_type = 'ride')
|
||||
)
|
||||
SELECT jsonb_agg(
|
||||
jsonb_build_object(
|
||||
'id', id,
|
||||
'name', name,
|
||||
'slug', slug,
|
||||
'entity_type', entity_type,
|
||||
'updated_at', updated_at,
|
||||
'completeness_score', completeness_score,
|
||||
'missing_fields', missing_fields
|
||||
) ORDER BY completeness_score ASC, name ASC
|
||||
)
|
||||
INTO v_rides
|
||||
FROM ride_analysis
|
||||
WHERE (p_min_score IS NULL OR completeness_score >= p_min_score)
|
||||
AND (p_max_score IS NULL OR completeness_score <= p_max_score)
|
||||
LIMIT p_limit OFFSET p_offset;
|
||||
|
||||
-- Companies Analysis
|
||||
WITH company_analysis AS (
|
||||
SELECT
|
||||
c.id,
|
||||
c.name,
|
||||
c.slug,
|
||||
'company' as entity_type,
|
||||
c.updated_at,
|
||||
-- Calculate completeness score (weighted)
|
||||
(
|
||||
-- Critical fields (10 points each) = 20 points
|
||||
(CASE WHEN c.company_type IS NOT NULL THEN 10 ELSE 0 END) +
|
||||
(CASE WHEN c.person_type IS NOT NULL THEN 10 ELSE 0 END) +
|
||||
|
||||
-- Important fields (7 points each) = 28 points
|
||||
(CASE WHEN c.description IS NOT NULL AND length(c.description) > 50 THEN 7 ELSE 0 END) +
|
||||
(CASE WHEN c.logo_url IS NOT NULL THEN 7 ELSE 0 END) +
|
||||
(CASE WHEN c.banner_image_id IS NOT NULL THEN 7 ELSE 0 END) +
|
||||
(CASE WHEN c.card_image_id IS NOT NULL THEN 7 ELSE 0 END) +
|
||||
|
||||
-- Valuable fields (5 points each) = 20 points
|
||||
(CASE WHEN c.founded_year IS NOT NULL THEN 5 ELSE 0 END) +
|
||||
(CASE WHEN c.founded_date IS NOT NULL THEN 5 ELSE 0 END) +
|
||||
(CASE WHEN c.website_url IS NOT NULL THEN 5 ELSE 0 END) +
|
||||
(CASE WHEN c.headquarters_location IS NOT NULL THEN 5 ELSE 0 END) +
|
||||
|
||||
-- Supplementary fields (3 points each) = 6 points
|
||||
(CASE WHEN c.founded_date_precision IS NOT NULL THEN 3 ELSE 0 END) +
|
||||
(CASE WHEN c.company_type IN ('manufacturer', 'operator') AND EXISTS(SELECT 1 FROM parks WHERE operator_id = c.id OR property_owner_id = c.id LIMIT 1) THEN 3 ELSE 0 END)
|
||||
)::NUMERIC / 100.0 * 100 as completeness_score,
|
||||
|
||||
-- Missing fields tracking (using helper function)
|
||||
jsonb_build_object(
|
||||
'critical', filter_jsonb_array_nulls(jsonb_build_array(
|
||||
CASE WHEN c.company_type IS NULL THEN 'company_type' END,
|
||||
CASE WHEN c.person_type IS NULL THEN 'person_type' END
|
||||
)),
|
||||
'important', filter_jsonb_array_nulls(jsonb_build_array(
|
||||
CASE WHEN c.description IS NULL OR length(c.description) <= 50 THEN 'description' END,
|
||||
CASE WHEN c.logo_url IS NULL THEN 'logo_url' END,
|
||||
CASE WHEN c.banner_image_id IS NULL THEN 'banner_image' END,
|
||||
CASE WHEN c.card_image_id IS NULL THEN 'card_image' END
|
||||
)),
|
||||
'valuable', filter_jsonb_array_nulls(jsonb_build_array(
|
||||
CASE WHEN c.founded_year IS NULL THEN 'founded_year' END,
|
||||
CASE WHEN c.founded_date IS NULL THEN 'founded_date' END,
|
||||
CASE WHEN c.website_url IS NULL THEN 'website_url' END,
|
||||
CASE WHEN c.headquarters_location IS NULL THEN 'headquarters_location' END
|
||||
))
|
||||
) as missing_fields
|
||||
FROM companies c
|
||||
WHERE (p_entity_type IS NULL OR p_entity_type = 'company')
|
||||
)
|
||||
SELECT jsonb_agg(
|
||||
jsonb_build_object(
|
||||
'id', id,
|
||||
'name', name,
|
||||
'slug', slug,
|
||||
'entity_type', entity_type,
|
||||
'updated_at', updated_at,
|
||||
'completeness_score', completeness_score,
|
||||
'missing_fields', missing_fields
|
||||
) ORDER BY completeness_score ASC, name ASC
|
||||
)
|
||||
INTO v_companies
|
||||
FROM company_analysis
|
||||
WHERE (p_min_score IS NULL OR completeness_score >= p_min_score)
|
||||
AND (p_max_score IS NULL OR completeness_score <= p_max_score)
|
||||
LIMIT p_limit OFFSET p_offset;
|
||||
|
||||
-- Ride Models Analysis - FIXED: Removed introduction_year references (lines 306, 322)
|
||||
-- Total points reduced from 70 to 65 (removed 5 points from introduction_year)
|
||||
WITH model_analysis AS (
|
||||
SELECT
|
||||
rm.id,
|
||||
rm.name,
|
||||
rm.slug,
|
||||
'ride_model' as entity_type,
|
||||
rm.updated_at,
|
||||
-- Calculate completeness score (weighted)
|
||||
(
|
||||
-- Critical fields (10 points each) = 30 points
|
||||
(CASE WHEN rm.manufacturer_id IS NOT NULL THEN 10 ELSE 0 END) +
|
||||
(CASE WHEN rm.category IS NOT NULL THEN 10 ELSE 0 END) +
|
||||
(CASE WHEN rm.ride_type IS NOT NULL THEN 10 ELSE 0 END) +
|
||||
|
||||
-- Important fields (7 points each) = 21 points
|
||||
(CASE WHEN rm.description IS NOT NULL AND length(rm.description) > 50 THEN 7 ELSE 0 END) +
|
||||
(CASE WHEN rm.banner_image_id IS NOT NULL THEN 7 ELSE 0 END) +
|
||||
(CASE WHEN rm.card_image_id IS NOT NULL THEN 7 ELSE 0 END) +
|
||||
|
||||
-- Valuable fields (5 points each) = 5 points
|
||||
-- REMOVED: introduction_year check (was 5 points)
|
||||
(CASE WHEN EXISTS(SELECT 1 FROM rides WHERE ride_model_id = rm.id LIMIT 1) THEN 5 ELSE 0 END)
|
||||
)::NUMERIC / 100.0 * 100 as completeness_score,
|
||||
|
||||
-- Missing fields tracking (using helper function)
|
||||
jsonb_build_object(
|
||||
'critical', filter_jsonb_array_nulls(jsonb_build_array(
|
||||
CASE WHEN rm.manufacturer_id IS NULL THEN 'manufacturer_id' END,
|
||||
CASE WHEN rm.category IS NULL THEN 'category' END,
|
||||
CASE WHEN rm.ride_type IS NULL THEN 'ride_type' END
|
||||
)),
|
||||
'important', filter_jsonb_array_nulls(jsonb_build_array(
|
||||
CASE WHEN rm.description IS NULL OR length(rm.description) <= 50 THEN 'description' END,
|
||||
CASE WHEN rm.banner_image_id IS NULL THEN 'banner_image' END,
|
||||
CASE WHEN rm.card_image_id IS NULL THEN 'card_image' END
|
||||
)),
|
||||
'valuable', filter_jsonb_array_nulls(jsonb_build_array(
|
||||
-- REMOVED: introduction_year from missing fields tracking
|
||||
))
|
||||
) as missing_fields
|
||||
FROM ride_models rm
|
||||
WHERE (p_entity_type IS NULL OR p_entity_type = 'ride_model')
|
||||
)
|
||||
SELECT jsonb_agg(
|
||||
jsonb_build_object(
|
||||
'id', id,
|
||||
'name', name,
|
||||
'slug', slug,
|
||||
'entity_type', entity_type,
|
||||
'updated_at', updated_at,
|
||||
'completeness_score', completeness_score,
|
||||
'missing_fields', missing_fields
|
||||
) ORDER BY completeness_score ASC, name ASC
|
||||
)
|
||||
INTO v_ride_models
|
||||
FROM model_analysis
|
||||
WHERE (p_min_score IS NULL OR completeness_score >= p_min_score)
|
||||
AND (p_max_score IS NULL OR completeness_score <= p_max_score)
|
||||
LIMIT p_limit OFFSET p_offset;
|
||||
|
||||
-- Generate Summary
|
||||
v_summary := jsonb_build_object(
|
||||
'total_entities', (
|
||||
SELECT COUNT(*)::INTEGER FROM (
|
||||
SELECT id FROM parks WHERE (p_entity_type IS NULL OR p_entity_type = 'park')
|
||||
UNION ALL
|
||||
SELECT id FROM rides WHERE (p_entity_type IS NULL OR p_entity_type = 'ride')
|
||||
UNION ALL
|
||||
SELECT id FROM companies WHERE (p_entity_type IS NULL OR p_entity_type = 'company')
|
||||
UNION ALL
|
||||
SELECT id FROM ride_models WHERE (p_entity_type IS NULL OR p_entity_type = 'ride_model')
|
||||
) all_entities
|
||||
),
|
||||
'avg_completeness_score', (
|
||||
SELECT ROUND(AVG(score)::NUMERIC, 2) FROM (
|
||||
SELECT ((10 + 10 + 10)::NUMERIC / 100.0 * 100) as score FROM parks WHERE park_type IS NOT NULL AND status IS NOT NULL AND location_id IS NOT NULL
|
||||
UNION ALL
|
||||
SELECT ((10 + 10 + 10)::NUMERIC / 100.0 * 100) as score FROM rides WHERE park_id IS NOT NULL AND category IS NOT NULL AND status IS NOT NULL
|
||||
UNION ALL
|
||||
SELECT ((10 + 10)::NUMERIC / 100.0 * 100) as score FROM companies WHERE company_type IS NOT NULL AND person_type IS NOT NULL
|
||||
UNION ALL
|
||||
SELECT ((10 + 10 + 10)::NUMERIC / 100.0 * 100) as score FROM ride_models WHERE manufacturer_id IS NOT NULL AND category IS NOT NULL AND ride_type IS NOT NULL
|
||||
) scores
|
||||
),
|
||||
'entities_below_50', (
|
||||
SELECT COUNT(*)::INTEGER FROM (
|
||||
SELECT id FROM parks WHERE (p_entity_type IS NULL OR p_entity_type = 'park')
|
||||
UNION ALL
|
||||
SELECT id FROM rides WHERE (p_entity_type IS NULL OR p_entity_type = 'ride')
|
||||
UNION ALL
|
||||
SELECT id FROM companies WHERE (p_entity_type IS NULL OR p_entity_type = 'company')
|
||||
UNION ALL
|
||||
SELECT id FROM ride_models WHERE (p_entity_type IS NULL OR p_entity_type = 'ride_model')
|
||||
) all_entities
|
||||
WHERE id IN (
|
||||
SELECT id FROM parks WHERE description IS NULL OR manufacturer_id IS NULL
|
||||
UNION
|
||||
SELECT id FROM rides WHERE description IS NULL OR manufacturer_id IS NULL
|
||||
UNION
|
||||
SELECT id FROM companies WHERE description IS NULL
|
||||
UNION
|
||||
SELECT id FROM ride_models WHERE description IS NULL
|
||||
)
|
||||
),
|
||||
'entities_100_complete', 0,
|
||||
'by_entity_type', jsonb_build_object(
|
||||
'parks', (SELECT COUNT(*)::INTEGER FROM parks WHERE (p_entity_type IS NULL OR p_entity_type = 'park')),
|
||||
'rides', (SELECT COUNT(*)::INTEGER FROM rides WHERE (p_entity_type IS NULL OR p_entity_type = 'ride')),
|
||||
'companies', (SELECT COUNT(*)::INTEGER FROM companies WHERE (p_entity_type IS NULL OR p_entity_type = 'company')),
|
||||
'ride_models', (SELECT COUNT(*)::INTEGER FROM ride_models WHERE (p_entity_type IS NULL OR p_entity_type = 'ride_model'))
|
||||
)
|
||||
);
|
||||
|
||||
-- Build final result
|
||||
v_result := jsonb_build_object(
|
||||
'summary', v_summary,
|
||||
'entities', jsonb_build_object(
|
||||
'parks', COALESCE(v_parks, '[]'::jsonb),
|
||||
'rides', COALESCE(v_rides, '[]'::jsonb),
|
||||
'companies', COALESCE(v_companies, '[]'::jsonb),
|
||||
'ride_models', COALESCE(v_ride_models, '[]'::jsonb)
|
||||
),
|
||||
'generated_at', now()
|
||||
);
|
||||
|
||||
RETURN v_result;
|
||||
END;
|
||||
$$;
|
||||
@@ -0,0 +1,129 @@
|
||||
-- Fix get_recent_additions: Remove created_by joins for tables without created_by column
|
||||
-- Only entity_timeline_events has created_by column, not parks/rides/companies/ride_models/locations
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.get_recent_additions(limit_count integer DEFAULT 50)
|
||||
RETURNS TABLE(entity_id uuid, entity_type text, entity_name text, entity_slug text, park_slug text, image_url text, created_at timestamp with time zone, created_by_id uuid, created_by_username text, created_by_avatar text)
|
||||
LANGUAGE plpgsql
|
||||
STABLE SECURITY DEFINER
|
||||
SET search_path TO 'public'
|
||||
AS $function$
|
||||
BEGIN
|
||||
RETURN QUERY
|
||||
SELECT * FROM (
|
||||
-- Parks - FIXED: Removed created_by join (parks table doesn't have created_by column)
|
||||
SELECT
|
||||
p.id as entity_id,
|
||||
'park'::text as entity_type,
|
||||
p.name as entity_name,
|
||||
p.slug as entity_slug,
|
||||
NULL::text as park_slug,
|
||||
p.card_image_url as image_url,
|
||||
p.created_at,
|
||||
NULL::uuid as created_by_id,
|
||||
NULL::text as created_by_username,
|
||||
NULL::text as created_by_avatar
|
||||
FROM parks p
|
||||
|
||||
UNION ALL
|
||||
|
||||
-- Rides - FIXED: Removed created_by join (rides table doesn't have created_by column)
|
||||
SELECT
|
||||
r.id as entity_id,
|
||||
'ride'::text as entity_type,
|
||||
r.name as entity_name,
|
||||
r.slug as entity_slug,
|
||||
pk.slug as park_slug,
|
||||
r.card_image_url as image_url,
|
||||
r.created_at,
|
||||
NULL::uuid as created_by_id,
|
||||
NULL::text as created_by_username,
|
||||
NULL::text as created_by_avatar
|
||||
FROM rides r
|
||||
LEFT JOIN parks pk ON pk.id = r.park_id
|
||||
|
||||
UNION ALL
|
||||
|
||||
-- Companies - FIXED: Removed created_by join (companies table doesn't have created_by column)
|
||||
SELECT
|
||||
c.id as entity_id,
|
||||
'company'::text as entity_type,
|
||||
c.name as entity_name,
|
||||
c.slug as entity_slug,
|
||||
NULL::text as park_slug,
|
||||
c.card_image_url as image_url,
|
||||
c.created_at,
|
||||
NULL::uuid as created_by_id,
|
||||
NULL::text as created_by_username,
|
||||
NULL::text as created_by_avatar
|
||||
FROM companies c
|
||||
|
||||
UNION ALL
|
||||
|
||||
-- Ride Models - FIXED: Removed created_by join (ride_models table doesn't have created_by column)
|
||||
SELECT
|
||||
rm.id as entity_id,
|
||||
'ride_model'::text as entity_type,
|
||||
rm.name as entity_name,
|
||||
rm.slug as entity_slug,
|
||||
NULL::text as park_slug,
|
||||
rm.card_image_url as image_url,
|
||||
rm.created_at,
|
||||
NULL::uuid as created_by_id,
|
||||
NULL::text as created_by_username,
|
||||
NULL::text as created_by_avatar
|
||||
FROM ride_models rm
|
||||
|
||||
UNION ALL
|
||||
|
||||
-- Locations - FIXED: Removed created_by join (locations table doesn't have created_by column)
|
||||
SELECT
|
||||
l.id as entity_id,
|
||||
'location'::text as entity_type,
|
||||
COALESCE(l.city || ', ' || l.country, l.country, 'Location') as entity_name,
|
||||
NULL::text as entity_slug,
|
||||
NULL::text as park_slug,
|
||||
NULL::text as image_url,
|
||||
l.created_at,
|
||||
NULL::uuid as created_by_id,
|
||||
NULL::text as created_by_username,
|
||||
NULL::text as created_by_avatar
|
||||
FROM locations l
|
||||
|
||||
UNION ALL
|
||||
|
||||
-- Timeline Events - KEPT: This table has created_by column
|
||||
SELECT
|
||||
te.id as entity_id,
|
||||
'timeline_event'::text as entity_type,
|
||||
te.event_title as entity_name,
|
||||
NULL::text as entity_slug,
|
||||
NULL::text as park_slug,
|
||||
NULL::text as image_url,
|
||||
te.created_at,
|
||||
te.created_by as created_by_id,
|
||||
prof.username as created_by_username,
|
||||
prof.avatar_url as created_by_avatar
|
||||
FROM entity_timeline_events te
|
||||
LEFT JOIN profiles prof ON prof.user_id = te.created_by
|
||||
|
||||
UNION ALL
|
||||
|
||||
-- Photos - KEPT: This table has submitted_by column
|
||||
SELECT
|
||||
p.id as entity_id,
|
||||
'photo'::text as entity_type,
|
||||
COALESCE(p.title, 'Photo') as entity_name,
|
||||
NULL::text as entity_slug,
|
||||
NULL::text as park_slug,
|
||||
p.cloudflare_image_url as image_url,
|
||||
p.created_at as created_at,
|
||||
p.submitted_by as created_by_id,
|
||||
prof.username as created_by_username,
|
||||
prof.avatar_url as created_by_avatar
|
||||
FROM photos p
|
||||
LEFT JOIN profiles prof ON prof.user_id = p.submitted_by
|
||||
) combined
|
||||
ORDER BY created_at DESC
|
||||
LIMIT limit_count;
|
||||
END;
|
||||
$function$;
|
||||
Reference in New Issue
Block a user