create schema if not exists extensions; create extension if not exists vector with schema extensions; create table if not exists public.falsa_info_chunks ( id text primary key, chunk_text text not null, source text, embedding extensions.vector(1024) not null, embedding_model text not null, created_at timestamptz not null default now(), updated_at timestamptz not null default now() ); create table if not exists public.driver_trip_embeddings ( trip_id uuid primary key references public.driver_trips(id) on delete cascade, chunk_text text not null, embedding extensions.vector(1024) not null, embedding_model text not null, created_at timestamptz not null default now(), updated_at timestamptz not null default now() ); create index if not exists idx_falsa_info_chunks_embedding on public.falsa_info_chunks using hnsw (embedding vector_cosine_ops); create index if not exists idx_driver_trip_embeddings_embedding on public.driver_trip_embeddings using hnsw (embedding vector_cosine_ops); drop trigger if exists set_falsa_info_chunks_updated_at on public.falsa_info_chunks; create trigger set_falsa_info_chunks_updated_at before update on public.falsa_info_chunks for each row execute function public.set_updated_at(); drop trigger if exists set_driver_trip_embeddings_updated_at on public.driver_trip_embeddings; create trigger set_driver_trip_embeddings_updated_at before update on public.driver_trip_embeddings for each row execute function public.set_updated_at(); create or replace function public.departure_bucket_clock_time(bucket text) returns time language sql immutable as $$ select case bucket when 'morning' then time '06:00' when 'noon' then time '12:00' when 'night' then time '18:00' else null end; $$; create or replace function public.match_falsa_info( query_embedding extensions.vector(1024), match_threshold float default 0.0, match_count int default 5 ) returns table ( id text, chunk_text text, source text, similarity float ) language sql stable as $$ select falsa_info_chunks.id, falsa_info_chunks.chunk_text, falsa_info_chunks.source, 1 - (falsa_info_chunks.embedding <=> query_embedding) as similarity from public.falsa_info_chunks where 1 - (falsa_info_chunks.embedding <=> query_embedding) >= match_threshold order by falsa_info_chunks.embedding <=> query_embedding limit match_count; $$; create or replace function public.match_active_trips( query_embedding extensions.vector(1024), match_threshold float default 0.0, match_count int default 10, filter_departure text default null, filter_destination text default null, filter_departure_date date default null, filter_departure_time text default null, filter_requested_time time default null, filter_seats int default 1, filter_vehicle_type text default null ) returns table ( trip_id uuid, departure text, destination text, departure_date date, departure_time text, available_seats integer, total_seats integer, price numeric, status text, driver_name text, driver_phone_number text, car_type text, chunk_text text, similarity float, time_difference_minutes integer ) language sql stable as $$ with ranked as ( select driver_trips.id as trip_id, driver_trips.departure, driver_trips.destination, driver_trips.departure_date, driver_trips.departure_time, driver_trips.available_seats, driver_trips.total_seats, driver_trips.price, driver_trips.status, customers.name as driver_name, customers."remoteJid" as driver_phone_number, driver_cars.car_type, driver_trip_embeddings.chunk_text, 1 - (driver_trip_embeddings.embedding <=> query_embedding) as similarity, driver_trip_embeddings.embedding <=> query_embedding as vector_distance, case when filter_requested_time is null then null else abs( extract( epoch from ( public.departure_bucket_clock_time(driver_trips.departure_time) - filter_requested_time ) ) / 60 )::integer end as time_difference_minutes from public.driver_trip_embeddings join public.driver_trips on driver_trips.id = driver_trip_embeddings.trip_id left join public.drivers on drivers.id = driver_trips.driver_id left join public.customers on customers.id = drivers.customer_id left join public.driver_cars on driver_cars.id = driver_trips.car_id where driver_trips.status = 'active' and driver_trips.available_seats >= coalesce(filter_seats, 1) and ( filter_departure is null or driver_trips.departure ilike '%' || filter_departure || '%' ) and ( filter_destination is null or driver_trips.destination ilike '%' || filter_destination || '%' ) and ( filter_departure_date is null or driver_trips.departure_date = filter_departure_date ) and ( filter_departure_time is null or driver_trips.departure_time = filter_departure_time ) and ( filter_vehicle_type is null or driver_cars.car_type ilike '%' || filter_vehicle_type || '%' ) and ( driver_trips.departure_date > (now() at time zone 'Asia/Aden')::date or ( driver_trips.departure_date = (now() at time zone 'Asia/Aden')::date and ( (now() at time zone 'Asia/Aden')::time < time '12:00' or ( (now() at time zone 'Asia/Aden')::time < time '18:00' and driver_trips.departure_time in ('noon', 'night') ) or ( (now() at time zone 'Asia/Aden')::time >= time '18:00' and driver_trips.departure_time = 'night' ) ) ) ) ) select ranked.trip_id, ranked.departure, ranked.destination, ranked.departure_date, ranked.departure_time, ranked.available_seats, ranked.total_seats, ranked.price, ranked.status, ranked.driver_name, ranked.driver_phone_number, ranked.car_type, ranked.chunk_text, ranked.similarity, ranked.time_difference_minutes from ranked where ranked.similarity >= match_threshold order by ranked.time_difference_minutes nulls last, ranked.departure_date, ranked.vector_distance limit match_count; $$; alter table public.falsa_info_chunks enable row level security; alter table public.driver_trip_embeddings enable row level security; grant all on public.falsa_info_chunks to service_role; grant all on public.driver_trip_embeddings to service_role; grant usage on schema extensions to service_role; grant all on all routines in schema public to service_role;