artificial-intelligence

I Used AI to Build a Job-Matching System That Actually Thinks Like a Recruiter

Most people use AI for job hunting in the most basic way possible.

They paste a CV. They paste a job description. They ask, “Am I fit for this?” The AI replies with a polite motivational speech.

Nice? Yes. Useful? Sometimes. Recruiter-level? Not really.

A real job-matching system should not just say:

You are a good fit.

It should explain:

Which skills match Which skills are missing Which keywords matter How strong the match is What the candidate should improve Which jobs should be applied to first

That is where AI becomes useful.

So I started thinking: what if I build an AI-powered job matcher that works like a mini recruiter?

Not a motivational chatbot. A proper system.

1. The real problem with job searching

Job searching is not just about finding jobs.

It is about matching.

A candidate may apply to 100 jobs and still get no response because the CV and job description do not connect clearly.

For example, if a job requires:

Customer communication MS Excel Basic computer skills Sales calling CRM usage

And the CV says:

Good communication Computer knowledge Teamwork

The candidate may be relevant, but the CV is not speaking the employer’s language.

That is exactly where AI can help.

"""
AI Job Matching WorkflowCandidate CV
    -> Extract skills
    -> Extract experience
    -> Extract education
    -> Extract job preferences
Job Description
    -> Extract required skills
    -> Extract responsibilities
    -> Extract location
    -> Extract experience level
Matching Engine
    -> Compare candidate with job
    -> Score the match
    -> Identify missing skills
    -> Suggest CV improvements
    -> Rank jobs
"""

The goal is simple:

Do not apply randomly. Apply strategically.

2. Start with clean data models

Before using AI, I like creating structure.

Because if the data is messy, the AI output will also be messy.

from dataclasses import dataclass, field
from typing import List, Optional
@dataclass
class CandidateProfile:
    name: str
    education: str
    skills: List[str]
    experience: List[str]
    preferred_roles: List[str]
    preferred_location: str
    strengths: List[str]

@dataclass
class JobPost:
    title: str
    company: str
    location: str
    required_skills: List[str]
    responsibilities: List[str]
    experience_level: str
    description: str

@dataclass
class JobMatchResult:
    job_title: str
    company: str
    match_score: float
    matched_skills: List[str]
    missing_skills: List[str]
    reason: str
    recommendation: str

This structure matters.

A good job system should not just return paragraphs. It should return useful fields.

Because later we can sort, filter, rank, and compare.

That is when AI becomes a system instead of just a chat reply.

3. Extract useful skills from a CV

The first step is skill extraction.

For a simple version, we can use keyword matching. In a more advanced version, we can use embeddings or an LLM.

class SkillExtractor:
    def __init__(self):
        self.skill_keywords = [
            "communication",
            "sales",
            "customer service",
            "excel",
            "ms office",
            "data entry",
            "python",
            "sql",
            "power bi",
            "teamwork",
            "problem solving",
            "computer skills",
            "call handling",
            "crm",
            "email writing",
            "internet research"
        ]
def extract_skills(self, cv_text: str) -> List[str]:
        cv_text = cv_text.lower()
        found_skills = []
        for skill in self.skill_keywords:
            if skill in cv_text:
                found_skills.append(skill)
        return found_skills

cv_text = """
I have basic communication skills, basic computer knowledge,
MS Office understanding, internet research experience, and interest
in customer service and call center roles.
"""
extractor = SkillExtractor()
skills = extractor.extract_skills(cv_text)
print(skills)

This is simple but useful.

In real projects, I would improve it by detecting similar phrases.

For example:

Customer dealing = customer service Calling experience = call handling Excel sheets = MS Excel Online searching = internet research

That is where AI helps more than basic keyword matching.

4. Extract requirements from job descriptions

Now we need to understand the job post.

class JobRequirementExtractor:
    def __init__(self):
        self.common_requirements = [
            "communication",
            "customer service",
            "sales",
            "call handling",
            "english speaking",
            "computer skills",
            "ms office",
            "excel",
            "crm",
            "data entry",
            "problem solving",
            "teamwork"
        ]
def extract_requirements(self, job_description: str) -> List[str]:
        job_description = job_description.lower()
        requirements = []
        for requirement in self.common_requirements:
            if requirement in job_description:
                requirements.append(requirement)
        return requirements

