site-web-targo/supabase/migrations/20260212002212_3e43ab2e-3340-4ba5-923c-5caf1f84b2aa.sql
louispaulb 88dc3714a1 Initial deploy: gigafibre.ca website with self-hosted address search
React/Vite/shadcn-ui site for Gigafibre ISP.
Address qualification via PostgreSQL (5.2M AQ addresses, pg_trgm fuzzy search).
No Supabase dependency for address search.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 14:37:50 -04:00

116 lines
3.9 KiB
PL/PgSQL

CREATE OR REPLACE FUNCTION public.search_addresses(search_term text, result_limit integer DEFAULT 10)
RETURNS TABLE(identifiant_unique_adresse text, adresse_formatee text, numero_municipal text, numero_unite text, code_postal text, odonyme_recompose_normal text, nom_municipalite text, longitude double precision, latitude double precision, fiber_available boolean, zone_tarifaire integer, max_speed integer, similarity_score real)
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path TO 'public'
AS $function$
DECLARE
clean_term text;
num_part text;
street_part text;
BEGIN
clean_term := trim(search_term);
-- Extract leading number and street part
num_part := (regexp_match(clean_term, '^\s*(\d+)'))[1];
street_part := trim(regexp_replace(clean_term, '^\s*\d+\s*', ''));
-- Phase 1: If we have a number, filter by numero_municipal prefix first (very fast with btree)
-- Then rank by street similarity if street_part exists
IF num_part IS NOT NULL AND length(num_part) >= 1 THEN
RETURN QUERY
SELECT
a.identifiant_unique_adresse,
a.adresse_formatee,
a.numero_municipal,
a.numero_unite,
a.code_postal,
a.odonyme_recompose_normal,
a.nom_municipalite,
a.longitude,
a.latitude,
(f.id IS NOT NULL) AS fiber_available,
f.zone_tarifaire,
f.max_speed,
CASE
WHEN street_part = '' THEN
CASE WHEN a.numero_municipal = num_part THEN 1.0 ELSE 0.5 END::real
ELSE
similarity(COALESCE(a.odonyme_recompose_normal, ''), street_part)
END AS similarity_score
FROM public.addresses a
LEFT JOIN public.fiber_availability f ON f.uuidadresse = a.identifiant_unique_adresse
WHERE a.numero_municipal = num_part
AND (street_part = '' OR COALESCE(a.odonyme_recompose_normal, '') % street_part)
ORDER BY similarity_score DESC
LIMIT result_limit;
-- If we got results, return them
IF FOUND THEN
RETURN;
END IF;
-- Fallback: prefix match on numero_municipal (e.g. user typed "193")
RETURN QUERY
SELECT
a.identifiant_unique_adresse,
a.adresse_formatee,
a.numero_municipal,
a.numero_unite,
a.code_postal,
a.odonyme_recompose_normal,
a.nom_municipalite,
a.longitude,
a.latitude,
(f.id IS NOT NULL) AS fiber_available,
f.zone_tarifaire,
f.max_speed,
CASE
WHEN street_part = '' THEN 0.5::real
ELSE similarity(COALESCE(a.odonyme_recompose_normal, ''), street_part)
END AS similarity_score
FROM public.addresses a
LEFT JOIN public.fiber_availability f ON f.uuidadresse = a.identifiant_unique_adresse
WHERE a.numero_municipal LIKE (num_part || '%')
AND (street_part = '' OR COALESCE(a.odonyme_recompose_normal, '') % street_part)
ORDER BY similarity_score DESC, a.numero_municipal
LIMIT result_limit;
IF FOUND THEN
RETURN;
END IF;
END IF;
-- Phase 2: No number found or no results — full trigram search on street/address
RETURN QUERY
SELECT
a.identifiant_unique_adresse,
a.adresse_formatee,
a.numero_municipal,
a.numero_unite,
a.code_postal,
a.odonyme_recompose_normal,
a.nom_municipalite,
a.longitude,
a.latitude,
(f.id IS NOT NULL) AS fiber_available,
f.zone_tarifaire,
f.max_speed,
GREATEST(
similarity(COALESCE(a.odonyme_recompose_normal, ''), clean_term),
similarity(COALESCE(a.adresse_formatee, ''), clean_term)
) AS similarity_score
FROM public.addresses a
LEFT JOIN public.fiber_availability f ON f.uuidadresse = a.identifiant_unique_adresse
WHERE COALESCE(a.odonyme_recompose_normal, '') % clean_term
OR a.adresse_formatee % clean_term
ORDER BY similarity_score DESC
LIMIT result_limit;
END;
$function$;
-- Btree index on numero_municipal for fast exact/prefix matching
CREATE INDEX IF NOT EXISTS idx_addresses_numero_municipal
ON public.addresses (numero_municipal);