Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 162 additions & 0 deletions PathFinder.DatabaseMigrator/Migrations/020_Add_AiAnalytics_Tables.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
-- =============================================
-- Migration: AI Analytics Tables for Job Matching & CV Analysis
-- Author: Your Name
-- Date: 2026-04-16
-- Description: Adds tables for AI-powered features including:
-- - CV analysis results
-- - Job match analytics
-- - Applicant screening
-- - Analytics history snapshots
-- =============================================

BEGIN TRY
BEGIN TRANSACTION;

-- Table 1: CV Analysis Results (stores ATS scoring)
IF OBJECT_ID('dbo.cv_analysis_results', 'U') IS NULL
BEGIN
CREATE TABLE dbo.cv_analysis_results (
id INT IDENTITY(1,1) PRIMARY KEY,
student_id INT NOT NULL,
job_id INT NULL,
ats_score INT NOT NULL,
match_percentage INT NULL,
strengths NVARCHAR(MAX) NULL,
suggestions NVARCHAR(MAX) NULL,
missing_keywords NVARCHAR(MAX) NULL,
present_keywords NVARCHAR(MAX) NULL,
formatting_feedback NVARCHAR(MAX) NULL,
recommendation NVARCHAR(200) NULL,
analysis_type NVARCHAR(50) NOT NULL DEFAULT 'Standalone',
created_at DATETIME2 NOT NULL DEFAULT SYSUTCDATETIME(),

CONSTRAINT FK_cv_analysis_student
FOREIGN KEY (student_id) REFERENCES dbo.students(id) ON DELETE CASCADE,
CONSTRAINT FK_cv_analysis_job
FOREIGN KEY (job_id) REFERENCES dbo.jobs(id) ON DELETE SET NULL,
CONSTRAINT CK_analysis_type
CHECK (analysis_type IN ('Standalone', 'JobSpecific'))
);

CREATE INDEX IX_cv_analysis_student_id ON dbo.cv_analysis_results(student_id);
CREATE INDEX IX_cv_analysis_job_id ON dbo.cv_analysis_results(job_id);
CREATE INDEX IX_cv_analysis_created_at ON dbo.cv_analysis_results(created_at);

PRINT '✅ Created cv_analysis_results table';
END
ELSE
PRINT 'cv_analysis_results table already exists';

-- Table 2: Job Match Analytics (stores student-job matching scores)
IF OBJECT_ID('dbo.job_match_analytics', 'U') IS NULL
BEGIN
CREATE TABLE dbo.job_match_analytics (
id INT IDENTITY(1,1) PRIMARY KEY,
job_id INT NOT NULL,
student_id INT NOT NULL,
match_score INT NOT NULL,
matched_skills NVARCHAR(MAX) NULL,
missing_skills NVARCHAR(MAX) NULL,
partial_matches NVARCHAR(MAX) NULL,
recommendation NVARCHAR(200) NULL,
calculated_at DATETIME2 NOT NULL DEFAULT SYSUTCDATETIME(),

CONSTRAINT FK_match_job
FOREIGN KEY (job_id) REFERENCES dbo.jobs(id) ON DELETE CASCADE,
CONSTRAINT FK_match_student
FOREIGN KEY (student_id) REFERENCES dbo.students(id) ON DELETE CASCADE
);

CREATE INDEX IX_job_match_job_id ON dbo.job_match_analytics(job_id);
CREATE INDEX IX_job_match_student_id ON dbo.job_match_analytics(student_id);
CREATE INDEX IX_job_match_calculated_at ON dbo.job_match_analytics(calculated_at);

PRINT '✅ Created job_match_analytics table';
END
ELSE
PRINT 'job_match_analytics table already exists';

-- Table 3: Applicant Screening (stores AI screening results)
IF OBJECT_ID('dbo.applicant_screening', 'U') IS NULL
BEGIN
CREATE TABLE dbo.applicant_screening (
id INT IDENTITY(1,1) PRIMARY KEY,
application_id INT NOT NULL,
screening_score INT NOT NULL,
screening_recommendation NVARCHAR(100) NULL,
ai_analysis_json NVARCHAR(MAX) NULL,
screened_at DATETIME2 NOT NULL DEFAULT SYSUTCDATETIME(),

CONSTRAINT FK_screening_application
FOREIGN KEY (application_id) REFERENCES dbo.applications(id) ON DELETE CASCADE
);

CREATE INDEX IX_applicant_screening_application_id ON dbo.applicant_screening(application_id);
CREATE INDEX IX_applicant_screening_score ON dbo.applicant_screening(screening_score);

