Database Schema Documentation
Overview
JobHive uses PostgreSQL as its primary database with a carefully designed schema that supports AI-powered interview analysis, user management, billing, and comprehensive analytics. The database is optimized for performance with strategic indexing and efficient relationships.Database Architecture
Core Design Principles
- Normalization: Follows 3NF with strategic denormalization for performance
- Scalability: Designed to handle millions of interview sessions
- Performance: Strategic indexing and query optimization
- Flexibility: JSON fields for dynamic data while maintaining structure
- Audit Trail: Comprehensive tracking of changes and history
Core Database Models
1. User Management Models
User Model
Copy
CREATE TABLE users_user (
id SERIAL PRIMARY KEY,
username VARCHAR(255) UNIQUE NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
password VARCHAR(255) NOT NULL,
first_name VARCHAR(255),
last_name VARCHAR(255),
is_verified BOOLEAN DEFAULT FALSE,
is_active BOOLEAN DEFAULT TRUE,
is_staff BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
secret_key VARCHAR(32),
enable_2fa BOOLEAN DEFAULT FALSE,
role VARCHAR(20) DEFAULT 'user',
resume VARCHAR(100),
avatar VARCHAR(100),
auth_provider VARCHAR(255) DEFAULT 'email',
social_links JSONB DEFAULT '{}'
);
-- Indexes for performance
CREATE INDEX idx_users_user_email ON users_user(email);
CREATE INDEX idx_users_user_username ON users_user(username);
CREATE INDEX idx_users_user_role ON users_user(role);
CREATE INDEX idx_users_user_created_at ON users_user(created_at);
Resume Model
Copy
CREATE TABLE users_resume (
id SERIAL PRIMARY KEY,
user_id INTEGER REFERENCES users_user(id) ON DELETE CASCADE,
file VARCHAR(100),
uploaded_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
first_name VARCHAR(255),
last_name VARCHAR(255),
email VARCHAR(255),
phone VARCHAR(50),
linkedin VARCHAR(200),
github VARCHAR(200),
skills TEXT,
work_experience TEXT,
education TEXT,
summary TEXT,
projects_worked TEXT,
achievements TEXT
);
-- Indexes
CREATE INDEX idx_users_resume_user_id ON users_resume(user_id);
CREATE INDEX idx_users_resume_uploaded_at ON users_resume(uploaded_at);
OAuth Tokens Model
Copy
CREATE TABLE users_useroauthtoken (
id SERIAL PRIMARY KEY,
user_id INTEGER REFERENCES users_user(id) ON DELETE CASCADE,
provider VARCHAR(50) NOT NULL,
access_token TEXT NOT NULL,
refresh_token TEXT,
expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
scope TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(user_id, provider)
);
-- Indexes
CREATE INDEX idx_users_oauth_user_provider ON users_useroauthtoken(user_id, provider);
CREATE INDEX idx_users_oauth_expires_at ON users_useroauthtoken(expires_at);
2. Company Management Models
Company Model
Copy
CREATE TABLE company_company (
id SERIAL PRIMARY KEY,
user_id INTEGER UNIQUE REFERENCES users_user(id) ON DELETE CASCADE,
company_name VARCHAR(255) NOT NULL,
contact_email VARCHAR(254),
phone_number VARCHAR(20),
address VARCHAR(255),
industry VARCHAR(100),
company_logo VARCHAR(100),
website VARCHAR(200),
description TEXT,
is_recruiting_agency BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
team_member_count INTEGER DEFAULT 0,
-- Interview timing settings
custom_interview_timing BOOLEAN DEFAULT FALSE,
interview_duration_minutes INTEGER DEFAULT 30,
interview_first_warning_seconds INTEGER DEFAULT 300,
interview_final_warning_seconds INTEGER DEFAULT 120,
interview_force_end_seconds INTEGER DEFAULT 10,
allow_interview_extensions BOOLEAN DEFAULT TRUE,
max_interview_extensions INTEGER DEFAULT 2,
interview_extension_minutes INTEGER DEFAULT 10
);
-- Indexes
CREATE INDEX idx_company_company_user_id ON company_company(user_id);
CREATE INDEX idx_company_company_industry ON company_company(industry);
CREATE INDEX idx_company_company_created_at ON company_company(created_at);
Job Model
Copy
CREATE TABLE company_job (
id SERIAL PRIMARY KEY,
company_id INTEGER REFERENCES company_company(id) ON DELETE CASCADE,
title VARCHAR(255) NOT NULL,
description TEXT,
requirements TEXT,
job_focus VARCHAR(50) DEFAULT 'balanced',
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Indexes
CREATE INDEX idx_company_job_company_id ON company_job(company_id);
CREATE INDEX idx_company_job_is_active ON company_job(is_active);
CREATE INDEX idx_company_job_created_at ON company_job(created_at);
CREATE INDEX idx_company_job_job_focus ON company_job(job_focus);
3. Interview System Models
Interview Session Model (Core)
Copy
CREATE TABLE interview_interviewsession (
id SERIAL PRIMARY KEY,
session_id VARCHAR(255) UNIQUE NOT NULL,
user_id INTEGER REFERENCES users_user(id) ON DELETE CASCADE,
job_id INTEGER REFERENCES company_job(id) ON DELETE CASCADE,
-- Timing fields
start_time TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
end_time TIMESTAMP WITH TIME ZONE,
scheduled_time TIMESTAMP WITH TIME ZONE,
completion_time TIMESTAMP WITH TIME ZONE,
duration DOUBLE PRECISION,
max_duration_minutes INTEGER DEFAULT 15,
time_extension_count INTEGER DEFAULT 0,
-- Status and completion
status VARCHAR(20) DEFAULT 'scheduled',
is_active BOOLEAN DEFAULT TRUE,
completion_percentage INTEGER DEFAULT 0,
-- Scoring fields
technical_accuracy DECIMAL(6,2),
behavioral_score DECIMAL(5,2),
-- Settings
is_practice BOOLEAN DEFAULT FALSE,
enable_behavioral_analysis BOOLEAN DEFAULT FALSE,
-- Metadata
interviewer_notes TEXT,
technical_difficulty_reported BOOLEAN DEFAULT FALSE,
reminder_sent BOOLEAN DEFAULT FALSE,
reminder_time INTEGER DEFAULT 30,
calendar_event_id VARCHAR(255),
calendar_link VARCHAR(200),
-- Performance tracking
response_times JSONB DEFAULT '[]',
attention_metrics JSONB DEFAULT '{}',
employer_metrics JSONB DEFAULT '{}',
interviewer_growth_metrics JSONB DEFAULT '{}',
knowledge_gaps JSONB DEFAULT '[]',
suggested_resources JSONB DEFAULT '[]',
warning_sent_at JSONB DEFAULT '[]',
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Critical indexes for performance
CREATE INDEX idx_interview_session_user_status ON interview_interviewsession(user_id, status, start_time);
CREATE INDEX idx_interview_session_job_id ON interview_interviewsession(job_id);
CREATE INDEX idx_interview_session_session_id ON interview_interviewsession(session_id);
CREATE INDEX idx_interview_session_completion ON interview_interviewsession(completion_percentage);
CREATE INDEX idx_interview_session_status_time ON interview_interviewsession(status, start_time);
Sentiment Analysis Models
Copy
CREATE TABLE interview_sentimentsession (
id SERIAL PRIMARY KEY,
session_id VARCHAR(255) UNIQUE NOT NULL,
interview_session_id INTEGER REFERENCES interview_interviewsession(id) ON DELETE CASCADE,
user_id INTEGER REFERENCES users_user(id) ON DELETE CASCADE,
-- Core sentiment scores
positive_score DECIMAL(5,2) DEFAULT 0,
negative_score DECIMAL(5,2) DEFAULT 0,
neutral_score DECIMAL(5,2) DEFAULT 0,
mixed_score DECIMAL(5,2) DEFAULT 0,
overall_sentiment VARCHAR(20),
-- Additional metrics
confidence_level DECIMAL(5,2) DEFAULT 0,
engagement_score DECIMAL(5,2) DEFAULT 0,
professionalism_score DECIMAL(5,2) DEFAULT 0,
clarity_score DECIMAL(5,2) DEFAULT 0,
-- Visibility settings
employer_visible BOOLEAN DEFAULT TRUE,
interviewer_visible BOOLEAN DEFAULT TRUE,
-- Practice session features
is_practice BOOLEAN DEFAULT FALSE,
detailed_feedback JSONB DEFAULT '{}',
improvement_suggestions JSONB DEFAULT '[]',
practice_focus_areas JSONB DEFAULT '[]',
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Indexes
CREATE INDEX idx_sentiment_session_interview ON interview_sentimentsession(interview_session_id, created_at);
CREATE INDEX idx_sentiment_session_user ON interview_sentimentsession(user_id);
CREATE INDEX idx_sentiment_session_overall ON interview_sentimentsession(overall_sentiment);
Sentiment History and Context
Copy
CREATE TABLE interview_sentimenthistory (
id SERIAL PRIMARY KEY,
sentiment_session_id INTEGER REFERENCES interview_sentimentsession(id) ON DELETE CASCADE,
interview_session_id INTEGER REFERENCES interview_interviewsession(id) ON DELETE CASCADE,
positive_score DECIMAL(5,2) DEFAULT 0,
negative_score DECIMAL(5,2) DEFAULT 0,
neutral_score DECIMAL(5,2) DEFAULT 0,
mixed_score DECIMAL(5,2) DEFAULT 0,
overall_sentiment VARCHAR(20),
confidence DECIMAL(5,2) DEFAULT 0,
timestamp TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
detail TEXT
);
CREATE TABLE interview_sentimentcontextfactors (
id SERIAL PRIMARY KEY,
sentiment_history_id INTEGER UNIQUE REFERENCES interview_sentimenthistory(id) ON DELETE CASCADE,
value_alignment_score DECIMAL(5,2) DEFAULT 0,
requirement_addressing_score DECIMAL(5,2) DEFAULT 0,
response_relevance_score DECIMAL(5,2) DEFAULT 0,
confidence_score DECIMAL(5,2) DEFAULT 0,
enthusiasm_score DECIMAL(5,2) DEFAULT 0,
authenticity_score DECIMAL(5,2) DEFAULT 0,
key_themes JSONB DEFAULT '[]',
flags JSONB DEFAULT '[]'
);
-- Indexes
CREATE INDEX idx_sentiment_history_session ON interview_sentimenthistory(interview_session_id, timestamp);
CREATE INDEX idx_sentiment_context_history ON interview_sentimentcontextfactors(sentiment_history_id);
Skill Assessment Models
Copy
CREATE TABLE interview_skillcategory (
id SERIAL PRIMARY KEY,
name VARCHAR(100) UNIQUE NOT NULL
);
CREATE TABLE interview_skill (
id SERIAL PRIMARY KEY,
name VARCHAR(100) UNIQUE NOT NULL,
category_id INTEGER REFERENCES interview_skillcategory(id) ON DELETE SET NULL
);
CREATE TABLE interview_skillassessment (
id SERIAL PRIMARY KEY,
interview_session_id INTEGER REFERENCES interview_interviewsession(id) ON DELETE CASCADE,
sentiment_session_id INTEGER REFERENCES interview_sentimentsession(id) ON DELETE CASCADE,
skill_id INTEGER REFERENCES interview_skill(id) ON DELETE CASCADE,
accumulated_score DECIMAL(10,2) DEFAULT 0,
update_count INTEGER DEFAULT 0,
UNIQUE(interview_session_id, skill_id)
);
CREATE TABLE interview_skillassessmenthistory (
id SERIAL PRIMARY KEY,
skill_assessment_id INTEGER REFERENCES interview_skillassessment(id) ON DELETE CASCADE,
score DECIMAL(5,2) NOT NULL,
timestamp TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
details JSONB
);
-- Indexes
CREATE INDEX idx_skill_assessment_session ON interview_skillassessment(interview_session_id);
CREATE INDEX idx_skill_assessment_skill ON interview_skillassessment(skill_id);
CREATE INDEX idx_skill_assessment_history_timestamp ON interview_skillassessmenthistory(timestamp);
4. Assessment and Scoring Models
Assessment Framework
Copy
CREATE TABLE interview_assessmenttype (
id SERIAL PRIMARY KEY,
name VARCHAR(100) UNIQUE NOT NULL,
description TEXT
);
CREATE TABLE interview_assessmentcategory (
id SERIAL PRIMARY KEY,
assessment_type_id INTEGER REFERENCES interview_assessmenttype(id) ON DELETE CASCADE,
name VARCHAR(100) NOT NULL,
UNIQUE(assessment_type_id, name)
);
CREATE TABLE interview_interviewassessment (
id SERIAL PRIMARY KEY,
interview_session_id INTEGER REFERENCES interview_interviewsession(id) ON DELETE CASCADE,
assessment_type_id INTEGER REFERENCES interview_assessmenttype(id) ON DELETE CASCADE,
category_id INTEGER REFERENCES interview_assessmentcategory(id) ON DELETE SET NULL,
score DECIMAL(5,2) NOT NULL CHECK (score >= 0 AND score <= 100),
details JSONB,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(interview_session_id, assessment_type_id, category_id)
);
-- Indexes
CREATE INDEX idx_interview_assessment_session ON interview_interviewassessment(interview_session_id);
CREATE INDEX idx_interview_assessment_type ON interview_interviewassessment(assessment_type_id);
CREATE INDEX idx_interview_assessment_created ON interview_interviewassessment(created_at);
Cultural Fit and Behavioral Analysis
Copy
CREATE TABLE interview_culturalfit (
id SERIAL PRIMARY KEY,
interview_session_id INTEGER UNIQUE REFERENCES interview_interviewsession(id) ON DELETE CASCADE,
score DECIMAL(5,2) DEFAULT 0,
comments TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE TABLE interview_behavioralanalysis (
id SERIAL PRIMARY KEY,
interview_session_id INTEGER UNIQUE REFERENCES interview_interviewsession(id) ON DELETE CASCADE,
confidence_score DECIMAL(5,2) NOT NULL CHECK (confidence_score >= 0 AND confidence_score <= 100),
fear_score DECIMAL(5,2) NOT NULL CHECK (fear_score >= 0 AND fear_score <= 100),
sincerity_score DECIMAL(5,2) NOT NULL CHECK (sincerity_score >= 0 AND sincerity_score <= 100),
nervousness_score DECIMAL(5,2) NOT NULL CHECK (nervousness_score >= 0 AND nervousness_score <= 100),
excitement_score DECIMAL(5,2) NOT NULL CHECK (excitement_score >= 0 AND excitement_score <= 100),
curiosity_score DECIMAL(5,2) NOT NULL CHECK (curiosity_score >= 0 AND curiosity_score <= 100),
adaptability_score DECIMAL(5,2) NOT NULL CHECK (adaptability_score >= 0 AND adaptability_score <= 100),
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
raw_data JSONB DEFAULT '{}'
);
5. Learning and Development Models
Learning Resources
Copy
CREATE TABLE interview_learningresource (
id SERIAL PRIMARY KEY,
title VARCHAR(255) NOT NULL,
description TEXT NOT NULL,
resource_type VARCHAR(50) NOT NULL,
url VARCHAR(200),
estimated_hours INTEGER DEFAULT 1,
skill_id INTEGER REFERENCES interview_skill(id) ON DELETE CASCADE
);
CREATE TABLE interview_learningpath (
id SERIAL PRIMARY KEY,
interview_session_id INTEGER REFERENCES interview_interviewsession(id) ON DELETE CASCADE,
status VARCHAR(20) DEFAULT 'draft',
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE TABLE interview_learningpathresource (
id SERIAL PRIMARY KEY,
learning_path_id INTEGER REFERENCES interview_learningpath(id) ON DELETE CASCADE,
resource_id INTEGER REFERENCES interview_learningresource(id) ON DELETE CASCADE,
order_position INTEGER NOT NULL,
completed BOOLEAN DEFAULT FALSE,
completion_date TIMESTAMP WITH TIME ZONE,
UNIQUE(learning_path_id, order_position)
);
-- Indexes
CREATE INDEX idx_learning_path_session ON interview_learningpath(interview_session_id);
CREATE INDEX idx_learning_path_resource_path ON interview_learningpathresource(learning_path_id, order_position);
6. Billing and Subscription Models
Subscription Plans
Copy
CREATE TABLE billing_subscriptionplan (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
description TEXT,
plan_type VARCHAR(20) NOT NULL, -- 'candidate' or 'employer'
interval VARCHAR(20) DEFAULT 'month', -- 'month' or 'year'
-- Pricing
price DECIMAL(10,2) NOT NULL,
stripe_price_id VARCHAR(100),
stripe_product_id VARCHAR(100),
-- Feature limits
interview_limit INTEGER DEFAULT 0, -- 0 = unlimited
job_posting_limit INTEGER DEFAULT 0,
application_limit INTEGER DEFAULT 0,
-- Additional features
features JSONB DEFAULT '[]',
sort_order INTEGER DEFAULT 0,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Indexes
CREATE INDEX idx_billing_plan_type ON billing_subscriptionplan(plan_type, is_active);
CREATE INDEX idx_billing_plan_sort ON billing_subscriptionplan(sort_order);
Customer Subscriptions
Copy
CREATE TABLE billing_customersubscription (
id SERIAL PRIMARY KEY,
user_id INTEGER UNIQUE REFERENCES users_user(id) ON DELETE CASCADE,
plan_id INTEGER REFERENCES billing_subscriptionplan(id) ON DELETE PROTECT,
status VARCHAR(20) DEFAULT 'active',
stripe_customer_id VARCHAR(100),
stripe_subscription_id VARCHAR(100),
stripe_subscription_item_id VARCHAR(100) DEFAULT '',
-- Subscription timing
start_date TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
end_date TIMESTAMP WITH TIME ZONE,
trial_end TIMESTAMP WITH TIME ZONE,
current_period_start TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
current_period_end TIMESTAMP WITH TIME ZONE,
cancel_at_period_end BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Indexes
CREATE INDEX idx_billing_subscription_user ON billing_customersubscription(user_id, status);
CREATE INDEX idx_billing_subscription_stripe ON billing_customersubscription(stripe_subscription_id);
CREATE INDEX idx_billing_subscription_status ON billing_customersubscription(status);
Usage Tracking
Copy
CREATE TABLE billing_usagerecord (
id SERIAL PRIMARY KEY,
user_id INTEGER REFERENCES users_user(id) ON DELETE CASCADE,
subscription_id INTEGER REFERENCES billing_customersubscription(id) ON DELETE CASCADE,
usage_type VARCHAR(20) NOT NULL, -- 'interview', 'job_posting', 'application'
quantity INTEGER DEFAULT 1,
resource_id VARCHAR(100),
stripe_usage_record_id VARCHAR(100),
metadata JSONB DEFAULT '{}',
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Indexes
CREATE INDEX idx_usage_record_user_type ON billing_usagerecord(user_id, usage_type, created_at);
CREATE INDEX idx_usage_record_subscription ON billing_usagerecord(subscription_id, created_at);
7. Analytics and Reporting Models
Interview Statistics
Copy
CREATE TABLE interview_interviewstatistics (
id SERIAL PRIMARY KEY,
company_id INTEGER REFERENCES company_company(id) ON DELETE CASCADE,
date DATE NOT NULL,
period VARCHAR(20) DEFAULT 'daily',
-- Basic metrics
total_interviews INTEGER DEFAULT 0,
completed_interviews INTEGER DEFAULT 0,
cancelled_interviews INTEGER DEFAULT 0,
rescheduled_interviews INTEGER DEFAULT 0,
-- Performance metrics
average_duration INTERVAL,
average_score DECIMAL(5,2) DEFAULT 0,
technical_score_avg DECIMAL(5,2) DEFAULT 0,
communication_score_avg DECIMAL(5,2) DEFAULT 0,
-- Quality metrics
no_show_rate DECIMAL(5,2) DEFAULT 0,
conversion_rate DECIMAL(5,2) DEFAULT 0,
time_to_hire INTERVAL,
cost_per_hire DECIMAL(10,2),
offer_acceptance_rate DECIMAL(5,2) DEFAULT 0,
candidate_satisfaction DECIMAL(5,2) DEFAULT 0,
skill_match_rate DECIMAL(5,2) DEFAULT 0,
-- Advanced analytics
diversity_metrics JSONB DEFAULT '{}',
source_effectiveness JSONB DEFAULT '{}',
UNIQUE(company_id, date)
);
-- Indexes
CREATE INDEX idx_interview_stats_company_date ON interview_interviewstatistics(company_id, date);
CREATE INDEX idx_interview_stats_period ON interview_interviewstatistics(period, date);
Candidate Rankings
Copy
CREATE TABLE interview_candidateranking (
id SERIAL PRIMARY KEY,
job_id INTEGER REFERENCES company_job(id) ON DELETE CASCADE,
candidate_id INTEGER REFERENCES users_user(id) ON DELETE CASCADE,
overall_score DECIMAL(5,2) NOT NULL CHECK (overall_score >= 0 AND overall_score <= 100),
technical_score DECIMAL(5,2) NOT NULL CHECK (technical_score >= 0 AND technical_score <= 100),
cultural_fit_score DECIMAL(5,2) NOT NULL CHECK (cultural_fit_score >= 0 AND cultural_fit_score <= 100),
communication_score DECIMAL(5,2) NOT NULL CHECK (communication_score >= 0 AND communication_score <= 100),
ai_confidence DECIMAL(5,2) NOT NULL CHECK (ai_confidence >= 0 AND ai_confidence <= 100),
ranking_date TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(job_id, candidate_id)
);
-- Indexes
CREATE INDEX idx_candidate_ranking_job_score ON interview_candidateranking(job_id, overall_score DESC);
CREATE INDEX idx_candidate_ranking_candidate ON interview_candidateranking(candidate_id);
8. Scoring and Weighting Models
Score Weights
Copy
CREATE TABLE interview_scoreweight (
id SERIAL PRIMARY KEY,
job_focus VARCHAR(50) UNIQUE NOT NULL, -- 'technical', 'behavioral', 'balanced'
technical_weight DECIMAL(3,2) DEFAULT 0.30,
skills_weight DECIMAL(3,2) DEFAULT 0.25,
cultural_fit_weight DECIMAL(3,2) DEFAULT 0.25,
sentiment_weight DECIMAL(3,2) DEFAULT 0.20
);
CREATE TABLE interview_dynamicscoreweight (
id SERIAL PRIMARY KEY,
job_id INTEGER UNIQUE REFERENCES company_job(id) ON DELETE CASCADE,
-- Base weights
base_technical_weight DECIMAL(3,2) NOT NULL,
base_skills_weight DECIMAL(3,2) NOT NULL,
base_cultural_fit_weight DECIMAL(3,2) NOT NULL,
base_sentiment_weight DECIMAL(3,2) NOT NULL,
-- Adjustments
technical_adjustment DECIMAL(3,2) DEFAULT 0.00,
skills_adjustment DECIMAL(3,2) DEFAULT 0.00,
cultural_fit_adjustment DECIMAL(3,2) DEFAULT 0.00,
sentiment_adjustment DECIMAL(3,2) DEFAULT 0.00,
-- Sentiment-specific weights
value_alignment_weight DECIMAL(3,2) DEFAULT 0.40,
confidence_weight DECIMAL(3,2) DEFAULT 0.20,
enthusiasm_weight DECIMAL(3,2) DEFAULT 0.20,
authenticity_weight DECIMAL(3,2) DEFAULT 0.20,
last_updated TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
Database Relationships
Entity Relationship Overview
Performance Optimization
Critical Indexes
Copy
-- Most frequently used queries
CREATE INDEX CONCURRENTLY idx_interview_user_status_time
ON interview_interviewsession(user_id, status, start_time DESC);
CREATE INDEX CONCURRENTLY idx_sentiment_interview_created
ON interview_sentimentsession(interview_session_id, created_at DESC);
CREATE INDEX CONCURRENTLY idx_skill_assessment_session_skill
ON interview_skillassessment(interview_session_id, skill_id);
-- Analytics queries
CREATE INDEX CONCURRENTLY idx_interview_stats_company_period
ON interview_interviewstatistics(company_id, period, date DESC);
-- Billing queries
CREATE INDEX CONCURRENTLY idx_usage_user_type_date
ON billing_usagerecord(user_id, usage_type, created_at DESC);
-- Search and filtering
CREATE INDEX CONCURRENTLY idx_job_company_active
ON company_job(company_id, is_active, created_at DESC);
Partitioning Strategy
Copy
-- Partition large tables by date for better performance
CREATE TABLE interview_sentimenthistory_y2024m01
PARTITION OF interview_sentimenthistory
FOR VALUES FROM ('2024-01-01') TO ('2024-02-01');
CREATE TABLE interview_sentimenthistory_y2024m02
PARTITION OF interview_sentimenthistory
FOR VALUES FROM ('2024-02-01') TO ('2024-03-01');
-- Automatic partition creation
CREATE OR REPLACE FUNCTION create_monthly_partition()
RETURNS void AS $$
DECLARE
start_date date;
end_date date;
table_name text;
BEGIN
start_date := date_trunc('month', CURRENT_DATE + interval '1 month');
end_date := start_date + interval '1 month';
table_name := 'interview_sentimenthistory_y' || to_char(start_date, 'YYYY') || 'm' || to_char(start_date, 'MM');
EXECUTE format('CREATE TABLE %I PARTITION OF interview_sentimenthistory FOR VALUES FROM (%L) TO (%L)',
table_name, start_date, end_date);
END;
$$ LANGUAGE plpgsql;
Data Integrity and Constraints
Business Logic Constraints
Copy
-- Ensure sentiment scores are valid
ALTER TABLE interview_sentimentsession
ADD CONSTRAINT chk_sentiment_scores_valid
CHECK (positive_score + negative_score + neutral_score + mixed_score <= 100);
-- Ensure subscription dates are logical
ALTER TABLE billing_customersubscription
ADD CONSTRAINT chk_subscription_dates
CHECK (end_date IS NULL OR end_date > start_date);
-- Ensure interview durations are reasonable
ALTER TABLE interview_interviewsession
ADD CONSTRAINT chk_interview_duration
CHECK (duration IS NULL OR (duration > 0 AND duration < 14400)); -- Max 4 hours
-- Ensure scores are within valid ranges
ALTER TABLE interview_skillassessment
ADD CONSTRAINT chk_skill_score_range
CHECK (accumulated_score >= 0 AND accumulated_score <= 100);
Foreign Key Relationships
Copy
-- Cascade deletes for dependent data
ALTER TABLE interview_sentimentsession
ADD CONSTRAINT fk_sentiment_interview
FOREIGN KEY (interview_session_id)
REFERENCES interview_interviewsession(id)
ON DELETE CASCADE;
-- Protect critical references
ALTER TABLE billing_customersubscription
ADD CONSTRAINT fk_subscription_plan
FOREIGN KEY (plan_id)
REFERENCES billing_subscriptionplan(id)
ON DELETE RESTRICT;
Database Maintenance
Regular Maintenance Tasks
Copy
-- Analyze table statistics (run weekly)
ANALYZE interview_interviewsession;
ANALYZE interview_sentimentsession;
ANALYZE billing_usagerecord;
-- Vacuum large tables (run monthly)
VACUUM ANALYZE interview_sentimenthistory;
VACUUM ANALYZE billing_usagerecord;
-- Reindex frequently updated tables (run monthly)
REINDEX INDEX idx_interview_user_status_time;
REINDEX INDEX idx_sentiment_interview_created;
Automated Cleanup
Copy
-- Clean up old sentiment history (keep 2 years)
DELETE FROM interview_sentimenthistory
WHERE timestamp < NOW() - INTERVAL '2 years';
-- Archive completed interviews older than 1 year
CREATE TABLE interview_interviewsession_archive AS
SELECT * FROM interview_interviewsession
WHERE status = 'completed' AND start_time < NOW() - INTERVAL '1 year';
-- Clean up expired OAuth tokens
DELETE FROM users_useroauthtoken
WHERE expires_at < NOW() - INTERVAL '1 day';
Backup and Recovery
Backup Strategy
Copy
-- Full database backup (daily)
pg_dump -h localhost -U jobhive_user -d jobhive_production > backup_$(date +%Y%m%d).sql
-- Selective table backups for critical data
pg_dump -h localhost -U jobhive_user -d jobhive_production \
-t interview_interviewsession \
-t interview_sentimentsession \
-t billing_customersubscription > critical_data_backup.sql
-- Point-in-time recovery setup
SELECT pg_start_backup('daily_backup');
-- Copy data files
SELECT pg_stop_backup();
Recovery Procedures
Copy
-- Restore full database
psql -h localhost -U jobhive_user -d jobhive_production < backup_20240215.sql
-- Restore specific tables
psql -h localhost -U jobhive_user -d jobhive_production < critical_data_backup.sql
-- Point-in-time recovery
pg_ctl stop -D /var/lib/postgresql/data
# Restore base backup and WAL files
pg_ctl start -D /var/lib/postgresql/data
Query Optimization Examples
Common Query Patterns
Copy
-- Get user's recent interviews with scores
SELECT
i.id,
i.session_id,
i.start_time,
i.completion_percentage,
i.technical_accuracy,
i.behavioral_score,
j.title as job_title,
c.company_name
FROM interview_interviewsession i
JOIN company_job j ON i.job_id = j.id
JOIN company_company c ON j.company_id = c.id
WHERE i.user_id = $1
AND i.status = 'completed'
ORDER BY i.start_time DESC
LIMIT 10;
-- Get interview analytics for company
SELECT
DATE(i.start_time) as interview_date,
COUNT(*) as total_interviews,
COUNT(CASE WHEN i.status = 'completed' THEN 1 END) as completed,
AVG(i.completion_percentage) as avg_score,
AVG(EXTRACT(EPOCH FROM (i.end_time - i.start_time))/60) as avg_duration_minutes
FROM interview_interviewsession i
JOIN company_job j ON i.job_id = j.id
WHERE j.company_id = $1
AND i.start_time >= $2
AND i.start_time < $3
GROUP BY DATE(i.start_time)
ORDER BY interview_date DESC;
-- Get skill assessment summary for interview
SELECT
s.name as skill_name,
sa.accumulated_score,
sa.update_count,
sc.name as category_name
FROM interview_skillassessment sa
JOIN interview_skill s ON sa.skill_id = s.id
LEFT JOIN interview_skillcategory sc ON s.category_id = sc.id
WHERE sa.interview_session_id = $1
ORDER BY sa.accumulated_score DESC;
