Semantic Job Matching Platform
Matching people to jobs on meaning, not keywords
DjangoPostgres + pgvectorCeleryTailwindDockerAWS
- Built
- 2025 – ongoing
- My role
- Front end and LMS integration — one of five contributors
- Stack
- Django, PostgreSQL + pgvector, Celery/Redis, Tailwind, Docker, AWS ECS, Terraform
- Code
- Private, under NDA — happy to talk about the engineering
Problem
Matching a person to a job on résumé keywords is a poor approximation of whether they can actually do the work. Keyword overlap misreads a gap in a work history as an absence of skill, and it can’t see that experience from a different industry transfers directly. Two people with nearly identical keywords can be completely different candidates.
The people doing the placing know all of this, and compensate with time and memory — both finite. Past a certain number of jobseekers, good candidates stop reaching the right employers. Not because anyone judged badly, but because nobody had the hours to look properly.
Solution
The platform scores person-to-job compatibility from 0 to 100 using semantic similarity rather than keyword overlap — embeddings stored in Postgres via pgvector, with weighting that adapts to the kind of candidate being scored (career changer, transitioning, domain expert). It scores training courses against jobs on the same basis, so a low match arrives with a suggestion for closing the gap instead of just a number.
Travel time between candidate and posting is factored in through the Google Routes API, and it earns its place: a role that scores well on paper but sits ninety minutes each way by bus is not, in practice, a match. When travel time can’t be calculated the scoring degrades gracefully rather than failing the whole comparison.
My work has been the layer the people using it sit in front of:
- The interface: brand colours and theming, the nav and collapsible left rail, pagination locked to the bottom of the viewport, and candidate and course detail behind modals rather than separate pages
- The front end for the in-app assistant panel
- LMS integration over LTI, so the platform launches inside the organisation’s existing learning system as an embedded tool rather than being one more login to remember
- Docker and deploy fixes along the way
Worth noting
The LTI work was the most stubborn part, and none of the difficulty is in the spec. Launching inside a learning system means running in a third-party iframe, which means the browser’s cookie rules are working against you by default — the session that authenticates perfectly in a tab silently fails to exist once it’s embedded. Getting that right took more attempts than I’d like to admit, and the branch names still show it.