Ten different hiring platforms, one canonical model, zero duplicate data
Every applicant-tracking system represents the same idea — a job posting — differently: different APIs, different HTML, different pagination, different field names for the same concept. This engine puts ten of them behind a shared adapter interface, converts everything into one canonical job model, and computes the delta between snapshots rather than re-ingesting the same postings as new data every run. It's the acquisition core that also powers the job-market side of the larger Elevya Signal Intelligence platform elsewhere in this portfolio, built first here as a standalone, reusable package.
Beta · Source reviewed · Selected tests passing
Why the project mattered
Structured as a proper Python package — Python 3.11+, database schemas, orchestration scripts, observability hooks — with dedicated modules for rate limiting, proxy health, normalization, revision history and hiring-velocity scoring, aimed at recurring monitoring rather than a one-off export. I built the adapter layer here first, proved it independently with its own test suite, then extended it inside Signal Intelligence with registry and procurement sources — the relationship between the two projects is direct, and I'd rather state that plainly than have someone diff the source and find it themselves.
My role and ownership
- Full ownership of the adapter and registry architecture
- Normalization and delta processing
- Rate limiting, retry and proxy-health controls
- PostgreSQL schemas and revision history
- The automated test suite for core logic and individual sources
Personal contribution
Solo design and implementation of the adapter registry, canonical schema, delta engine and test suite.
The operational pain
A title with a requisition code baked in, a location string that mixes city and remote-work signals in no consistent order, a salary range written as free text, a contract type spelled four different ways across four platforms — none of that is usable for comparison until it's normalized. And without snapshot-to-snapshot comparison, "new data" is really just the same postings re-ingested on every run, which produces volume without producing intelligence.
Before
- One-off source-specific scripts
- Different field names and types per ATS
- Repeated processing when boards have not changed
- No durable record of added, changed or removed jobs
- No company-level hiring momentum signal
After
- Adapter registry with source-specific clients behind one interface
- Canonical title, location, contract, salary and function fields
- Conditional fetch and rate-limit controls where the source supports it
- Delta and revision layers for full lifecycle tracking
- A High-Velocity Indicator for hiring-acceleration classification
How the system works
Ten source adapters — Greenhouse, Lever, Ashby, Workable, Personio, BambooHR, Recruitee, SmartRecruiters, Teamtailor and France Travail — sit behind one registry and shared orchestration, so adding an eleventh platform means writing one adapter, not touching the pipeline that runs all ten. Every record, regardless of source, lands in the same canonical schema: a title, a location, a contract type, a salary range and a function, normalized the same way whether it arrived as clean JSON from a GraphQL API or as HTML that needed parsing.
A delta module compares each new snapshot against the last known state per source, so only genuine changes — a new posting, a closed posting, a changed field — generate a revision record instead of re-writing everything every run. A hiring-velocity module turns the resulting change history into a company-level acceleration signal, which is the piece that actually makes ten normalized feeds worth more than ten separate spreadsheets: it answers "who's hiring faster than last month," not just "who's hiring."

Architecture and decisions
The adapter interface is strict enough that the orchestrator never needs to know which platform it's talking to, at the cost of upfront design time — that cost pays back every time a new ATS gets added, since the tenth adapter took a fraction of the effort the first one did. httpx and asyncio handle I/O-bound concurrency throughout, with psycopg for direct, predictable PostgreSQL access instead of an ORM's extra abstraction layer, and Selectolax for fast HTML parsing on the sources that don't expose a clean API.
Thin adapters, shared policy
Platform-specific parsing stays isolated in each adapter while retries, rate limits, metrics and database behavior improve once for the whole engine.
Normalize before downstream use
A canonical job record converts inconsistent titles, locations, contracts and salaries into fields that actually support analytics and comparison.
Track revisions, not only current rows
Delta processing distinguishes new, changed and closed jobs and preserves the evidence a hiring-momentum signal needs to exist at all.
Test contracts and edge cases
The repository includes tests for both infrastructure and individual adapters; the supplied capture shows 68 tests passing with 19 warnings.
Evidence that can be inspected.
Source-reviewed architecture and an actual passing test run, not a claimed one.


Value and honest current state
Operational impact
Ten adapter source files, 24 test modules covering infrastructure and platform clients, and PostgreSQL schemas for core records, revisions and cache with optimized indexes — this is the acquisition layer that made the larger Signal Intelligence platform possible to build in the time it took, because the hardest normalization problem was already solved and tested here first.
Verified evidence
Current limitations
The package's own configuration classifies it as Beta, and that's the accurate label, not a downgrade — no live multi-platform sweep is captured in this portfolio package, so the 68-test run proves selected logic is correct, not that every credential path and every live integration is exercised end to end. An optimization figure that appears in internal source documentation isn't repeated here because there's no benchmark artifact behind it, and a public demo of this should run against fixtures, not live credentials.
Need heterogeneous sources unified into one model?
Adapters, canonical schemas and delta tracking — built to survive a source changing its markup without notice.