155 lines
4.0 KiB
PL/PgSQL
155 lines
4.0 KiB
PL/PgSQL
-- 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 d’INSERT 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.';
|