Skip to content

Improve search matching diagnostics and ranking - #88

Merged
emnl51 merged 1 commit into
mainfrom
codex/improve-search-matching-v3
Sep 5, 2026
Merged

emnl51 merged 1 commit into
mainfrom
codex/improve-search-matching-v3

Conversation

@emnl51

@emnl51 emnl51 commented Sep 5, 2026

Copy link
Copy Markdown
Owner

Summary

Redesigns the next layer of job discovery and matching around measurable retrieval quality, evidence-backed eligibility and conservative learning.

Matching and recall

  • adds a public vacancy diagnostic that replays blocklist, role, working-time, language, learning and fit gates
  • saves labelled “should match / should not match” examples and reports precision, recall and exact failures
  • enriches sparse provider results from safe public vacancy pages before deciding that hours or other constraints are unknown
  • extracts weekly hours, workload, student requirements, experience, CEFR language levels and quality role specialization with evidence
  • prioritizes technician-specific and engineer-specific quality queries

Ranking and learning

  • makes ordinary positive/negative learning require two distinct corroborating vacancies
  • keeps interview and offer evidence immediately actionable
  • adds optional local multilingual Ollama embedding reranking (off by default, maximum 20%)
  • semantic similarity only reorders already eligible vacancies and cannot override hard gates

Sources and data quality

  • records per-Search Job source and query funnels with productive / low-yield / no-results status
  • clusters conservative cross-source duplicates
  • preserves the richest description, all source application links and discovery queries across runs
  • keeps enrichment, analytics and semantic failures from breaking deterministic search

UI and operations

  • adds “Why was a job missed?” diagnostics and benchmark controls to Search Jobs
  • exposes source/query quality reports
  • shows extracted role specialization, experience and student requirement in job details
  • documents configuration and behavior in README and CHANGELOG

Validation

  • 263 passed
  • total coverage: 74.10% (required: 50%)
  • ruff check app tests
  • ruff format --check app tests
  • python -m compileall -q app tests
  • node --check app/*.js
  • pip-audit -r requirements.txt --ignore-vuln PYSEC-2026-1604: clean (one documented upstream exception ignored)

The local runner does not provide Docker; the existing GitHub Actions container job will perform the image build and health smoke test.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 33c2ae664d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread app/job_enrichment.py
Comment on lines +179 to +183
current = await asyncio.to_thread(_public_http_url, url)
headers = {"User-Agent": "BertJobAnalyzer/20 (+https://github.com/emnl51/bert)"}
async with httpx.AsyncClient(timeout=20, follow_redirects=False, headers=headers) as client:
for _ in range(4):
response = await client.get(current)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Pin the validated address for public fetches

When an attacker-controlled hostname changes its DNS answer between _public_http_url() and client.get(), validation can see a global address while HTTPX's second resolution connects to a private or link-local service. This leaves both the diagnostic endpoint and automatic enrichment vulnerable to DNS-rebinding SSRF; resolve once and pin the connection to the validated address, or validate the actual connected peer.

Useful? React with 👍 / 👎.

Comment on lines +217 to +225
eligible = bool(
not blocked
and role.relevant
and not hard_employment
and employment_ok
and job.overall_score >= min_score
and job.language_score >= min_language
and language_allowed
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include the CV Match gate in diagnostic eligibility

When a Search Job has an assigned candidate and the vacancy has sufficient description evidence, run_search_job() can exclude it because analyze_job() falls below min_cv_match, but diagnose_job() never loads the assignment or evaluates that gate. Such vacancies are reported as eligible and saved benchmarks produce incorrect precision/recall, so the diagnostic must replay the candidate gate or explicitly report it as unevaluated.

Useful? React with 👍 / 👎.

Comment thread app/search_job_service.py
Comment on lines +186 to +188
company = _company_signature(getattr(job, "company", ""))
candidates = company_buckets.get(company, []) if company else range(len(unique))
matched_index = next((index for index in candidates if _same_vacancy(unique[index], job)), None)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Check canonical URLs before restricting candidates by company

When two providers return the same canonical URL with different non-empty company strings, this bucket lookup only compares the new job against entries having the same normalized company, so _same_vacancy() never gets a chance to apply its URL match. Common variants such as Acme GmbH versus Acme Deutschland GmbH therefore remain duplicate rows and lose the promised combined source links; exact URL candidates need a separate index checked before company bucketing.

Useful? React with 👍 / 👎.

Comment thread app/positive_learning.py
Comment on lines +315 to +318
"""SELECT scope,term,weight FROM positive_rules
WHERE enabled=1 AND profile_id=?
AND (evidence_count>=? OR strongest_event IN ('interview','offer'))""",
(profile_id, MIN_POSITIVE_EVIDENCE),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Count positive corroboration by distinct vacancy

When one vacancy is first marked suitable and later reaches applied, record_positive_event() increments the same rules once for each event type, so evidence_count becomes 2 and this predicate activates the boost without a second example. That defeats the new two-example safeguard for ordinary evidence; readiness should count distinct job keys, while retaining the explicit interview/offer exception.

Useful? React with 👍 / 👎.

Comment thread app/stepstone_provider.py
Comment on lines +192 to 194
job.discovered_queries = list(dict.fromkeys([*job.discovered_queries, term]))
if job.url in seen:
continue

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve every query hit before provider deduplication

When the same StepStone vacancy is returned for multiple search terms, each parsed object receives only the current term and all occurrences after the first are immediately discarded, making the query funnel credit only whichever term ran first. The same ordering-dependent pattern exists in Adzuna and JobSpy, so fetched, recommended, and new_matches are inaccurate for overlapping queries; merge the term into the retained job before skipping the duplicate.

Useful? React with 👍 / 👎.

@emnl51
emnl51 merged commit 301103d into main Sep 5, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant