Job search in Denmark: aggregate jobnet.dk and jobindex.dk, rank matches with Laya
Collects vacancies, filters them against a candidate profile, ranks with the multilingual Laya checkpoint and prints IT and warehouse tables plus a Markdown report.
# Job classifier search </div> Collects job ads from jobnet.dk and jobindex.dk, filters them against a candidate profile, ranks them with the Laya decision model, prints two tables (IT, warehouse/logistics) and writes the same result to `jobs.md`. ## Example of work | Ranked IT jobs | Warehouse / logistics | | --- | --- | | <img src="img/1.png" alt="Terminal table with ranked IT jobs" width="480"> | <img src="img/2.png" alt="Continuation of the IT table and the warehouse/logistics table" width="480"> | ## Run Python 3.13 and `uv`. Commands are run from the project root: `profile.yaml`, `.cache`, `labels.csv` and `jobs.md` are resolved relative to the working directory. ``` uv sync uv run python src/jobfit.py ``` The first run downloads the Laya multilingual checkpoint and fetches every job page, about four minutes. Later runs use the cache and take seconds. ## Pipeline 1. Sources. `sources/jobnet.py` calls the Jobnet search API, `sources/jobindex.py` calls the Jobindex search API. Pages are fetched in parallel and cached. 2. Enrichment. `sources/enrich.py` opens each Jobindex ad page: internal ads give the full text, external ones only a teaser. Jobnet returns full descriptions. 3. Normalization. Both sources map to one dict: title, company, location, url, description, deadline, address, coordinates, source. Duplicates are dropped by title and company. 4. Filters. A keyword gate keeps ads that mention a profile skill or role. `blacklist_companies`, `blacklist_title_keywords` and `blacklist_description_patterns` from `profile.yaml` remove the rest. 5. Scoring. Laya answers nine typed questions per ad: job family, seniority, skill overlap, role fit, English/Danish, Copenhagen, spam, sponsorship. Raw answers are cached, so percents can be