Title tier of relevance_score accepts token containment

Live case: the job titled "Generative Engineer" sourced a pool titled
"Generative AI Engineer" -- the exact phrase never occurs in that title,
so every genuine match fell through to the scattered 35 tier and the
whole pool compressed into the 40s. A current title containing every
job-title token now earns the full 55. The headline tier stays
phrase-only so keyword-stuffed headlines still cap at the 35 tier.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pull/29/head
Talha Ahmed 2026-08-25 19:46:34 +05:00
parent 031261f179
commit cec3be73fb
2 changed files with 35 additions and 7 deletions

View File

@ -418,11 +418,17 @@ def _match_tokens(*texts) -> set[str]:
def relevance_score(job: dict, profile: dict) -> int:
"""0-100 job-fit rank for sorting, computed when a profile is persisted.
Deterministic and free. Title component: the job title as an exact PHRASE
in the person's current title scores 55, in their headline 45; scattered
Deterministic and free. Title component: a current title CONTAINING every
job-title token scores 55 containment, not exact phrase, because job
titles rarely reappear verbatim ("Generative Engineer" vs the pool's
"Generative AI Engineer"; seen live: the phrase rule dropped every real
match to the scattered tier and compressed the whole pool into the 40s).
The job title as an exact phrase in the headline scores 45; scattered
token overlap caps at 35 a keyword-stuffed headline ("AI/ML Engineer |
Python | FastAPI | ...") must not outrank someone whose title IS the job
title, which is exactly what token overlap alone did on live data.
title, which is exactly what token overlap alone did on live data. The
headline tier stays phrase-only for the same reason: stuffed headlines
contain every token of every hot title.
Skills component (up to 45): GRADED token overlap between the content
words of the job's requirements + optional skills and the person's
@ -432,16 +438,20 @@ def relevance_score(job: dict, profile: dict) -> int:
whole live pool on exactly 60.
"""
job_title = _clean_phrase(job.get("title"))
job_title_tokens = set(job_title.split())
title_text = _clean_phrase(profile.get("current_title"))
headline_text = _clean_phrase(profile.get("headline"))
if job_title and job_title in title_text:
if job_title and job_title_tokens <= set(title_text.split()):
title_component = 55.0
elif job_title and job_title in headline_text:
title_component = 45.0
else:
title_tokens = set(job_title.split())
role_tokens = set(title_text.split()) | set(headline_text.split())
ratio = len(title_tokens & role_tokens) / len(title_tokens) if title_tokens else 0.0
ratio = (
len(job_title_tokens & role_tokens) / len(job_title_tokens)
if job_title_tokens
else 0.0
)
title_component = 35 * ratio
job_tokens = _match_tokens(

View File

@ -404,7 +404,25 @@ def test_headline_phrase_scores_below_title_phrase():
scattered = plugins.relevance_score(job, {"current_title": "Engineer", "headline": "Agentic AI | Python"})
assert in_title == 55
assert in_headline == 45
assert scattered == 35 # both tokens present but never as the phrase
assert scattered == 35 # tokens split across title and headline never combine
def test_title_containment_scores_like_an_exact_title():
# Live case: job "Generative Engineer", pool titled "Generative AI
# Engineer" — the exact phrase never occurs, so every genuine match fell
# to the scattered 35 tier and the whole pool compressed into the 40s.
job = {"title": "Generative Engineer", "requirements": [], "optional_skills": []}
interleaved = plugins.relevance_score(job, {"current_title": "Generative AI Engineer"})
senior = plugins.relevance_score(job, {"current_title": "Senior Generative AI Engineer"})
assert interleaved == 55
assert senior == 55
# Containment applies to the TITLE only: the same tokens scattered across
# a keyword-stuffed headline still cap at the 35 tier.
stuffed = plugins.relevance_score(
job,
{"current_title": "Developer", "headline": "Generative AI | Engineer | Python"},
)
assert stuffed == 35
# ---------------------------------------------------------------- detail extraction