From 5fe330f9fb2b3b12b1a54614bfc97d8f03b30e6f Mon Sep 17 00:00:00 2001 From: Gabriel Ramos Date: Wed, 10 Jun 2026 09:27:35 -0400 Subject: [PATCH] chore: add interview audit and cleanup scripts --- scripts/check-interviews.ts | 39 +++++++++++++++++ scripts/clean-interviews.ts | 85 +++++++++++++++++++++++++++++++++++++ scripts/load-env.ts | 17 ++++++++ 3 files changed, 141 insertions(+) create mode 100644 scripts/check-interviews.ts create mode 100644 scripts/clean-interviews.ts create mode 100644 scripts/load-env.ts diff --git a/scripts/check-interviews.ts b/scripts/check-interviews.ts new file mode 100644 index 0000000..fdb0a99 --- /dev/null +++ b/scripts/check-interviews.ts @@ -0,0 +1,39 @@ +import "./load-env"; +import { createServerSupabaseClient } from "@/lib/supabase"; + +async function main() { + const supabase = createServerSupabaseClient(); + const { data: interviews, error } = await supabase + .from("interviews") + .select("*, candidates(name, contact_info), jobs(title, requirements)"); + + if (error) { + console.error("Error fetching interviews:", error.message); + return; + } + + console.log("Found interviews:", interviews?.length); + for (const i of interviews || []) { + const candidateId = i.candidate_id; + const jobId = i.job_id; + + // Fetch latest score + const { data: scores } = await supabase + .from("scores") + .select("*") + .eq("candidate_id", candidateId) + .eq("job_id", jobId) + .order("created_at", { ascending: false }) + .limit(1); + + const score = scores?.[0]; + console.log(`Interview ID: ${i.id}`); + console.log(` Candidate: ${i.candidates?.name}`); + console.log(` Job: ${i.jobs?.title}`); + console.log(` AI Score: ${score ? score.ai_score : "No Score"}`); + console.log(` AI Classification: ${score ? score.evaluation.classification : "No Score"}`); + console.log("---------------------------------------"); + } +} + +main().catch(console.error); diff --git a/scripts/clean-interviews.ts b/scripts/clean-interviews.ts new file mode 100644 index 0000000..708cc18 --- /dev/null +++ b/scripts/clean-interviews.ts @@ -0,0 +1,85 @@ +import "./load-env"; +import { createServerSupabaseClient } from "@/lib/supabase"; + +const skillsMatch = (candSkill: string, jobSkill: string): boolean => { + const c = candSkill.toLowerCase().trim(); + const j = jobSkill.toLowerCase().trim(); + if (c === j) return true; + if (c.includes(j) || j.includes(c)) return true; + + const cWords = c.split(/[\s,./()&+-]+/).filter(w => w.length > 2); + const jWords = j.split(/[\s,./()&+-]+/).filter(w => w.length > 2); + + const stopWords = ['and', 'for', 'with', 'the', 'management', 'administration', 'development', 'developer', 'engineer', 'system', 'systems', 'integration', 'operations', 'knowledge', 'experience', 'expert', 'proficiency', 'proficient']; + + const sharedWords = cWords.filter(w => jWords.includes(w) && !stopWords.includes(w)); + return sharedWords.length > 0; +}; + +async function main() { + const supabase = createServerSupabaseClient(); + + // 1. Fetch all interviews + const { data: interviews, error: interviewsError } = await supabase + .from("interviews") + .select("*, candidates(*), jobs(*)"); + + if (interviewsError) { + console.error("Error fetching interviews:", interviewsError.message); + return; + } + + console.log(`Analyzing ${interviews?.length} interviews...`); + + for (const i of interviews || []) { + const candidate = i.candidates; + const job = i.jobs; + if (!candidate || !job) continue; + + // Check skills overlap + const jobSkills: string[] = job.requirements?.skills || []; + const candidateSkills: string[] = candidate.contact_info?.skills || []; + const matchedSkills = jobSkills.filter((js) => + candidateSkills.some((cs) => skillsMatch(cs, js)) + ); + + const overlapCount = matchedSkills.length; + const totalRequired = jobSkills.length; + const matchPct = totalRequired > 0 ? Math.round((overlapCount / totalRequired) * 100) : 0; + const isPotentialMatch = matchPct >= 75; + + // Fetch score + const { data: scores } = await supabase + .from("scores") + .select("*") + .eq("candidate_id", candidate.id) + .eq("job_id", job.id) + .order("created_at", { ascending: false }) + .limit(1); + + const score = scores?.[0]; + const isUnqualified = score?.evaluation?.classification === "Unqualified"; + + if (!isPotentialMatch || isUnqualified) { + console.log(`Deleting interview ID ${i.id}:`); + console.log(` Candidate: ${candidate.name}`); + console.log(` Job: ${job.title}`); + console.log(` Reason: ${!isPotentialMatch ? `Skill mismatch (${matchPct}% overlap)` : 'Deemed Unqualified by AI'}`); + + const { error: deleteError } = await supabase + .from("interviews") + .delete() + .eq("id", i.id); + + if (deleteError) { + console.error(` Failed to delete: ${deleteError.message}`); + } else { + console.log(" Successfully deleted."); + } + } else { + console.log(`Keeping interview ID ${i.id} for ${candidate.name} - ${job.title}`); + } + } +} + +main().catch(console.error); diff --git a/scripts/load-env.ts b/scripts/load-env.ts new file mode 100644 index 0000000..ac93a3b --- /dev/null +++ b/scripts/load-env.ts @@ -0,0 +1,17 @@ +import * as fs from "fs"; +import * as path from "path"; + +const envPath = path.resolve(process.cwd(), ".env"); +if (fs.existsSync(envPath)) { + const envContent = fs.readFileSync(envPath, "utf8"); + for (const line of envContent.split("\n")) { + const match = line.match(/^\s*([\w.-]+)\s*=\s*(.*)\s*$/); + if (match) { + const key = match[1]; + let value = match[2].trim(); + if (value.startsWith('"') && value.endsWith('"')) value = value.slice(1, -1); + else if (value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1); + process.env[key] = value; + } + } +}