Files
mdb/supabase/migrations/20260429200000_deals_sources_scout.sql
Bastien COIGNOUX bd325fe456 init
2026-05-03 20:18:33 +02:00

155 lines
4.0 KiB
PL/PgSQL
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- Flux opportunités aspirées + agent Scout (batch JSON côté Postgres)
create table if not exists public.deals_sources (
id uuid primary key default gen_random_uuid(),
user_id uuid not null references auth.users (id) on delete cascade,
title text not null,
description text,
source_url text,
source_name text,
price_eur numeric(14, 2),
surface_m2 numeric(12, 2) not null,
price_per_m2_eur numeric(14, 2) not null,
dvf_avg_m2_simulated numeric(14, 2) not null default 3500,
distress_keywords text[] not null default '{}',
opportunity_score numeric(6, 2) not null,
grade text not null check (grade in ('A', 'B', 'C')),
raw_payload jsonb,
created_at timestamptz not null default now()
);
create index if not exists deals_sources_user_score_idx
on public.deals_sources (user_id, opportunity_score desc);
create index if not exists deals_sources_user_created_idx
on public.deals_sources (user_id, created_at desc);
alter table public.profiles add column if not exists expo_push_token text;
-- Activer Realtime sur cette table : Dashboard Supabase → Realtime → ajouter public.deals_sources
alter table public.deals_sources enable row level security;
create policy deals_sources_select_own on public.deals_sources
for select to authenticated
using (auth.uid() = user_id);
-- Pas dINSERT direct : uniquement via la fonction SECURITY DEFINER ci-dessous.
create or replace function public.scout_process_batch(p_listings jsonb)
returns jsonb
language plpgsql
security definer
set search_path = public
as $$
declare
uid uuid := auth.uid();
el jsonb;
txt text;
price numeric;
surf numeric;
pm2 numeric;
avg_m2 constant numeric := 3500;
keywords text[] := array['succession', 'urgent', 'travaux important'];
k text;
matched text[];
ok_kw boolean;
ok_pm2 boolean;
score numeric;
grade text;
n int := 0;
na int := 0;
begin
if uid is null then
raise exception 'scout_process_batch: non authentifié';
end if;
for el in select * from jsonb_array_elements(coalesce(p_listings, '[]'::jsonb))
loop
txt := lower(
coalesce(el ->> 'description', '') || ' ' || coalesce(el ->> 'title', '')
);
price := nullif(trim(el ->> 'price_eur'), '')::numeric;
surf := nullif(trim(el ->> 'surface_m2'), '')::numeric;
if price is null or surf is null or surf <= 0 then
continue;
end if;
pm2 := price / surf;
matched := array[]::text[];
ok_kw := false;
foreach k in array keywords
loop
if strpos(txt, k) > 0 then
ok_kw := true;
matched := array_append(matched, k);
end if;
end loop;
ok_pm2 := pm2 < avg_m2;
if not (ok_kw and ok_pm2) then
continue;
end if;
score := 40::numeric
+ greatest(0::numeric, (avg_m2 - pm2) / nullif(avg_m2, 0) * 50)
+ coalesce(array_length(matched, 1), 0) * 10;
grade := case
when score >= 80 then 'A'
when score >= 55 then 'B'
else 'C'
end;
insert into public.deals_sources (
user_id,
title,
description,
source_url,
source_name,
price_eur,
surface_m2,
price_per_m2_eur,
dvf_avg_m2_simulated,
distress_keywords,
opportunity_score,
grade,
raw_payload
)
values (
uid,
coalesce(nullif(trim(el ->> 'title'), ''), 'Sans titre'),
el ->> 'description',
nullif(trim(el ->> 'url'), ''),
nullif(trim(el ->> 'source'), ''),
price,
surf,
pm2,
avg_m2,
matched,
score,
grade,
el
);
n := n + 1;
if grade = 'A' then
na := na + 1;
end if;
end loop;
return jsonb_build_object(
'inserted_count', n,
'grade_a_count', na,
'simulated_dvf_avg_m2', avg_m2
);
end;
$$;
grant execute on function public.scout_process_batch(jsonb) to authenticated;
comment on function public.scout_process_batch(jsonb) is
'Filtre un lot JSON d''annonces (mots-clés détresse + prix/m² < moyenne simulée) et insère dans deals_sources.';