175 lines
7.3 KiB
Markdown
175 lines
7.3 KiB
Markdown
# Bron- en adapterarchitectuur
|
||
|
||
## 1. Adaptercontract
|
||
|
||
Iedere adapter implementeert conceptueel:
|
||
|
||
```python
|
||
class SourceAdapter(Protocol):
|
||
parser_key: str
|
||
parser_version: str
|
||
|
||
def extract(self, content: str, *, url: str) -> ExtractionResult: ...
|
||
```
|
||
|
||
`ExtractionResult` bevat:
|
||
|
||
- `jobs: list[ExtractedJob]`;
|
||
- stabiele parser key/version;
|
||
- confidence 0..1;
|
||
- machine-/mensleesbare warnings.
|
||
|
||
Adapters:
|
||
|
||
- doen geen netwerkrequests;
|
||
- schrijven niet naar ORM;
|
||
- voeren geen scoring of bronpolicy uit;
|
||
- werken deterministisch op aangeleverde fixtures;
|
||
- bewaren alleen beperkte raw payload voor debugging;
|
||
- leveren evidence per belangrijk veld.
|
||
|
||
## 2. Parservolgorde
|
||
|
||
Voor HTML:
|
||
|
||
1. JSON-LD `JobPosting`;
|
||
2. gespecialiseerde adapter op expliciete source parser key;
|
||
3. generieke HTML-fallback;
|
||
4. geen vacature wanneer minimumvelden ontbreken.
|
||
|
||
Voor XML/feed:
|
||
|
||
1. RSS/Atom;
|
||
2. sitemapdiscovery (backlog);
|
||
3. geen generieke HTMLparser.
|
||
|
||
Voor e-mail:
|
||
|
||
1. MIME decode zonder attachments;
|
||
2. linkextractie met unsubscribe/privacyfilter;
|
||
3. bij een platformmailbox uitsluitend gelabelde HTTPS-links op het gekozen jobboarddomein aanvaarden;
|
||
4. externe tracking-, account-, help-, privacy- en uitschrijflinks verwerpen;
|
||
5. platformalias en provenance bewaren;
|
||
6. oorspronkelijke werkgever proberen te resolveren in een aparte begrensde taak.
|
||
|
||
## 3. Minimumvelden
|
||
|
||
Een jobdraft is verwerkbaar wanneer minstens aanwezig:
|
||
|
||
- titel;
|
||
- HTTP(S)-URL of stabiele externe identiteit.
|
||
|
||
Employer, locatie en beschrijving mogen tijdelijk ontbreken, maar verlagen confidence. Een generieke pagina zonder herkenbare titel wordt niet als job opgeslagen.
|
||
|
||
## 4. Confidence
|
||
|
||
Richtwaarden:
|
||
|
||
| Methode | Baseline |
|
||
|---|---:|
|
||
| JSON-LD geldig individueel JobPosting | 0,95 |
|
||
| gespecialiseerde publieke ATS-adapter | 0,90 |
|
||
| RSS/Atom | 0,82 |
|
||
| vacaturemailanchor | 0,65–0,75 |
|
||
| generieke HTMLselectors | 0,45–0,75 |
|
||
|
||
Fieldconfidence en extractionconfidence zijn gescheiden. Een perfecte titel maakt een ontbrekende werkgever niet betrouwbaar.
|
||
|
||
## 5. Nieuwe adapter toevoegen
|
||
|
||
1. Maak `apps/sources/adapters/<naam>.py`.
|
||
2. Geef vaste parser key en semverachtige version.
|
||
3. Parse alleen de aangeleverde content.
|
||
4. Maak geanonimiseerde fixture onder `fixtures/`.
|
||
5. Test happy path, ontbrekende velden, gewijzigd markup en malafide HTML.
|
||
6. Registreer uitsluitend voor een expliciete source/parserdetectie.
|
||
7. Documenteer bronbeleid, rate limit en canonical URL-gedrag.
|
||
8. Voeg traceability toe.
|
||
|
||
## 6. ATS-strategie
|
||
|
||
Gespecialiseerde adapters mogen publieke HTML of publieke, documenteerbare endpoints gebruiken wanneer:
|
||
|
||
- geen login/token nodig is;
|
||
- bronpolicy `allow` is;
|
||
- gebruiksvoorwaarden/robots zijn beoordeeld;
|
||
- het endpoint rechtstreeks vacatures van de werkgever publiceert;
|
||
- rate limiting en identifiers stabiel zijn;
|
||
- fallback naar HTML mogelijk is.
|
||
|
||
Een endpoint mag technisch JSON zijn zonder een commerciële platform-API-integratie te vormen. De policybeslissing blijft per bron vereist.
|
||
|
||
## 7. Bronpromotie
|
||
|
||
```text
|
||
candidate
|
||
-> policy/robots/terms review
|
||
-> trial (kleine frequentie, parserconfidence meten)
|
||
-> active (voldoende succesvolle runs)
|
||
-> quarantined (policy, security of herhaalde parsefout)
|
||
-> trial/active na expliciet herstel
|
||
```
|
||
|
||
Automatische promotie naar `active` vereist minimaal:
|
||
|
||
- meerdere succesvolle runs;
|
||
- geen private/deny redirect;
|
||
- voldoende extractieconfidence;
|
||
- geen onverwachte volumepiek;
|
||
- policy nog geldig.
|
||
|
||
Juridische voorwaarden mogen niet door een taalmodel als definitief toegestaan worden verklaard.
|
||
|
||
## 8. Rate limiting
|
||
|
||
Per eTLD+1/domein:
|
||
|
||
- concurrency standaard 1;
|
||
- minimum interval standaard 30 seconden;
|
||
- respecteer `Retry-After`;
|
||
- conditionele GET met ETag/Last-Modified;
|
||
- exponentiële backoff bij fout;
|
||
- geen retry op policyblokkade;
|
||
- adaptieve lagere frequentie bij stabiele, zelden wijzigende bron.
|
||
|
||
## 9. Parserdrift
|
||
|
||
Bronhealth detecteert:
|
||
|
||
- leeg resultaat waar eerder vacatures waren;
|
||
- sterke daling in velddichtheid/confidence;
|
||
- nieuwe foutstatus/contenttype;
|
||
- wijziging in jobvolume;
|
||
- ontbrekende title/URL;
|
||
- veel nieuwe canonical keys door URLtrackingwijziging.
|
||
|
||
Automatisch herstel mag veilige selectorvarianten proberen, maar nieuwe logica moet eerst op opgeslagen fixture/snapshot draaien. Geen live agressieve exploratie.
|
||
|
||
## Publieke ATS-providers (VR-106)
|
||
|
||
| Provider | Hostherkenning | Parserbenadering | Opmerking | Rate limit (advies) |
|
||
|---|---|---|---|---|
|
||
| Greenhouse | `*.greenhouse.io`, `*.boards.greenhouse.io` | gespecialiseerd endpoint/JSON + detailstructuren | gebruik alleen publieke boardpagina's/feeds, geen private/persoonlijke endpoints | 15 requests/min met conditional request indien beschikbaar |
|
||
| Lever | `jobs.lever.co` | gespecialiseerd JSON/HTML listing+detail | alleen publieke postings, geen recruiter login flow | 60 requests/min per domein |
|
||
| Recruitee | `*.recruitee.com` | gespecialiseerd listing+detail parser | parse alleen door werkgever geïntendeerde publieke vacature-URL’s | 60 requests/min per domein |
|
||
| SmartRecruiters | `*.smartrecruiters.com` | gespecialiseerd listing+detail parser | geen interne API-auth nodig; stop bij CAPTCHA/anti-bot | 30 requests/min per domein |
|
||
| Workable | `apply.workable.com` | gespecialiseerd listing+detail parser | alleen publieke vacaturepagina/JSON; geen private endpoints | 30 requests/min per domein |
|
||
|
||
Gedeelde ATS-hosts zijn geen bronidentiteit: meerdere werkgevers mogen hetzelfde providerdomein gebruiken zolang iedere bron een unieke publieke `base_url` heeft. De scheduler blijft per origin coördineren, zodat deze extra bronrecords de providerlimiet niet omzeilen.
|
||
|
||
Voor Corda Campus is een begrensde regionale listingadapter beschikbaar. Die leest uitsluitend de expliciete `/jobs/`-kaarten, bewaart de vermelde werkgever en weigert iedere joblink buiten `cordacampus.com`. Vacaturedetail en sollicitatie-URL's worden niet automatisch gevolgd.
|
||
|
||
Voor elke provider-adapter:
|
||
- `supports()` kijkt eerst op host/marker om andere pagina's niet te matchen;
|
||
- listingpayloads produceren kandidaten zonder fetch van detail;
|
||
- detailpayloads vullen een enkele vacaturestructuur;
|
||
- elke veldafklaring krijgt veld-evidence met method-labels en confidence.
|
||
|
||
## Regionale Mol-adapter (VR-126)
|
||
|
||
`MolRegionEmployerAdapter` verwerkt uitsluitend vijf vooraf gereviewde HTTPS-lijstroutes rond postcode 2400: SCK CEN, Cipal Schaubroeck, VanRoey, NTX/Netropolix en Thomas More. De adapter doet geen netwerkrequests en accepteert alleen same-host vacaturelinks. Cipal, NTX en Thomas More vereisen een expliciete lokale marker; bij Thomas More wordt bovendien de vaste publieke werkgever-GUID gecontroleerd. VanRoey-detailpaden zijn door robots uitgesloten en worden daarom nooit door VacatureRadar gefetcht; alleen de toegestane overzichtspagina dient als bron.
|
||
|
||
## Kempen-werkgeversadapter (VR-201)
|
||
|
||
`KempenEmployerAdapter` voegt zeven gereviewde werkgeversroutes toe voor Ziekenhuis Geel, lokaal bestuur Geel, Stad Turnhout, Group Renotec, Ravago, Sanofi en DAF Trucks Westerlo. Iedere route heeft een vaste HTTPS-host en een toegestaan lijstpad; vacaturelinks moeten same-host zijn. Voor gedeelde of landelijke lijsten blijft alleen een kaart met de expliciete gereviewde vestiging over. Gemeente Mol wordt via de officiële same-host RSS-feed verwerkt. Interessante werkgevers zonder stabiele fail-closed parser blijven als niet-scanbare waaklijstbron zichtbaar.
|