-- ───────────────────────────────────────────────────────────────────────────── -- LabCard AI — Supabase Schema -- Run this once in Supabase SQL Editor: -- https://supabase.com → Your project → SQL Editor → New query -- ───────────────────────────────────────────────────────────────────────────── -- ── Extensions ──────────────────────────────────────────────────────────────── create extension if not exists "uuid-ossp"; create extension if not exists vector; -- for future RAG embeddings -- ── Users (extends Supabase auth.users) ────────────────────────────────────── create table if not exists public.profiles ( id uuid primary key references auth.users(id) on delete cascade, name text, age int, gender text, city text, phone text, created_at timestamptz default now(), updated_at timestamptz default now() ); -- ── Reports ─────────────────────────────────────────────────────────────────── create table if not exists public.reports ( id uuid primary key default gen_random_uuid(), user_id uuid references public.profiles(id) on delete cascade, lab_name text, report_date date, health_score int check (health_score between 0 and 100), biological_age int, grade text, summary_en text, summary_hi text, doctor_note text, top_priority text, has_critical boolean default false, raw_text_hash text, -- SHA-256 of raw text — dedup without storing PII created_at timestamptz default now() ); create index if not exists idx_reports_user_date on public.reports (user_id, created_at desc); -- ── Biomarkers ──────────────────────────────────────────────────────────────── create table if not exists public.biomarkers ( id bigserial primary key, report_id uuid references public.reports(id) on delete cascade, name text not null, std_name text, value numeric, unit text, normal_low numeric, normal_high numeric, status text, category text, explanation_en text, explanation_hi text, advice text, foods jsonb default '[]', created_at timestamptz default now() ); create index if not exists idx_biomarkers_report on public.biomarkers (report_id); create index if not exists idx_biomarkers_trend on public.biomarkers (report_id, std_name, created_at); -- ── Reference Ranges ───────────────────────────────────────────────────────── create table if not exists public.reference_ranges ( id bigserial primary key, biomarker text not null, std_name text, gender text default 'A', age_min int default 0, age_max int default 120, normal_low numeric, normal_high numeric, critical_low numeric, critical_high numeric, unit text, category text, notes text, updated_at timestamptz default now(), unique (biomarker, gender) ); -- ── Chat Messages ───────────────────────────────────────────────────────────── create table if not exists public.chat_messages ( id bigserial primary key, report_id uuid references public.reports(id) on delete cascade, role text not null check (role in ('user','assistant')), content text not null, lang text default 'en', created_at timestamptz default now() ); create index if not exists idx_chat_report on public.chat_messages (report_id, created_at); -- ── Medical Knowledge (for future RAG) ─────────────────────────────────────── create table if not exists public.medical_knowledge ( id bigserial primary key, content text not null, embedding vector(1536), metadata jsonb default '{}', source text, created_at timestamptz default now() ); create index if not exists idx_medical_knowledge_embedding on public.medical_knowledge using hnsw (embedding vector_cosine_ops) with (m = 16, ef_construction = 64); -- Similarity search function create or replace function match_medical_knowledge ( query_embedding vector(1536), match_threshold float default 0.75, match_count int default 5 ) returns table (id bigint, content text, metadata jsonb, similarity float) language sql stable as $$ select id, content, metadata, 1 - (embedding <=> query_embedding) as similarity from public.medical_knowledge where 1 - (embedding <=> query_embedding) > match_threshold order by embedding <=> query_embedding limit match_count; $$; -- ── Row Level Security ──────────────────────────────────────────────────────── alter table public.profiles enable row level security; alter table public.reports enable row level security; alter table public.biomarkers enable row level security; alter table public.chat_messages enable row level security; -- Profiles: users can only see/edit their own profile create policy "profiles_own" on public.profiles for all using (auth.uid() = id); -- Reports: users can only see their own reports create policy "reports_own" on public.reports for all using (auth.uid() = user_id); -- Biomarkers: accessible through report ownership create policy "biomarkers_own" on public.biomarkers for all using ( report_id in ( select id from public.reports where user_id = auth.uid() ) ); -- Chat: accessible through report ownership create policy "chat_own" on public.chat_messages for all using ( report_id in ( select id from public.reports where user_id = auth.uid() ) ); -- Reference ranges: public read create policy "ref_ranges_read" on public.reference_ranges for select using (true);