This commit is contained in:
Bastien COIGNOUX
2026-05-03 20:18:33 +02:00
parent ffc2e6b895
commit bd325fe456
113 changed files with 29532 additions and 220 deletions

View File

@ -0,0 +1,154 @@
-- 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.';