feat: CV Application - AI-powered CV management, parsing, matching, and generation
This commit is contained in:
235
database.py
Normal file
235
database.py
Normal file
@@ -0,0 +1,235 @@
|
||||
"""Database schema initialization and connection management."""
|
||||
import psycopg2
|
||||
from psycopg2.extras import RealDictCursor, Json
|
||||
from contextlib import contextmanager
|
||||
from config import get_settings
|
||||
|
||||
settings = get_settings()
|
||||
|
||||
SCHEMA_SQL = """
|
||||
-- Enable extensions
|
||||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||||
|
||||
-- Candidates table (person info)
|
||||
CREATE TABLE IF NOT EXISTS candidates (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
first_name VARCHAR(255),
|
||||
last_name VARCHAR(255),
|
||||
email VARCHAR(255),
|
||||
phone VARCHAR(255),
|
||||
address TEXT,
|
||||
linkedin VARCHAR(500),
|
||||
github VARCHAR(500),
|
||||
website VARCHAR(500),
|
||||
summary TEXT,
|
||||
-- Full raw text of original CV (extracted from document)
|
||||
raw_cv_text TEXT,
|
||||
-- Original filename
|
||||
source_filename VARCHAR(500),
|
||||
-- Metadata
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
-- AI parse status: pending, parsed, error
|
||||
parse_status VARCHAR(50) DEFAULT 'pending',
|
||||
parse_error TEXT
|
||||
);
|
||||
|
||||
-- Skills table (normalized)
|
||||
CREATE TABLE IF NOT EXISTS skills (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
candidate_id UUID REFERENCES candidates(id) ON DELETE CASCADE,
|
||||
skill_name VARCHAR(255) NOT NULL,
|
||||
skill_category VARCHAR(100), -- e.g. "Programming", "Database", "Cloud", "Tools"
|
||||
proficiency VARCHAR(50), -- e.g. "Expert", "Advanced", "Intermediate", "Beginner"
|
||||
-- When did they start using this skill (for dynamic experience calculation)
|
||||
start_date DATE, -- can be just a year or full date
|
||||
-- When did they stop (NULL = still using it = "present")
|
||||
end_date DATE, -- NULL means still active
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Experience table (work history)
|
||||
CREATE TABLE IF NOT EXISTS experience (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
candidate_id UUID REFERENCES candidates(id) ON DELETE CASCADE,
|
||||
company VARCHAR(255) NOT NULL,
|
||||
position VARCHAR(255) NOT NULL,
|
||||
location VARCHAR(255),
|
||||
start_date DATE,
|
||||
end_date DATE, -- NULL = current/present
|
||||
description TEXT,
|
||||
-- JSON array of achievements/bullet points
|
||||
achievements JSONB DEFAULT '[]',
|
||||
-- JSON array of skills used in this role
|
||||
skills_used JSONB DEFAULT '[]',
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Education table
|
||||
CREATE TABLE IF NOT EXISTS education (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
candidate_id UUID REFERENCES candidates(id) ON DELETE CASCADE,
|
||||
institution VARCHAR(255) NOT NULL,
|
||||
degree VARCHAR(255),
|
||||
field_of_study VARCHAR(255),
|
||||
start_date DATE,
|
||||
end_date DATE,
|
||||
grade VARCHAR(100),
|
||||
description TEXT,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Certifications table
|
||||
CREATE TABLE IF NOT EXISTS certifications (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
candidate_id UUID REFERENCES candidates(id) ON DELETE CASCADE,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
issuer VARCHAR(255),
|
||||
issue_date DATE,
|
||||
expiry_date DATE,
|
||||
credential_id VARCHAR(255),
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- CV Templates table
|
||||
CREATE TABLE IF NOT EXISTS cv_templates (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
name VARCHAR(255) NOT NULL,
|
||||
description TEXT,
|
||||
-- JSON structure defining the template layout
|
||||
-- sections: [{name, type, order, fields: [...], styling: {...}}]
|
||||
template_structure JSONB NOT NULL,
|
||||
-- CSS/styling for the template
|
||||
styling TEXT,
|
||||
-- Whether AI generated this or user created manually
|
||||
created_by VARCHAR(50) DEFAULT 'manual', -- 'ai' or 'manual'
|
||||
-- The prompt that was used to generate it (if AI)
|
||||
generation_prompt TEXT,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Requirement requests (customer sends a list of positions to fill)
|
||||
CREATE TABLE IF NOT EXISTS requirement_requests (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
title VARCHAR(255) NOT NULL,
|
||||
description TEXT,
|
||||
customer_name VARCHAR(255),
|
||||
-- JSON array of position requirements
|
||||
-- [{position_title, quantity, skills: [{name, min_years, required/optional}],
|
||||
-- experience_years, education, certifications, description}]
|
||||
requirements JSONB NOT NULL DEFAULT '[]',
|
||||
status VARCHAR(50) DEFAULT 'active', -- active, completed, archived
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Generated CVs (CVs regenerated to align with specific requirements)
|
||||
CREATE TABLE IF NOT EXISTS generated_cvs (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
candidate_id UUID REFERENCES candidates(id) ON DELETE CASCADE,
|
||||
requirement_request_id UUID REFERENCES requirement_requests(id) ON DELETE CASCADE,
|
||||
template_id UUID REFERENCES cv_templates(id) ON DELETE SET NULL,
|
||||
-- Which specific position in the requirements this CV targets
|
||||
position_title VARCHAR(255),
|
||||
-- The generated CV content (formatted text/HTML)
|
||||
generated_content TEXT NOT NULL,
|
||||
-- JSON snapshot of the data used to generate this CV (for editing)
|
||||
generated_data JSONB NOT NULL DEFAULT '{}',
|
||||
-- The date this CV was generated (used for dynamic experience calculation)
|
||||
generation_date DATE NOT NULL DEFAULT CURRENT_DATE,
|
||||
-- Match score (0-100) how well the candidate matches the requirement
|
||||
match_score INTEGER DEFAULT 0,
|
||||
-- AI reasoning for the match
|
||||
match_reasoning TEXT,
|
||||
-- Status: draft, reviewed, approved, rejected
|
||||
status VARCHAR(50) DEFAULT 'draft',
|
||||
-- User can edit the generated content
|
||||
edited_content TEXT,
|
||||
edited_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Chat conversations for AI interaction
|
||||
CREATE TABLE IF NOT EXISTS chat_conversations (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
-- Context: what is the user working on when they started this chat
|
||||
context_type VARCHAR(100), -- e.g. "requirement_match", "template_create", "general"
|
||||
context_ref_id UUID, -- reference to the related entity
|
||||
title VARCHAR(255),
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS chat_messages (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
conversation_id UUID REFERENCES chat_conversations(id) ON DELETE CASCADE,
|
||||
role VARCHAR(50) NOT NULL, -- 'user' or 'assistant'
|
||||
content TEXT NOT NULL,
|
||||
-- Optional: metadata about what the AI did (tools used, etc.)
|
||||
metadata JSONB DEFAULT '{}',
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Indexes
|
||||
CREATE INDEX IF NOT EXISTS idx_skills_candidate ON skills(candidate_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_skills_name ON skills(skill_name);
|
||||
CREATE INDEX IF NOT EXISTS idx_experience_candidate ON experience(candidate_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_education_candidate ON education(candidate_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_certifications_candidate ON certifications(candidate_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_generated_cvs_candidate ON generated_cvs(candidate_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_generated_cvs_requirement ON generated_cvs(requirement_request_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_chat_messages_conversation ON chat_messages(conversation_id);
|
||||
"""
|
||||
|
||||
|
||||
@contextmanager
|
||||
def get_db():
|
||||
"""Get a database connection."""
|
||||
conn = psycopg2.connect(settings.database_url)
|
||||
try:
|
||||
yield conn
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def init_db():
|
||||
"""Initialize database schema."""
|
||||
with get_db() as conn:
|
||||
cur = conn.cursor()
|
||||
cur.execute(SCHEMA_SQL)
|
||||
conn.commit()
|
||||
print("Database schema initialized successfully.")
|
||||
|
||||
|
||||
def query(sql, params=None, fetch='all'):
|
||||
"""Execute a query and return results."""
|
||||
with get_db() as conn:
|
||||
cur = conn.cursor(cursor_factory=RealDictCursor)
|
||||
cur.execute(sql, params or ())
|
||||
if fetch == 'all':
|
||||
return cur.fetchall()
|
||||
elif fetch == 'one':
|
||||
return cur.fetchone()
|
||||
elif fetch == 'none':
|
||||
conn.commit()
|
||||
return None
|
||||
else:
|
||||
return cur.fetchall()
|
||||
|
||||
|
||||
def execute(sql, params=None):
|
||||
"""Execute a query that modifies data, return the row if RETURNING is used."""
|
||||
with get_db() as conn:
|
||||
cur = conn.cursor(cursor_factory=RealDictCursor)
|
||||
cur.execute(sql, params or ())
|
||||
conn.commit()
|
||||
try:
|
||||
return cur.fetchone()
|
||||
except psycopg2.ProgrammingError:
|
||||
return None
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
init_db()
|
||||
Reference in New Issue
Block a user