job_description = """
We are hiring a Customer Sales Representative for our Lahore office.
The candidate should have good communication skills, basic computer skills,
customer service attitude, and ability to handle calls professionally.
Fresh candidates can apply.
"""
job_extractor = JobRequirementExtractor()
requirements = job_extractor.extract_requirements(job_description)
print(requirements)

Now we have both sides:

Candidate skills Job requirements

Next step: compare them.

5. Build the matching engine

The matching engine should not only calculate a score. It should explain the score.

That explanation is important because job seekers need direction.

class JobMatchingEngine:
    def calculate_match(
        self,
        candidate: CandidateProfile,
        job: JobPost
    ) -> JobMatchResult:
candidate_skills = set(skill.lower() for skill in candidate.skills)
        required_skills = set(skill.lower() for skill in job.required_skills)
        matched_skills = list(candidate_skills.intersection(required_skills))
        missing_skills = list(required_skills.difference(candidate_skills))
        if not required_skills:
            match_score = 0
        else:
            match_score = (len(matched_skills) / len(required_skills)) * 100
        reason = self.create_reason(candidate, job, matched_skills, missing_skills)
        recommendation = self.create_recommendation(match_score, missing_skills)
        return JobMatchResult(
            job_title=job.title,
            company=job.company,
            match_score=round(match_score, 2),
            matched_skills=matched_skills,
            missing_skills=missing_skills,
            reason=reason,
            recommendation=recommendation
        )
    def create_reason(
        self,
        candidate: CandidateProfile,
        job: JobPost,
        matched_skills: List[str],
        missing_skills: List[str]
    ) -> str:
        return (
            f"{candidate.name} matches this {job.title} role because the profile "
            f"contains relevant skills such as {', '.join(matched_skills) if matched_skills else 'no direct listed skills'}. "
            f"The missing areas are {', '.join(missing_skills) if missing_skills else 'none'}."
        )
    def create_recommendation(
        self,
        match_score: float,
        missing_skills: List[str]
    ) -> str:
        if match_score >= 75:
            return "Strong match. Apply with a tailored CV."
        if match_score >= 50:
            return (
                "Moderate match. Apply, but improve the CV by adding missing keywords: "
                + ", ".join(missing_skills)
            )
        return (
            "Weak match. Improve relevant skills first or apply only if the role accepts fresh candidates."
        )

This is already better than a normal AI answer.

Because now the system is not just saying “yes” or “no.”

It is showing why.

And in job searching, “why” is everything.

6. Test it with a fresher profile

Let’s test it with a fresher candidate looking for call center or customer support jobs.

candidate = CandidateProfile(
    name="Waqar",
    education="BS Business Analytics, completed 2 semesters",
    skills=[
        "communication",
        "computer skills",
        "ms office",
        "internet research",
        "teamwork"
    ],
    experience=[
        "fresh candidate",
        "academic projects",
        "basic computer work"
    ],
    preferred_roles=[
        "call center representative",
        "customer sales representative",
        "data entry assistant"
    ],
    preferred_location="Lahore",
    strengths=[
        "quick learner",
        "basic communication",
        "basic computer knowledge",
        "willing to learn"
    ]
)
job = JobPost(
    title="Customer Sales Representative",
    company="ABC Solutions",
    location="Lahore",
    required_skills=[
        "communication",
        "customer service",
        "sales",
        "computer skills",
        "call handling"
    ],
    responsibilities=[
        "handle customer calls",
        "explain services",
        "maintain customer records",
        "achieve daily calling targets"
    ],
    experience_level="Fresh candidates can apply",
    description="Call center role for fresh candidates with communication and computer skills."
)
engine = JobMatchingEngine()
result = engine.calculate_match(candidate, job)
print("Job:", result.job_title)
print("Company:", result.company)
print("Score:", result.match_score)
print("Matched:", result.matched_skills)
print("Missing:", result.missing_skills)
print("Reason:", result.reason)
print("Recommendation:", result.recommendation)

This gives a clear result.

If the candidate matches 2 out of 5 required skills, the score becomes 40%.

But that does not mean the candidate should immediately reject the job.

Because for fresh call center jobs, attitude, availability, communication, and basic computer skills often matter a lot.

So the system should also understand job level.

7. Add fresher-friendly scoring

A fresher should not be judged the same way as a senior candidate.

If the job says:

Fresh candidates can apply

