{"slug": "under-the-hood-of-jobradar-8-job-sources-a-1-7b-model-zero-cloud", "title": "Under the Hood of JobRadar: 8 Job Sources, a 1.7B Model, Zero Cloud", "summary": "A developer built JobRadar, a CLI tool that searches eight job sources concurrently and scores listings against a user's profile using a local 1.7B parameter LLM (qwen3-1.7b) running on the user's CPU, with no cloud APIs or subscriptions. The tool pulls roles from company career pages via Greenhouse and Ashby APIs without authentication, and ranks jobs based on skills, experience, salary, and remote fit, with a SQLite cache to avoid duplicates.", "body_md": "Job searching is tab hell. You open six boards, re-read the same listings, and pay $30/month for tools that are just feed aggregators wearing an \"AI-powered\" sticker.\n\nSo I built JobRadar — a CLI tool that searches 8 job sources concurrently and scores every listing against your profile using a local LLM that runs on your own CPU. No API keys, no subscriptions, no resume leaving your machine.\n\nThe launch post covered the what. This one covers the how.\n\nThe whole thing is one linear pipeline:\n\n```\nquery + profile.yaml\n      │\n      ▼\n8 source adapters (concurrent) ──► normalized Job objects\n      │\n      ▼\nlocal LLM scores each job 0-100 (skills, experience, salary, remote fit)\n      │\n      ▼\nseen-jobs cache (SQLite, 7-day)  ──► ranked table + results.csv\n```\n\nIn code, the search phase looks like this:\n\n``` python\nfrom concurrent.futures import ThreadPoolExecutor, as_completed\n\ndef search_all(sources, query, limit, max_pages):\n    jobs = []\n    with ThreadPoolExecutor(max_workers=len(sources)) as pool:\n        futures = {pool.submit(s.search, query, limit, max_pages): s\n                   for s in sources}\n        for future in as_completed(futures):\n            jobs.extend(future.result())\n    return jobs\n```\n\nEight adapters, one interface, all running in parallel. A query that used to take me 20 minutes of tab-hopping finishes in under a minute.\n\nFive of the sources are classic job board APIs:\n\nThe interesting two are **Greenhouse** and **Ashby**. These are applicant tracking systems that a huge chunk of tech companies use — and they have public career-page APIs. No auth, no scraping. You just hit their job board endpoint with a company slug:\n\n``` python\nclass GreenhouseSearch(Source):\n    def search(self, query, limit, max_pages):\n        jobs = []\n        for company in self.companies:          # e.g. gitlab, figma, stripe\n            url = f\"https://boards-api.greenhouse.io/v1/boards/{company}/jobs\"\n            resp = requests.get(url, timeout=10)\n            jobs.extend(normalize_greenhouse(resp.json()))\n        return jobs\n```\n\nThat means JobRadar pulls roles straight from company career pages — GitLab, Figma, Stripe, OpenAI, Anthropic, Linear — without an account or an API key on any of them. The company list is a YAML file you can edit:\n\n```\ngreenhouse:\n  - gitlab\n  - figma\n  - discord\n  - shopify\n  - stripe\n```\n\nLinkedIn scraping exists but is **off by default** — it depends on undocumented HTML that breaks constantly, and it might violate their ToS. I'd rather ship without it and be honest about the tradeoff than bundle a ToS risk into the default path.\n\nThis is the part people ask about the most. Every other tool I found uses cloud APIs (Claude, OpenAI) for scoring — your resume and search history go to someone else's server, and you pay per token. JobRadar runs **qwen3-1.7b** (a 1.1 GB GGUF) on your machine via Ollama or llama.cpp.\n\nThe rater auto-detects whatever local LLM server is running by scanning the standard ports:\n\n```\n_DEFAULT_PORTS = [\n    (\"http://localhost:11434\", \"Ollama\"),    # Ollama default\n    (\"http://localhost:8080\",  \"llama.cpp\"), # llama.cpp default\n    (\"http://localhost:1234\",  \"LM Studio\"), # LM Studio default\n]\n```\n\nIf you have a bigger model installed, it picks that up too — you can override with `--llm-model qwen3:8b`\n\nand scoring gets smarter at the cost of speed.\n\nEach job gets scored 0-100 across four dimensions — skills match, experience fit, salary fit, remote fit — with the model's reasoning captured so you can see *why* a job scored the way it did. Rating calls run concurrently (3 by default, `--max-concurrency`\n\nto tune) with retry logic for the occasional malformed JSON response.\n\nHonest take: a 1.7B model is a good filter, not an oracle. It catches \"this says Python but is actually a sales role\" reliably. It does not have your gut feel about company culture, and it shouldn't — that's the point. The score is a starting point, not a verdict.\n\nJob boards republish the same listings constantly. JobRadar keeps a SQLite database of seen jobs (`~/.jobradar/seen_jobs.db`\n\n) with a 7-day window, so every run only surfaces what's new. `--cache-days 30`\n\nto stretch it, `--no-cache`\n\nto see everything again.\n\nThe CLI is the core, but there's a companion web dashboard — a FastAPI backend, SQLite storage, and a dark-mode SPA with a Kanban pipeline. Jobs flow from Discovered to Reviewing to Applied to Interviewing, cards are color-coded by match score, and there's a live terminal-style activity log showing the LLM scoring progress as it happens. It reads like a terminal because the whole product is terminal-first.\n\n`Job`\n\ndataclass at the boundary and every downstream step gets simpler.\n\n```\ncurl -fsSL https://raw.githubusercontent.com/ANIRudH-lab-life/job-radar/main/setup.sh | bash\n```\n\nOr browse the source: [https://github.com/ANIRudH-lab-life/job-radar](https://github.com/ANIRudH-lab-life/job-radar)\n\nLanding page: [https://anirudh-lab-life.github.io/job-radar/](https://anirudh-lab-life.github.io/job-radar/)\n\nMIT licensed. Built with Python, Rich, FastAPI, SQLite, and a whole lot of tabs closed.\n\n*Also published: the launch post*", "url": "https://wpnews.pro/news/under-the-hood-of-jobradar-8-job-sources-a-1-7b-model-zero-cloud", "canonical_source": "https://dev.to/anirudh_shivam/under-the-hood-of-jobradar-8-job-sources-a-17b-model-zero-cloud-3gne", "published_at": "2026-08-02 05:28:42+00:00", "updated_at": "2026-08-02 06:10:57.393165+00:00", "lang": "en", "topics": ["developer-tools", "artificial-intelligence", "large-language-models", "ai-products"], "entities": ["JobRadar", "Greenhouse", "Ashby", "Ollama", "llama.cpp", "LM Studio", "qwen3-1.7b"], "alternates": {"html": "https://wpnews.pro/news/under-the-hood-of-jobradar-8-job-sources-a-1-7b-model-zero-cloud", "markdown": "https://wpnews.pro/news/under-the-hood-of-jobradar-8-job-sources-a-1-7b-model-zero-cloud.md", "text": "https://wpnews.pro/news/under-the-hood-of-jobradar-8-job-sources-a-1-7b-model-zero-cloud.txt", "jsonld": "https://wpnews.pro/news/under-the-hood-of-jobradar-8-job-sources-a-1-7b-model-zero-cloud.jsonld"}}