Skip to content
xanes.dev
All projects

In development

Kortex

A job-scraping and application platform that discovers relevant roles across multiple job boards, deduplicates and scores them by relevance, and surfaces matches through a clean dashboard — built to cut through the noise of manual job hunting.

Overview

Kortex is a job-scraping and application platform: it discovers relevant roles across multiple job boards, deduplicates and scores them by relevance, and surfaces matches through a dashboard. It exists to cut through the noise of manual job hunting.

It is a multi-phase build in active development — the scraping engine, relevance scoring, and dashboard have shipped; application tracking and automation are in progress.

Problem

Job hunting is a high-noise, repetitive workflow. The same search runs across five boards, returns the same postings under different URLs, and buries the handful of genuinely relevant roles under hundreds of near-misses. Doing this manually every day is demoralizing and error-prone — exactly the kind of work software should absorb.

Solution

Kortex splits the pipeline into three phases. A scraping engine collects postings from multiple boards on a schedule and normalizes them into a common schema. A scoring layer deduplicates cross-board reposts and ranks each role against the user's profile, so the feed is ordered by relevance rather than recency. A dashboard surfaces the ranked matches with enough context to decide quickly.

Architecture

The backend is FastAPI, with scraping and scoring jobs running as ARQ workers on Redis — scrapes are scheduled, retried, and isolated from the request path. Supabase (Postgres) stores normalized listings and scoring state. The dashboard is a Next.js/TypeScript app talking to the API.

Deduplication happens at ingest: postings are canonicalized (company, title, location, description fingerprint) before scoring, so the same role syndicated across boards collapses into one entry.

job boards ──► per-source adapters ──► normalize (common schema)
                   │                             │
        ARQ workers on Redis                     ▼
        scheduled · retried ·             dedup at ingest
        isolated per source         (company · title · location ·
                   │                 description fingerprint)
                   ▼                             │
             request path                        ▼
             stays clean               relevance scoring vs
                                          user profile
                                                 │
                                                 ▼
                                  Supabase (Postgres) — listings
                                        + scoring state
                                                 │
                                                 ▼
                                   FastAPI ──► Next.js dashboard
                                               (ranked, not
                                                chronological)

Tech stack

FastAPINext.jsTypeScriptSupabase (Postgres)RedisARQ workers

Challenges

  • Job boards differ wildly in structure and change without notice — the scraper layer needs per-source adapters and has to fail per-source, not globally.
  • Cross-board deduplication is fuzzier than it looks: the same role appears with different titles, salary framing, and truncated descriptions depending on the board.
  • Relevance scoring has to stay explainable — a ranked feed the user doesn't trust is worse than a chronological one.

Lessons learned

  • Queue-first design pays for itself immediately: putting every scrape behind ARQ made retries, scheduling, and rate-limiting boring problems instead of architectural ones.
  • Normalizing to a strict schema at ingest — before storage, before scoring — kept every downstream phase simple.
  • Shipping in phases (scrape → score → dashboard) meant each layer was validated against real data before the next was built on top of it.

Future improvements

  • Application tracking — moving from “found a match” to managing the full application lifecycle.
  • Notification channels for high-score matches.
  • Feedback-driven scoring: letting accept/reject decisions tune the relevance model.
  • More board adapters.