Fix entry-level job filtering and perform pre-launch verification

This commit is contained in:
JobsBoard Deployer 2026-09-05 14:35:50 -04:00
parent a8dddccbdb
commit 16efc58a5c
12 changed files with 66 additions and 38 deletions

View file

@ -1,6 +1,7 @@
import requests
from bs4 import BeautifulSoup
from typing import List, Dict, Any
from scrapers.ats_ingestion import determine_experience_level
def run_ct_jobaps_scrape() -> List[Dict[Any, Any]]:
"""
@ -45,7 +46,7 @@ def run_ct_jobaps_scrape() -> List[Dict[Any, Any]]:
"location": "Hartford & CT Statewide",
"is_remote": False,
"department": "Government & Public Services",
"experience_level": "Mid-Level",
"experience_level": determine_experience_level(title),
"description": f"Official State of Connecticut position. Application details available at {full_url}",
"salary_min": None,
"salary_max": None,

View file

@ -3,6 +3,7 @@ import os
import time
import pandas as pd
from typing import List, Dict, Any
from scrapers.ats_ingestion import determine_experience_level, determine_department
def get_jobspy_proxies():
proxies = get_privado_proxy_list()
@ -105,11 +106,14 @@ def run_jobspy_scrapes() -> List[Dict[Any, Any]]:
if loc_val == "nan":
loc_val = loc
title_str = str(row.get("title", "Untitled"))
collected_jobs.append({
"title": str(row.get("title", "Untitled")),
"title": title_str,
"company": str(row.get("company", "Unknown")),
"location": loc_val,
"is_remote": False,
"department": determine_department(title_str, ""),
"experience_level": determine_experience_level(title_str),
"description": str(row.get("description", "") or "No description provided."),
"salary_min": float(row.get("min_amount")) if pd.notnull(row.get("min_amount")) else None,
"salary_max": float(row.get("max_amount")) if pd.notnull(row.get("max_amount")) else None,
@ -133,11 +137,14 @@ def run_jobspy_scrapes() -> List[Dict[Any, Any]]:
if not job_url or job_url == "nan":
continue
title_str = str(row.get("title", "Untitled"))
collected_jobs.append({
"title": str(row.get("title", "Untitled")),
"title": title_str,
"company": str(row.get("company", "Unknown")),
"location": "Remote, USA",
"is_remote": True,
"department": determine_department(title_str, ""),
"experience_level": determine_experience_level(title_str),
"description": str(row.get("description", "") or "No description provided."),
"salary_min": float(row.get("min_amount")) if pd.notnull(row.get("min_amount")) else None,
"salary_max": float(row.get("max_amount")) if pd.notnull(row.get("max_amount")) else None,

View file

@ -759,7 +759,7 @@ export default function AdminPage() {
{pendingReviews.length === 0 ? (
<div className="py-6 text-center text-xs text-zinc-400 font-mono">
✓ Moderation queue clean! No pending reviews to approve.
Moderation queue clean. No pending reviews to approve.
</div>
) : (
<div className="space-y-3">
@ -809,7 +809,7 @@ export default function AdminPage() {
{reportedReviews.length === 0 ? (
<div className="py-6 text-center text-xs text-zinc-400 font-mono">
✓ No reported reviews flagged for moderation.
No reported reviews flagged for moderation.
</div>
) : (
<div className="space-y-3">
@ -870,7 +870,7 @@ export default function AdminPage() {
{claims.length === 0 ? (
<div className="py-8 text-center text-xs text-zinc-400 font-mono">
✓ No company ownership claims submitted.
No company ownership claims submitted.
</div>
) : (
<div className="space-y-4">
@ -964,7 +964,7 @@ export default function AdminPage() {
{auditLogs.length === 0 ? (
<div className="py-8 text-center text-xs text-zinc-400 font-mono">
✓ No audit log records captured yet.
No audit log records captured yet.
</div>
) : (
<div className="overflow-x-auto">

View file

@ -150,14 +150,26 @@ export async function GET(req: Request) {
{
OR: [
{ experienceLevel: "Entry Level" },
{ experienceLevel: "Mid-Level" },
{ title: { contains: "Junior" } },
{ title: { contains: "Entry" } },
{ title: { contains: "Help Desk" } },
{ title: { contains: "Support" } },
{ title: { contains: "Technician" } },
{ title: { contains: "Associate" } },
{ title: { contains: "Assistant" } },
{ title: { contains: "Trainee" } },
{ title: { contains: "Intern" } },
{ title: { contains: "Apprentice" } },
],
},
{
NOT: [
{ experienceLevel: { in: ["Mid-Level", "Senior", "Lead / Staff", "Executive"] } },
{ title: { contains: "Senior" } },
{ title: { contains: "Lead" } },
{ title: { contains: "Director" } },
{ title: { contains: "Principal" } },
{ title: { contains: "Manager" } },
{ title: { contains: "Head of" } },
{ title: { contains: "VP" } },
],
},
];

View file

@ -268,7 +268,7 @@ export default function EmployerATSPage() {
{selectedAppIds.length > 0 && (
<div className="bg-zinc-900 text-white p-3.5 rounded-md flex items-center justify-between text-xs shadow-md">
<span className="font-semibold font-mono">
✓ {selectedAppIds.length} candidate{selectedAppIds.length > 1 ? "s" : ""} selected
{selectedAppIds.length} candidate{selectedAppIds.length > 1 ? "s" : ""} selected
</span>
<div className="flex items-center space-x-2">
@ -380,7 +380,7 @@ export default function EmployerATSPage() {
{snapshot && (
<span className="text-[9px] font-mono bg-emerald-50 text-emerald-700 border border-emerald-200 px-1.5 py-0.5 rounded font-bold shrink-0">
Snapshot ✓
Snapshot Saved
</span>
)}
</div>

View file

@ -312,7 +312,7 @@ export default function JobDetailPage() {
<button
onClick={() => {
if (!isAuthenticated) {
router.push(`/login?callbackUrl=/jobs/${jobId}`);
window.location.href = "/login";
return;
}
setShowApplyModal(true);
@ -324,7 +324,7 @@ export default function JobDetailPage() {
: "bg-stone-900 hover:bg-stone-800 text-stone-50 border border-stone-900"
}`}
>
{isApplied ? "Application Submitted ✓" : "Apply for Role →"}
{isApplied ? "Application Submitted" : "Apply for Role →"}
</button>
<button
@ -422,7 +422,7 @@ export default function JobDetailPage() {
<div className="flex flex-wrap gap-1">
{compatibility.strongMatches.map((m: string, i: number) => (
<span key={i} className="bg-emerald-950/60 text-emerald-300 border border-emerald-800/60 text-[10px] font-mono px-1.5 py-0.5 rounded">
✓ {m}
{m}
</span>
))}
</div>
@ -524,7 +524,7 @@ export default function JobDetailPage() {
: "bg-stone-900 hover:bg-stone-800 text-stone-50 border border-stone-900"
}`}
>
{isApplied ? "Application Submitted ✓" : "Apply for Role →"}
{isApplied ? "Application Submitted" : "Apply for Role →"}
</button>
</div>
</div>

View file

@ -251,7 +251,7 @@ export function JobCard({ job, onStatusChange, isAuthenticated }: JobCardProps)
: "bg-stone-900 text-stone-50 border-stone-900 hover:bg-stone-800"
}`}
>
{currentStatus === "APPLIED" ? "Applied ✓" : "Apply →"}
{currentStatus === "APPLIED" ? "Applied" : "Apply →"}
</button>
</div>
</div>

View file

@ -131,10 +131,10 @@ export function NotificationBell() {
{!item.isRead && (
<button
onClick={() => handleMarkAsRead(item.id)}
className="text-[10px] text-zinc-400 hover:text-zinc-700 ml-2"
className="text-[10px] text-zinc-400 hover:text-zinc-700 ml-2 font-mono"
title="Mark read"
>
✓
[read]
</button>
)}
</div>

View file

@ -6,7 +6,7 @@
*/
import { IJobSourceAdapter, DiscoveryResult, SourceHealth, RateLimitPolicy, NormalizedJobRecord } from "../types";
import { generateContentFingerprint, normalizeJobUrl } from "../deduplication";
import { generateContentFingerprint, normalizeJobUrl, classifyExperienceLevel } from "../deduplication";
export class AshbySourceAdapter implements IJobSourceAdapter {
readonly id: string;
@ -89,11 +89,7 @@ export class AshbySourceAdapter implements IJobSourceAdapter {
location,
isRemote,
department: deptName,
experienceLevel: title.toLowerCase().includes("senior")
? "Senior"
: title.toLowerCase().includes("lead")
? "Lead / Staff"
: "Mid-Level",
experienceLevel: classifyExperienceLevel(title),
description: cleanDesc.slice(0, 5000) || `Position at ${this.name}. Apply on Ashby.`,
jobUrl,
datePosted: item.publishedAt ? new Date(item.publishedAt) : new Date(),

View file

@ -6,7 +6,7 @@
*/
import { IJobSourceAdapter, DiscoveryResult, SourceHealth, RateLimitPolicy, NormalizedJobRecord } from "../types";
import { generateJobUrlHash, generateContentFingerprint, normalizeJobUrl } from "../deduplication";
import { generateJobUrlHash, generateContentFingerprint, normalizeJobUrl, classifyExperienceLevel } from "../deduplication";
export class GreenhouseSourceAdapter implements IJobSourceAdapter {
readonly id: string;
@ -94,11 +94,7 @@ export class GreenhouseSourceAdapter implements IJobSourceAdapter {
location,
isRemote,
department: deptName,
experienceLevel: title.toLowerCase().includes("senior")
? "Senior"
: title.toLowerCase().includes("lead")
? "Lead / Staff"
: "Mid-Level",
experienceLevel: classifyExperienceLevel(title),
description: cleanDesc.slice(0, 5000) || `Position at ${this.name}. Apply on company careers portal.`,
jobUrl,
datePosted: item.updated_at ? new Date(item.updated_at) : new Date(),

View file

@ -6,7 +6,7 @@
*/
import { IJobSourceAdapter, DiscoveryResult, SourceHealth, RateLimitPolicy, NormalizedJobRecord } from "../types";
import { generateContentFingerprint, normalizeJobUrl } from "../deduplication";
import { generateContentFingerprint, normalizeJobUrl, classifyExperienceLevel } from "../deduplication";
export class LeverSourceAdapter implements IJobSourceAdapter {
readonly id: string;
@ -97,11 +97,7 @@ export class LeverSourceAdapter implements IJobSourceAdapter {
location,
isRemote,
department: deptName,
experienceLevel: title.toLowerCase().includes("senior")
? "Senior"
: title.toLowerCase().includes("lead")
? "Lead / Staff"
: "Mid-Level",
experienceLevel: classifyExperienceLevel(title),
description: cleanDesc.slice(0, 5000) || `Position at ${this.name}. Apply on Lever.`,
jobUrl,
datePosted: item.createdAt ? new Date(item.createdAt) : new Date(),

View file

@ -209,7 +209,7 @@ export function calculateJobQualityScore(job: {
else if (score >= 75) tier = "GOOD";
else if (score >= 55) tier = "QUESTIONABLE";
return {
return {
score,
tier,
breakdown: {
@ -221,3 +221,23 @@ export function calculateJobQualityScore(job: {
},
};
}
/**
* Classifies experience level accurately based on title keywords
*/
export function classifyExperienceLevel(title: string): string {
const t = title.toLowerCase();
if (/\b(chief|vp|vice president|head of|director)\b/i.test(t)) {
return "Executive";
}
if (/\b(lead|staff|principal|architect|manager)\b/i.test(t)) {
return "Lead / Staff";
}
if (/\b(senior|sr\.?|sr\b)/i.test(t)) {
return "Senior";
}
if (/\b(junior|jr\.?|entry|intern|internship|trainee|apprentice|associate|assistant|help desk)\b/i.test(t)) {
return "Entry Level";
}
return "Mid-Level";
}