PRINT '✅ Created applicant_screening table';
END
ELSE
PRINT 'applicant_screening table already exists';

-- Table 4: Analytics History (for trends and reporting)
IF OBJECT_ID('dbo.analytics_history', 'U') IS NULL
BEGIN
CREATE TABLE dbo.analytics_history (
id INT IDENTITY(1,1) PRIMARY KEY,
snapshot_date DATE NOT NULL,
total_students INT NOT NULL DEFAULT 0,
active_companies INT NOT NULL DEFAULT 0,
active_jobs INT NOT NULL DEFAULT 0,
total_applications INT NOT NULL DEFAULT 0,
avg_ats_score DECIMAL(5,2) NULL,
avg_match_percentage DECIMAL(5,2) NULL,
application_success_rate DECIMAL(5,2) NULL,
top_skills NVARCHAR(MAX) NULL,
created_at DATETIME2 NOT NULL DEFAULT SYSUTCDATETIME(),

CONSTRAINT UQ_analytics_snapshot_date UNIQUE (snapshot_date)
);

CREATE INDEX IX_analytics_history_snapshot_date ON dbo.analytics_history(snapshot_date);

PRINT '✅ Created analytics_history table';
END
ELSE
PRINT 'analytics_history table already exists';

-- Verify all tables were created
DECLARE @TablesCreated TABLE (TableName NVARCHAR(100));

INSERT INTO @TablesCreated (TableName)
SELECT name FROM sys.objects
WHERE name IN ('cv_analysis_results', 'job_match_analytics', 'applicant_screening', 'analytics_history')
AND type = 'U';

SELECT * FROM @TablesCreated;

COMMIT TRANSACTION;
PRINT '✅ AI Analytics migration completed successfully!';

-- Return summary
SELECT
COUNT(*) as TablesCreated
FROM @TablesCreated;

END TRY
BEGIN CATCH
ROLLBACK TRANSACTION;
PRINT '❌ Error: ' + ERROR_MESSAGE();
PRINT '❌ Error Line: ' + CAST(ERROR_LINE() AS NVARCHAR(10));
PRINT '❌ Error Procedure: ' + ISNULL(ERROR_PROCEDURE(), 'N/A');
THROW;
END CATCH
GO

-- Verify the tables exist after migration
SELECT
TABLE_NAME,
TABLE_TYPE
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME IN ('cv_analysis_results', 'job_match_analytics', 'applicant_screening', 'analytics_history')
ORDER BY TABLE_NAME;
24 changes: 21 additions & 3 deletions Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@

var builder = WebApplication.CreateBuilder(args);

// Add logging
builder.Logging.ClearProviders();
builder.Logging.AddConsole();
builder.Logging.AddDebug();

// Add MVC controllers + Swagger
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
Expand Down Expand Up @@ -144,7 +149,6 @@
// Enable static files to serve uploaded images
app.UseStaticFiles();


// Swagger UI in Development and Production environments
if (app.Environment.IsDevelopment() || app.Environment.IsProduction())
{
Expand All @@ -169,13 +173,14 @@
// Health endpoint for monitoring/testing
app.MapGet("/health", () => Results.Ok(new { status = "Healthy", timestamp = DateTime.Now }));

// Seed default admin if enabled (idempotent)
// Seed default admin if enabled (idempotent) and create AI tables
// Credentials should be provided via environment variables to avoid hardcoding secrets.
using (var scope = app.Services.CreateScope())
{
var config = scope.ServiceProvider.GetRequiredService<IConfiguration>();
var adminRepo = scope.ServiceProvider.GetRequiredService<AdminRepository>();
var pwd = scope.ServiceProvider.GetRequiredService<PasswordService>();
var logger = scope.ServiceProvider.GetRequiredService<ILogger<Program>>();

var seedEnabled = config.GetValue("AdminSeed:Enabled", true);
if (seedEnabled)
Expand All @@ -195,8 +200,21 @@
{
// Hash password before inserting (never store plain password)
await adminRepo.EnsureSeedAdminAsync(seedFullName, seedEmail, pwd.Hash(seedPassword));
app.Logger.LogInformation("Admin seeded successfully");
}
}

// Create AI analytics tables if they don't exist
try
{
var aiRepo = scope.ServiceProvider.GetRequiredService<AiAnalyticsRepository>();
await aiRepo.EnsureAiTablesExistAsync();
app.Logger.LogInformation("AI analytics tables verified/created successfully");
}
catch (Exception ex)
{
app.Logger.LogWarning(ex, "Failed to ensure AI analytics tables exist. AI features may not work properly.");
}
}

app.Run();
app.Run();
Loading
Loading