feat: add ats source for live Greenhouse/Ashby role feeds#1
Open
setretter wants to merge 2 commits into
Open
Conversation
The `watchlist_careers` scraper cannot read JavaScript-rendered career
pages, so most modern boards yield no individual postings. This adds an
`ats` source that reads structured job data directly from the public
Greenhouse and Ashby JSON APIs.
For each watchlist company with an `ats: {provider, token}` block, the
adapter fetches that board and emits one FetchedItem per posting with
`evidence_type="job"`, so real role titles flow into scoring and can
reach the Act Now tier.
Optional per-source filters:
- `title_includes` / `title_excludes`: keep/drop postings by title term.
- `us_only`: keep only US-based or remote-eligible roles, classified from
structured country data (Ashby `addressCountry`, Greenhouse trailing
location token) via exact token match — US cities like Milwaukee or
Indianapolis are never misread as foreign.
- `location_block`: substring blocklist on the display location.
- `max_jobs_per_board`: cap postings per company.
Additive and resilient: existing adapters and `scoring.py` are unchanged,
a dead token or malformed job is skipped without failing the run, and the
new source type is registered in `doctor` validation. README documents the
config; adds network-free tests for parsing and country classification.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extends the `ats` source to read Lever's public postings API
(api.lever.co/v0/postings/<token>?mode=json) alongside Greenhouse and
Ashby. Lever returns a bare JSON list (not {"jobs": [...]}) and exposes
an ISO-3166 alpha-2 `country` field, which is classified directly
(US -> kept; any other -> foreign) rather than via the location-token
path, since ISO codes collide with US state abbreviations (Lever "CA"
means Canada, not California).
README documents the new provider; adds tests for Lever parsing and the
ISO/state collision case.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds a new
atssource type that pulls live role postings directly from the public Greenhouse and Ashby JSON job boards, for any watchlist company that declares anats: {provider, token}block.Why
The existing
watchlist_careersscraper reads static HTML, but most modern career pages are JavaScript-rendered — so for many companies it cannot see individual postings and falls back to a single page-level finding. Reading the structured ATS JSON gives real role titles, locations, and descriptions. Each posting is emitted withevidence_type="job", so real roles flow through scoring and can reach theAct Nowtier.How
fetch_ats()reads the watchlist and, for each company with anatsblock, fetches:boards-api.greenhouse.io/v1/boards/<token>/jobs?content=trueapi.ashbyhq.com/posting-api/job-board/<token>http_getchokepoint (retry/backoff + ETag cache).title_includes,title_excludes,us_only,location_block,max_jobs_per_board.us_onlyclassifies US vs foreign from structured country data (AshbyaddressCountry, Greenhouse trailing location token) using exact token matching, so US cities such as Milwaukee or Indianapolis are never misread as foreign.Notes
scoring.pyare unchanged; a dead token or malformed job is skipped without failing the run; the new source type is registered indoctorvalidation.tests/test_ats.py— network-free tests (mockedhttp_get) covering parsing, country derivation, US/foreign classification, and end-to-end filtering. Full suite: 66 passing.🤖 Generated with Claude Code