Compare commits

..

No commits in common. "411421f2b6498ca37d960bd593b218878b6d8a0f" and "6cd0e0ba0f80edbc0586f7c1344dc6ce69843e02" have entirely different histories.

13 changed files with 5 additions and 270 deletions

View file

@ -1,3 +0,0 @@
NEXT_PUBLIC_SUPABASE_URL=your_supabase_project_url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key
NEXT_PUBLIC_N8N_WEBHOOK_URL=your_n8n_webhook_url

42
.gitignore vendored
View file

@ -1,42 +0,0 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# env files (can opt-in for committing if needed)
.env*
!.env.example
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts

View file

@ -1,14 +1,7 @@
# AI Recruitment Platform (ATS)
# Semillero 2 - AI ATS
## Project Description
A modern ATS platform designed to parse PDF CVs using multimodal AI, rank candidates against job vacancies using semantic vector search, and orchestrate automated recruitment stages via an external n8n hub.
This repository contains the AI ATS project.
## Core User Stories
- **Recruiter - CV Upload:** As a recruiter, I want to upload CVs in PDF format so they can be evaluated automatically.
- **Recruiter - Vacancy Ranking:** As a recruiter, I want a ranking of candidates per job vacancy.
- **Recruiter - Seniority Detection:** As a recruiter, I want the system to detect seniority to adjust interviews.
- **Recruiter - Profile Summary:** As a recruiter, I want a concise AI summary of the profile for quick review.
- **Hiring Manager - Comparative Scoring:** As a hiring manager, I want a comparative score between candidates.
- **Recruiter - Stage Automation:** As a recruiter, I want candidates to move through recruitment stages automatically.
- **Candidate - Automated Emails:** As a candidate, I want to receive automated email confirmations for every stage change.
- **Talent Team - Vacancy Metrics:** As a talent team, we want metrics on the progress per vacancy.
## Remote Sync Details
- **Fetch:** Private Git server (`git.gaboggamer.online`)
- **Push:** Private Git server and GitHub (`github.com`)

View file

@ -1,8 +0,0 @@
export default function CandidatesPage() {
return (
<div className="bg-white p-6 rounded-lg shadow-sm border border-slate-200">
<h1 className="text-xl font-bold text-slate-900 mb-2">Candidates</h1>
<p className="text-slate-600 text-sm">View and evaluate parsed candidate profiles.</p>
</div>
);
}

View file

@ -1,8 +0,0 @@
export default function InterviewsPage() {
return (
<div className="bg-white p-6 rounded-lg shadow-sm border border-slate-200">
<h1 className="text-xl font-bold text-slate-900 mb-2">Interviews</h1>
<p className="text-slate-600 text-sm">Schedule and monitor candidate evaluations.</p>
</div>
);
}

View file

@ -1,8 +0,0 @@
export default function JobsPage() {
return (
<div className="bg-white p-6 rounded-lg shadow-sm border border-slate-200">
<h1 className="text-xl font-bold text-slate-900 mb-2">Jobs</h1>
<p className="text-slate-600 text-sm">Manage job vacancies and candidate matches.</p>
</div>
);
}

View file

@ -1,36 +0,0 @@
import Link from "next/link";
export default function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="min-h-screen bg-slate-50 flex flex-col">
<header className="bg-white border-b border-slate-200 shadow-sm">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 h-16 flex items-center justify-between">
<div className="flex items-center gap-8">
<span className="text-slate-900 font-bold text-lg">AI Recruitment</span>
<nav className="flex gap-4">
<Link href="/jobs" className="text-sm text-slate-600 hover:text-slate-900 font-medium">
Jobs
</Link>
<Link href="/candidates" className="text-sm text-slate-600 hover:text-slate-900 font-medium">
Candidates
</Link>
<Link href="/interviews" className="text-sm text-slate-600 hover:text-slate-900 font-medium">
Interviews
</Link>
<Link href="/workflows" className="text-sm text-slate-600 hover:text-slate-900 font-medium">
Workflows
</Link>
</nav>
</div>
</div>
</header>
<main className="flex-1 max-w-7xl mx-auto w-full p-6 sm:p-8">
{children}
</main>
</div>
);
}

View file

@ -1,8 +0,0 @@
export default function WorkflowsPage() {
return (
<div className="bg-white p-6 rounded-lg shadow-sm border border-slate-200">
<h1 className="text-xl font-bold text-slate-900 mb-2">Workflows</h1>
<p className="text-slate-600 text-sm">Configure automated recruitment pipelines orchestrated by n8n.</p>
</div>
);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

View file

@ -1,5 +0,0 @@
@import "tailwindcss";
body {
@apply bg-slate-50 text-slate-600 antialiased;
}

View file

@ -1,33 +0,0 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html
lang="en"
className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}
>
<body className="min-h-full flex flex-col">{children}</body>
</html>
);
}

View file

@ -1,22 +0,0 @@
import Link from "next/link";
export default function Home() {
return (
<main className="flex min-h-screen flex-col items-center justify-center bg-slate-50 p-6">
<div className="w-full max-w-md bg-white p-8 rounded-lg shadow-sm border border-slate-200 text-center">
<h1 className="text-2xl font-bold text-slate-900 mb-2">
AI Recruitment Platform
</h1>
<p className="text-slate-600 mb-6 text-sm">
Welcome to the ATS. Access your workspace below.
</p>
<Link
href="/jobs"
className="inline-block w-full bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded-md shadow-sm transition-colors text-sm"
>
Go to Dashboard
</Link>
</div>
</main>
);
}

View file

@ -1,85 +0,0 @@
-- 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;
$$;