"""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()