From 18734726cb6b694173435aace26fe0db34cd0abb Mon Sep 17 00:00:00 2001 From: Gabriel Ramos Date: Mon, 8 Jun 2026 11:27:02 -0400 Subject: [PATCH] db: create initial schema migration with pgvector --- supabase/migrations/00001_initial_schema.sql | 85 ++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 supabase/migrations/00001_initial_schema.sql diff --git a/supabase/migrations/00001_initial_schema.sql b/supabase/migrations/00001_initial_schema.sql new file mode 100644 index 0000000..f8b0006 --- /dev/null +++ b/supabase/migrations/00001_initial_schema.sql @@ -0,0 +1,85 @@ +-- Enable the pgvector extension to work with embeddings +CREATE EXTENSION IF NOT EXISTS vector; + +-- Users Table (Recruiters / Managers) +CREATE TABLE IF NOT EXISTS users ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + email TEXT UNIQUE NOT NULL, + role TEXT NOT NULL CHECK (role IN ('recruiter', 'manager')), + created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()) NOT NULL +); + +-- Jobs Table +CREATE TABLE IF NOT EXISTS jobs ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + title TEXT NOT NULL, + description TEXT NOT NULL, + requirements TEXT, + embedding vector(1536), + created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()) NOT NULL +); + +-- Candidates Table +CREATE TABLE IF NOT EXISTS candidates ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + name TEXT NOT NULL, + email TEXT UNIQUE NOT NULL, + phone TEXT, + resume_url TEXT, + summary TEXT, + seniority TEXT, + embedding vector(1536), + created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()) NOT NULL +); + +-- Interviews Table +CREATE TABLE IF NOT EXISTS interviews ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + candidate_id UUID REFERENCES candidates(id) ON DELETE CASCADE NOT NULL, + job_id UUID REFERENCES jobs(id) ON DELETE CASCADE NOT NULL, + scheduled_at TIMESTAMP WITH TIME ZONE NOT NULL, + status TEXT NOT NULL CHECK (status IN ('scheduled', 'completed', 'cancelled')) DEFAULT 'scheduled', + notes TEXT, + created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()) NOT NULL +); + +-- Scores Table +CREATE TABLE IF NOT EXISTS scores ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + candidate_id UUID REFERENCES candidates(id) ON DELETE CASCADE NOT NULL, + job_id UUID REFERENCES jobs(id) ON DELETE CASCADE NOT NULL, + interviewer_id UUID REFERENCES users(id) ON DELETE CASCADE, + score NUMERIC NOT NULL CHECK (score >= 0 AND score <= 100), + evaluation JSONB, -- MUST contain exactly: summary, classification, suggestions, riskLevel + created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()) NOT NULL +); + +-- RPC Function to Match Candidates using Cosine Distance +CREATE OR REPLACE FUNCTION match_candidates ( + query_embedding vector(1536), + match_threshold FLOAT, + match_count INT +) +RETURNS TABLE ( + id UUID, + name TEXT, + email TEXT, + seniority TEXT, + similarity FLOAT +) +LANGUAGE plpgsql +AS $$ +BEGIN + RETURN QUERY + SELECT + candidates.id, + candidates.name, + candidates.email, + candidates.seniority, + (1 - (candidates.embedding <=> query_embedding))::FLOAT AS similarity + FROM candidates + WHERE (1 - (candidates.embedding <=> query_embedding)) > match_threshold + ORDER BY candidates.embedding <=> query_embedding ASC + LIMIT match_count; +END; +$$;