Then the scoring should be softer.

class FresherFriendlyJobMatchingEngine(JobMatchingEngine):
    def calculate_match(
        self,
        candidate: CandidateProfile,
        job: JobPost
    ) -> JobMatchResult:
base_result = super().calculate_match(candidate, job)
        fresher_keywords = [
            "fresh",
            "fresh candidates",
            "no experience",
            "entry level",
            "training provided"
        ]
        description = job.description.lower()
        is_fresher_friendly = any(keyword in description for keyword in fresher_keywords)
        adjusted_score = base_result.match_score
        if is_fresher_friendly:
            adjusted_score += 15
        if job.location.lower() == candidate.preferred_location.lower():
            adjusted_score += 10
        adjusted_score = min(adjusted_score, 100)
        if adjusted_score >= 70:
            recommendation = "Good opportunity. Apply with a fresher-focused CV and simple confident cover message."
        elif adjusted_score >= 50:
            recommendation = "Possible opportunity. Apply if training is provided and improve missing skills."
        else:
            recommendation = "Low match. Better to improve missing skills before applying."
        return JobMatchResult(
            job_title=base_result.job_title,
            company=base_result.company,
            match_score=round(adjusted_score, 2),
            matched_skills=base_result.matched_skills,
            missing_skills=base_result.missing_skills,
            reason=base_result.reason,
            recommendation=recommendation
        )

Now the system thinks more like a human recruiter.

A fresh candidate does not need to be perfect. They need to be trainable, relevant, and available.

That is a big difference.

8. Rank multiple jobs automatically

Now we can compare multiple jobs and rank them.

jobs = [
    JobPost(
        title="Call Center Agent",
        company="Bright Call Services",
        location="Lahore",
        required_skills=["communication", "call handling", "computer skills"],
        responsibilities=["handle inbound calls", "record customer information"],
        experience_level="Fresh",
        description="Fresh candidates can apply. Training provided."
    ),
    JobPost(
        title="Data Analyst Intern",
        company="DataTech",
        location="Lahore",
        required_skills=["python", "sql", "excel", "power bi"],
        responsibilities=["clean data", "make dashboards", "prepare reports"],
        experience_level="Internship",
        description="Internship for students with Python, SQL, Excel and Power BI knowledge."
    ),
    JobPost(
        title="Customer Sales Representative",
        company="SalesPro",
        location="Lahore",
        required_skills=["communication", "sales", "customer service", "computer skills"],
        responsibilities=["make calls", "explain packages", "close sales"],
        experience_level="Fresh",
        description="Entry level sales role. Fresh candidates can apply."
    )
]
fresher_engine = FresherFriendlyJobMatchingEngine()
ranked_results = []
for job_item in jobs:
    match = fresher_engine.calculate_match(candidate, job_item)
    ranked_results.append(match)
ranked_results.sort(key=lambda item: item.match_score, reverse=True)
for index, match in enumerate(ranked_results, start=1):
    print(f"\nRank {index}")
    print("Job:", match.job_title)
    print("Company:", match.company)
    print("Score:", match.match_score)
    print("Matched Skills:", match.matched_skills)
    print("Missing Skills:", match.missing_skills)
    print("Recommendation:", match.recommendation)

This is where the tool becomes practical.

Instead of applying randomly, the candidate can apply in order:

Best match first Possible match second Weak match later

That saves time and energy.

And job searching already takes enough energy. No need to make it a full-time emotional workout.

9. Final thoughts

AI can make job searching much smarter, but only if we use it properly.

The goal is not to ask AI:

Am I good for this job?

The better question is:

Which parts of my profile match this job, which parts are missing, and how should I improve before applying?

That is how AI becomes useful.

A strong AI job-matching system should:

Read the CV Read the job post Extract skills Compare requirements Score the match Explain the reason Suggest improvements Rank jobs by fit

This is especially helpful for fresh candidates because they often do not know which jobs are realistic for them.

For example, a fresher with basic communication and computer skills may be a better fit for:

Call center agent Customer sales representative Data entry assistant Reception or front desk assistant Junior support role

And after improving Excel, SQL, Python, or Power BI, they can move closer to:

Business analyst intern Data analyst intern Reporting assistant Operations analyst trainee

That is the real power of AI in job searching.

Not just writing a fancy CV.

But helping people make better career decisions.