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
parent
031261f179
commit
cec3be73fb
|
|
@ -418,11 +418,17 @@ def _match_tokens(*texts) -> set[str]:
|
||||||
def relevance_score(job: dict, profile: dict) -> int:
|
def relevance_score(job: dict, profile: dict) -> int:
|
||||||
"""0-100 job-fit rank for sorting, computed when a profile is persisted.
|
"""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
|
Deterministic and free. Title component: a current title CONTAINING every
|
||||||
in the person's current title scores 55, in their headline 45; scattered
|
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 |
|
token overlap caps at 35 — a keyword-stuffed headline ("AI/ML Engineer |
|
||||||
Python | FastAPI | ...") must not outrank someone whose title IS the job
|
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
|
Skills component (up to 45): GRADED token overlap between the content
|
||||||
words of the job's requirements + optional skills and the person's
|
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.
|
whole live pool on exactly 60.
|
||||||
"""
|
"""
|
||||||
job_title = _clean_phrase(job.get("title"))
|
job_title = _clean_phrase(job.get("title"))
|
||||||
|
job_title_tokens = set(job_title.split())
|
||||||
title_text = _clean_phrase(profile.get("current_title"))
|
title_text = _clean_phrase(profile.get("current_title"))
|
||||||
headline_text = _clean_phrase(profile.get("headline"))
|
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
|
title_component = 55.0
|
||||||
elif job_title and job_title in headline_text:
|
elif job_title and job_title in headline_text:
|
||||||
title_component = 45.0
|
title_component = 45.0
|
||||||
else:
|
else:
|
||||||
title_tokens = set(job_title.split())
|
|
||||||
role_tokens = set(title_text.split()) | set(headline_text.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
|
title_component = 35 * ratio
|
||||||
|
|
||||||
job_tokens = _match_tokens(
|
job_tokens = _match_tokens(
|
||||||
|
|
|
||||||
|
|
@ -404,7 +404,25 @@ def test_headline_phrase_scores_below_title_phrase():
|
||||||
scattered = plugins.relevance_score(job, {"current_title": "Engineer", "headline": "Agentic AI | Python"})
|
scattered = plugins.relevance_score(job, {"current_title": "Engineer", "headline": "Agentic AI | Python"})
|
||||||
assert in_title == 55
|
assert in_title == 55
|
||||||
assert in_headline == 45
|
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
|
# ---------------------------------------------------------------- detail extraction
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue