Metadata-Version: 2.4
Name: job-radar
Version: 0.5.1
Summary: Harvest the whole open job market for roles that fit you — direct from company ATS feeds and aggregator APIs — scored, deduped, with optional LLM semantic ranking.
Author: Justin Hawkes
License-Expression: Apache-2.0
Project-URL: Homepage, https://github.com/hawkesj12/job-radar
Project-URL: Repository, https://github.com/hawkesj12/job-radar
Project-URL: Issues, https://github.com/hawkesj12/job-radar/issues
Project-URL: Changelog, https://github.com/hawkesj12/job-radar/blob/main/CHANGELOG.md
Keywords: job-search,ats,llm,cli,greenhouse,lever
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: End Users/Desktop
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Office/Business
Classifier: Topic :: Utilities
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pyyaml>=6.0
Requires-Dist: rapidfuzz>=3.0
Requires-Dist: tzdata; sys_platform == "win32"
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: mypy>=1.11; extra == "dev"
Requires-Dist: types-PyYAML>=6.0; extra == "dev"
Dynamic: license-file

# job-radar

**Scan the open job market in about a minute** — your watchlist of companies plus eleven aggregator APIs, straight from company applicant-tracking-system feeds — scored for fit, de-duplicated, and (optionally) semantically ranked by an LLM. It remembers what you've seen and applied to, so every run shows you what's _new_. Start with the ~20-company starter list and grow your watchlist to hundreds with one `seed` command.

Not another board to scroll. A radar that harvests the market _for_ you and routes you to the source.

```
pipx install job-radar                      # or: pip install job-radar
job-radar init                              # write a starter job-radar.yaml + watchlist.json here
# edit job-radar.yaml to make it yours
job-radar                                   # scan → ranked shortlist.csv
job-radar apply <id>                        # mark a role applied (it stops resurfacing)
job-radar list                              # see your current shortlist
job-radar --version                         # which version am I running?
```

Runs on Linux, macOS, and Windows, Python 3.10 or newer.

## What it does

1. **Harvests two ways.** _Depth_ — polls each company on your watchlist directly via its public ATS feed (Greenhouse, Lever, Ashby, SmartRecruiters, Workable, Workday), so you see roles the hour they post. Workday is the enterprise one: it reaches the manufacturers, insurers, hospitals, municipalities and national labs that never appear on the startup boards. _Breadth_ — queries aggregator APIs (Remotive, Jobicy, Arbeitnow, RemoteOK, Himalayas, Adzuna, USAJOBS, Google for Jobs, Hacker News "Who is Hiring," Braintrust, TechTree) across the whole market. Google for Jobs is the meta-aggregator: it indexes company career sites and enterprise ATSs (Workday, iCIMS) that no single feed exposes, and job-radar **prefers** its direct-to-employer apply link over an aggregator redirect — Google often lists LinkedIn or Indeed first, so the first non-aggregator option wins; if a listing offers only aggregators, you get the best of those.
2. **Scores every role on one comparable scale** — a transparent, weighted keyword model you fully control in the config, with BM25 length normalization and term-frequency saturation (`length_norm_b`, `score_k1`) so a long, thorough job description isn't punished for its length and a keyword-stuffed one-liner can't win on brevity. The weights are yours and hand-set; there's no IDF, so a keyword's value is what you say it is, not how rare it happens to be. Tuned for _recall_ by design (catch everything that might fit); the optional LLM re-rank below is the _precision_ layer.
3. **De-duplicates** the same role across sources into one entry.
4. **Grows its own watchlist two ways** — _reactively_, when a job links to a company's ATS that company is auto-added; and _proactively_, `job-radar seed greenhouse` (also `lever`, `ashby`, `workable`, `smartrecruiters`, `workday`) does one Common Crawl pass to enumerate the companies hosting a public board on that ATS and bulk-adds them — up to `--max` per run (default 500). Add `--verify` to probe each board and keep only the live ones. A couple of `seed` runs build out a several-hundred-company watchlist.
5. **Remembers.** One upserted `shortlist.csv` tracks `first_seen`, `status`, and every role's score. `apply`/`dismiss` are sticky — applied roles persist and stop resurfacing.

## Optional: LLM semantic fit-ranking

Keyword scoring is fast and free but blind to _meaning_ — a perfect role that phrases things differently scores low. Add an API key and job-radar re-ranks the **top of your list** for semantic fit (0–100) with a one-line _why it fits / what's missing_ note:

```yaml
llm:
  enabled: true
  provider: anthropic # or an OpenAI-compatible endpoint
  api_key_env: ANTHROPIC_API_KEY
  rerank_top_n: 25
```

Off by default (the tool runs free with no key), cost-bounded to the top-N, one request per run.

## Make it yours

Everything is in one file — `job-radar.yaml`. Set your **target titles**, tune the **fit-weight keywords** (add your industry, your city), and adjust **filters** (`max_age_days`, `min_score`, remote-only, excluded locations). See `job-radar.example.yaml` for every knob.

**Or let AI write it for you (easiest).** Paste [`prompts/build-config-with-ai.md`](prompts/build-config-with-ai.md) into any AI assistant (Claude, ChatGPT). It interviews you about the job you want in plain English, then hands you a ready-to-save `job-radar.yaml` — no YAML editing required.

## Scope — tuned for remote/tech, generalizes to anyone

Out of the box it's tuned for **remote software/AI** roles, because the shipped example config and the free/keyless sources are remote-tech boards. But nothing about the engine is tech-specific — you generalize it in three steps:

- **Any field:** change `signal_titles` + `fit_weights` in the config to your field's language (nursing, finance, trades…). No code.
- **On-site / any location:** set `remote_only: false` and `location: "Your City, ST"`.
- **Any field _and_ location, for real:** turn on the **general sources** — **Adzuna**, **USAJOBS**, and **Google for Jobs** (every field, any location, where the whole market lives). Adzuna and USAJOBS keys are free; Google for Jobs goes through SerpApi, whose free tier is 250 searches a month — each page of results is one search, so `GOOGLE_JOBS_PAGES` defaults to 1.

Honest limits: the tool's _superpower_ — harvesting a role the hour it posts, direct from a company's ATS — is strongest in tech, because Greenhouse/Lever/Ashby are tech-company systems; for other fields you lean on the general aggregators. And the truly local, unposted, word-of-mouth job isn't in any structured feed, so no tool reaches it. Everything that _is_ posted online, this can find.

## Use it as a library

The CLI is one caller; the engine underneath is importable, and it doesn't touch your disk. `harvest` takes your companies as **data** — either a `watchlist.json` path or a plain list — and _returns_ everything it produced, including the companies it discovered along the way. Persisting them is the caller's job (the CLI appends them to `watchlist.json`; an app might write them to its own database).

```python
from job_radar import config, engine

config.set_active(config.load_config("job-radar.yaml"))   # optional; defaults apply otherwise

rows, discovered, errors = engine.harvest(companies=[
    {"name": "Anthropic", "ats": "greenhouse", "slug": "anthropic"},
    # Workday needs a three-part key instead of a bare slug:
    {"name": "Example Corp", "ats": "workday", "slug": "examplecorp",
     "host": "wd1", "site": "External"},
])
```

`job_radar.discover` builds the company universe in bulk: mine the Common Crawl index for every board on an ATS, or resolve a company **name** to its slug for employers the index never saw. Every candidate is proven by a live probe, and — where the ATS will tell us who owns a board — checked for **identity**, not just liveness. A probe proves `jobs.lever.co/capital` is a real board with real jobs; it does not prove it belongs to Capital One.

> Upgrading from 0.2.x: `job_radar.store` is now `job_radar.shortlist`.

## Legal & etiquette

This is a **personal job-search tool**, not a data-resale product, and it's built to be a good citizen:

- **Default sources are official, public, no-auth APIs**, used exactly as their vendors document them — Greenhouse, Lever, and Ashby publish these job-board endpoints _for_ programmatic use. Consuming a public API is distinct from scraping behind a login, and job-radar does none of the latter by default.
- **It caps its own request volume.** Remotive is hard-capped at 4 calls per run (its documented limit), and the paged sources pause between requests. Not every adapter is throttled — the per-company ATS feeds are one request each, so the volume comes from the size of your watchlist rather than from hammering any single provider. It sends a self-identifying `User-Agent` so providers can see and contact the caller.
- **It asks for the smallest thing that answers the question.** Checking whether a company's board exists costs **one** request, not a full download of every job on it — for a Workday employer that is 1 request instead of 210, and for a large Greenhouse board roughly 280 KB instead of 5.6 MB (measured 2026-07-31; the ~20x ratio is the durable part — the absolute sizes track whatever that board is doing today). Discovery is where a tool like this can be rude at scale, so that's where the restraint matters most.
- **Attribution:** **RemoteOK** and **Remotive** require that, if you _republish_ their listings, you credit them and link back to the original job URL (job-radar keeps the direct source URL for exactly this). Honor their terms if you share `shortlist.csv` publicly.
- **API keys** (Adzuna, USAJOBS, SerpApi, the LLM) are read from environment variables only and never logged or committed. Note that Adzuna's and SerpApi's keys travel in the request URL per their API designs.
- **Google for Jobs is reached through SerpApi**, a commercial API that is licensed to query Google — job-radar does not scrape Google itself. It is off unless you set `SERPAPI_KEY`, and metered, so it stays a deliberate opt-in rather than a default cost.

One honest exception to "used as documented": **Workday**. Its CxS endpoint is public and no-auth — it's the same one Workday's own hosted careers-site widget calls — but Workday doesn't publish third-party API documentation for it the way Greenhouse and Lever do. It's a public endpoint used as its own front end uses it, which is a weaker claim than the others on this list, and worth knowing before you point it at hundreds of employers.

## License

Apache-2.0.
