- New Step 04.5 bm25_prefilter: cheap lexical BM25 pre-cut right after
hard_filter, before potion_broad's expensive embedding pass. Shrinks
potion's input from ~74K to 10K rows using JD-keyword overlap alone.
composite_narrow still runs its full potion+soft+bm25 blend later, once
those signals exist, so ranking quality is unchanged. Falls back to a
full passthrough on an empty/failed JD spec rather than an arbitrary cut.
- broad_retrieval/composite_narrow/precision all use fixed top_n counts
now (500/200/125) instead of a mix of top_pct and top_n.
- Moved the candidates Parquet cache down into load_candidates() itself
(was one layer up in run_preprocess, which Streamlit's per-phase
explorer bypasses). Now both the CLI and Streamlit share one cache —
verified Streamlit's eager warm-up dropped from 14.6s to 0.27s.
- Streamlit's _load_phase_input switched to st.cache_resource (avoids
copying the 100K-row frame on every cache hit; safe since every
downstream Polars st…
Move JD extraction from data/artifacts/ (gitignored) to data/ so the demo-critical artifact ships with the repo/image. Increase n_ctx from 4096→6144 to handle longer JDs safely. Reduce parse_attempts from 2→1 (mitigated via improved prompt clarity). Retire target_industries/exclude_companies from LLM prompt to free budget for skill/title recall, but keep as [] in schema for stability. Major prompt refinements: simplify role/task structure, emphasize skill recall priority with explicit scan guidance, consolidate constraints. Add data/jd_extraction.json fixture (parsed from sample job_description.md) and empty secrets file. Update tests & docs to reflect retired prompt fields.
Switch JD-parse/T1 polish to Qwen3-1.7B
Hemprasad Badgujar
baeef08
Replace references and defaults for the JD-parse / T1 reasoning polish from Qwen3.5-2B to Qwen3-1.7B across docs, config, Dockerfile and code. Make T1 LLM polish conditional on reasoning.mode == "llm" (default "template" keeps full NLG, no LLM). Update model repo/gguf names, README benchmarking notes, prefetch logging, and explanatory comments to reflect the new preferred model and behavior.
Add LLM call timeout and streaming pipeline phases
Hemprasad Badgujar
26f86bb
Implement hard per-call timeout (45s) via threading to prevent runaway LLM generations that were observed taking 1996s with no way to interrupt. Convert pipeline phases to generators to yield results as they complete instead of batching at the end, enabling live status updates on slow hardware. Add per-call timing metrics to JD parse artifact. Introduce post-hoc reasoning audit to catch empty/duplicate/leaked-name issues for hackathon grading. Update polish acceptance gate with sentence-count bounds (2-4) and contradiction markers to ensure top-10 notes read as strengths-only.
Enhance JD extraction (Qwen3.5-2B + richer prompt) and Step-11 reasoning
Hemprasad Badgujar
eb18ae9
JD extraction:
- Switch JD parse to Qwen3.5-2B (standard-instruct GGUF); reasoning stays Qwen3-1.7B (separate handle)
- Gate Qwen's /no_think soft switch to Qwen models only (LlmClient._soft_no_think)
- Enrich the OBJECTIVE prompt with related skills (nice_to_have), target_industries,
exclude_companies, and all welcomed locations; guard out JD-rejected domains
- best-of-N now targets must_have recall only; _as_str_list dedups keywords case-insensitively
- City->country gazetteer fallback for acceptable_countries
Step-11 reasoning:
- Enrich grounded facts: industry / education / recency / title-fit + dominant-signal tail
- Revive the recent_days knob; LLM polish on Qwen3-1.7B
Config/cleanup: hard-filter graded checks off at funnel entry; doc fixes; remove stray files.
Tests: +dedup / related-field / explain coverage (93 pass, ruff + mypy clean).
Enrich the embed/rerank query with extracted skills + roles
Hemprasad Badgujar
1cbb761
potion / granite / cross-encoder previously compared candidate text against only the
JD ideal_summary; the extracted must_have_skills and job_titles fed BM25 but not the
embedders. embed_query() now composes ideal_summary + "Core skills: ..." + "Relevant
roles: ..." (summary leads so it survives the embedders' 128-token truncation and
leaves the cross-encoder room for the doc; skills/titles capped 15/8). Wired into the
pipeline and the explorer (which now shows the enriched query per embed phase).
Quality-neutral on the full 100K run (mean matched skills 4.31 vs 4.37 baseline, within
temp-0.5 parse noise). Also fix a band assertion made flaky by the true-random phrasing.
JD prompts:
- Restructure objective/subjective prompts to Role-Task-Context-Constraints-Output;
pin the persona + Mode constraint so Qwen3 skips its thinking loop.
- Make the objective prompt GENERIC (no leaked JD-specific skill/title examples) after
experiments showed listed examples don't drive recall and can anchor/echo.
- Compress the schema 12->7 keys: merge nice_to_have into must_have_skills, merge the
two location fields, drop certifications/target_industries/exclude_companies from the
prompt (they default to [] in _coerce_objective, so the funnel is unaffected). The
leaner prompt freed model capacity -> ~30 skills recall.
- job_titles: keep the verbatim posted title first, add the role family, and always
include the JD's core-activity roles (Search/Ranking/IR Engineer). Generic framing.
- acceptable_countries inferred from cities/"outside <country>"; capture every city.
Recall controls (config):
- Best-of-N objective extraction: parse_temperature (0.5), parse_attem…
Add field-scoped BM25 and composite blending
Hemprasad Badgujar
99deeee
Implement field-scoped BM25 matching (match each JD keyword bag only against its candidate text section), z-normalize per-field scores, and blend them into the composite bm25_score used in composite_narrow.
Changes include:
- Rank logic: introduce field_scoped_bm25, _znorm, and helpers in src/rank/composite.py; define _BM25_FIELDS and use per-field bm25 weights to compute bm25_score and bm25_matched metrics; keep composite as z-blend of potion/soft/bm25.
- Config: expose per-field BM25 weights and document field-scoped behavior in src/config.py and config/settings.yaml.
- Streamlit: update explorer labels and inputs to show BM25 query terms by field, reuse field_scoped_bm25/_BM25_FIELDS for display, and add an option to regenerate JD extraction (live parse) before running the pipeline.
- README: document the field-scoped BM25 approach and show field→JD mappings and blending behavior.
- Tests: update tests/test_composite.py to exercise per-section text columns and adjust expectations f…
Drop disqualifiers field — remove dead JD signal
Hemprasad Badgujar
86788de
disqualifiers was stored on JDSpec and displayed but never consumed by
any filter (hard_filter doesn't read it). Removed it from the OBJECTIVE
prompt, _coerce_objective, JDSpec + _spec_from_mapping, the Streamlit JD
panel, and the test assertion, so there's no orphaned always-empty field.
Update Dockerfile and refine JD parsing prompts
Hemprasad Badgujar
02e7964
Remove unnecessary apt packages (build-essential, git) from Dockerfile and clarify that curl is the only essential package for the HEALTHCHECK; note that llama-cpp-python now installs as a prebuilt CPU wheel (improves image size and build caching). Refactor and clarify job-description prompts in src/jd/parse.py: reflow and simplify objective/subjective prompt text, require a JSON object output (remove trailing "JSON:" markers), expand and clarify list-field guidance (job_titles examples, years_min/years_max as numeric values, acceptable_countries semantics), and improve overall prompt clarity for extraction.
Fix JD OBJECTIVE prompt: request all fields the funnel scores on
Hemprasad Badgujar
fc30850
The prompt never asked the model for target_industries, exclude_companies,
disqualifiers, or certifications, yet _coerce_objective reads them and the
ranker consumes them (soft_filter industry boost + company penalty,
disqualifier hard-filters, composite BM25 terms). They were therefore almost
always empty, silently disabling those scoring branches. Add the four fields,
split the conflated locations_preferred/locations_acceptable bullets, restore
the JSON: anchor and the use-[] guidance, and fix the 'incliuding' typo and
the stray years line.
Prefer prebuilt llama-cpp-python wheels
Hemprasad Badgujar
d56482d
Add an explicit CPU wheel index in pyproject.toml and map llama-cpp-python to that source so the project pulls prebuilt CPU wheels instead of compiling the C++ backend from source (avoids slow CMake builds / MSVC on Windows). Also tweak job description parser examples in src/jd/parse.py (adjust nice_to_have and simplify job_titles examples). Lockfile updated accordingly.
Add reasoning LLM, perf tweaks, and UI timing
Hemprasad Badgujar
f85f907
Reduce ONNX truncation lengths to 128 for precision and rerank to cut O(n^2) cost. Add a configurable reasoning step (reasoning.mode: template|llm) with small Gemma GGUF client and token cap; implement safe LLM rephrase with cleaning and deterministic fallback. Parallelize JD parse and preprocessing to overlap model load / polars work. Make candidate location visible to BM25 and include JD locations in composite BM25 queries. Update JD parse prompt wording and add a Streamlit timing summary; tests updated to use template reasoning settings.
Add full pipeline, submission & reasoning support
Hemprasad Badgujar
e73e29e
Introduce an end-to-end pipeline and deliverable handling: add pipeline.run_full and a CLI --out flag to run the full funnel and write submission.csv. Add a deterministic reasoning step (reasoning.add_reasoning) to generate one-line recruiter blurbs and a submission writer (rank.write_submission) that normalizes scores, enforces the 100-row validator contract, and atomically writes the CSV. Move JD country hard-drop from soft_filter into composite_narrow and replace the legacy accept_outside_india boolean with an acceptable_countries list across jd.parse, jd.spec, config models, and tests. Add ReasoningSettings and SubmissionSettings to config and update settings.yaml and .gitignore. Wire reasoning into the Streamlit app, export write_submission from rank.__init__, and add tests for reasoning and submission; adjust tests that relied on the old location behavior.
Add retrieval & soft/composite ranking stages
Hemprasad Badgujar
08c3203
Introduce broad retrieval (potion Model2Vec) + soft_filter scoring + composite_narrow (BM25 + z-blend) pipeline steps. Adds retrieval implementations (potion static embedder, BM25 tokenizer/scorer), ranking modules (soft_filter, composite_narrow), and tests for bm25, potion, composite, soft_filter and hard_filter behavior. Update config/settings to expose new tuning blocks (broad_retrieval, soft_filter, composite_narrow) and default embedder, refine hard_filter inactive logic and integrity toggles, and export new rank/retrieve APIs. Streamlit explorer updated to include rank stages and show JD spec; pyproject dependencies added model2vec and numpy. These changes narrow the candidate pool deterministically before expensive embedding and blend semantic + lexical + quality signals for the top-N cut.
Add JD parsing, central config, checkpoints & LLM
Hemprasad Badgujar
700e896
Introduce a central Settings system (config/settings.yaml + src/config.py) and enable live JD extraction (problem/job_description.md) via a new JD parsing pipeline (src/jd/*) that emits a unified jd_extraction.json. Add a local GGUF LLM client and helper (src/models/llm.py, src/models/__init__.py) to drive the JD calls and model prefetching. Implement safe atomic file IO and per-phase Parquet checkpoints (src/common/io.py, src/common/checkpoint.py) and wire them into common exports. Expand preprocess functionality (schema overrides, date helper, pipeline exports) and update the CLI to run preprocess and accept a --checkpoints flag. Update Dockerfile to include central config and data, and refresh pyproject dependencies for the new modules. Add a suite of tests for the new components.