Files
VacatureRadar/docs/api/INTERNAL_CONTRACTS.md
T
Jens b8091e59bd
deploy / deploy (push) Canceled after 0s
Initial deploy setup
2026-07-21 14:00:00 +02:00

174 lines
6.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Interne contracten
Dit document beschrijft de stabiele grenzen tussen domeinmodules. Het is geen belofte van een externe REST-API. De eerste release gebruikt Django-views, formulieren en servicefuncties; daardoor blijft de onderhoudslast laag en kan later zonder herschrijven een JSON-laag boven dezelfde services worden geplaatst.
## Contractprincipes
1. **Adapters lezen, services beslissen.** Een bronadapter maakt alleen een `ExtractionResult`; hij schrijft niet naar de database en voert geen scoring uit.
2. **Onbetrouwbare data blijft data.** HTML, e-mail, JSON-LD en modeluitvoer mogen nooit instructies of code worden.
3. **Persistente writes zijn atomair en idempotent.** Herhaalde import van dezelfde broninhoud maakt geen tweede vacature aan.
4. **Orchestrators bevatten geen productregels.** Celery-taken plannen en registreren; domeinservices normaliseren, dedupliceren en scoren.
5. **AI is optioneel.** Geen core-contract mag een taalmodel vereisen om geldige output te leveren.
## Bronadapter
Implementatie: `apps/sources/adapters/base.py`.
```python
class SourceAdapter(Protocol):
parser_key: str
parser_version: str
def extract(self, content: str, *, url: str) -> ExtractionResult: ...
```
### Invoer
- `content`: reeds opgehaalde Unicode-inhoud; netwerktoegang vindt vóór de adapter plaats.
- `url`: gevalideerde bron- of document-URL, uitsluitend voor context en relatieve URL-resolutie.
### Uitvoer
`ExtractionResult` bevat:
- `jobs`: nul of meer `ExtractedJob`-objecten;
- `parser_key` en `parser_version`: reproduceerbare parseridentiteit;
- `confidence`: globaal getal tussen `0.0` en `1.0`;
- `warnings`: niet-fatale, korte, machineveilige waarschuwingen.
Een `ExtractedJob` gebruikt zo veel mogelijk brongetrouwe waarden. Normalisatie gebeurt pas in `normalize_extracted_job`. Een adapter moet:
- ontbrekende velden leeg laten in plaats van ze te verzinnen;
- iedere URL absoluut maken;
- beschrijving als bron-HTML én/of platte tekst leveren;
- veldbewijs toevoegen wanneer een waarde heuristisch is gevonden;
- nooit secrets, cookies, headers of volledige mailmetadata in `raw` opnemen;
- geen exception gebruiken voor een geldig document met nul vacatures.
## Fetcher
Implementatie: `apps/sources/services/fetcher.py` en `url_security.py`.
```python
fetch_url(
url: str,
*,
source: Source | None = None,
conditional_headers: dict[str, str] | None = None,
) -> FetchedDocument
```
Voorwaarden vóór ieder request en na iedere redirect:
- schema is `http` of `https`;
- host is niet leeg, localhost, link-local, multicast, private of anderszins speciaal;
- poort is 80/443, tenzij expliciet toegestaan;
- domein is niet op de centrale platformdenylist;
- bronstatus en bronbeleid laten toegang toe;
- response blijft binnen tijd-, redirect- en bytegrenzen.
`FetchedDocument` bevat de aangevraagde en uiteindelijke URL, status, gefilterde headers, bytes, tekst en SHA-256. Callers mogen de responsebody niet loggen.
Machineleesbare foutcategorieën:
- `PolicyBlockedError`: bron/domein is niet toegestaan; niet blind retrien;
- `ContentRejectedError`: type, grootte of response is onbruikbaar;
- `FetchError`: tijdelijke netwerk- of HTTP-fout die begrensd mag worden herhaald;
- `UnsafeUrlError`: URL faalt SSRF-validatie vóór netwerktoegang.
## Normalisatie
Implementatie: `apps/jobs/services/normalization.py`.
```python
normalize_extracted_job(extracted: ExtractedJob) -> CanonicalJobDraft
```
De functie:
- saneert HTML vóór opslag/rendering;
- normaliseert titel, contracttypen, werkplek en taal;
- canonicaliseert de URL en verwijdert gekende trackingparameters;
- berekent een stabiele `canonical_key` en `content_hash`;
- parseert datums conservatief en timezone-aware;
- behoudt bronwaarden in `raw` en `evidence` voor uitlegbaarheid.
Een `CanonicalJobDraft` is een immutable overdrachtsobject. Het kent geen database-ID en bevat geen score.
## Persistente pipeline
Implementatie: `apps/jobs/services/pipeline.py`.
```python
process_raw_document(document: RawDocument) -> dict[str, int | str | list[str]]
```
Transactionele volgorde:
1. kies adapter via registry;
2. extracteer jobs en parsermetadata;
3. normaliseer iedere job;
4. zoek een bestaand canoniek cluster;
5. maak of werk werkgever, vacature en bronalias bij;
6. leg veldherkomst en versie vast;
7. score tegen ieder actief zoekprofiel.
De resultaatmapping bevat minimaal `extracted`, `created`, `updated`, `duplicates`, `parser` en `warnings`. De tellers moeten deterministisch blijven bij een replay van hetzelfde document.
## Deduplicatie
Implementatie: `apps/jobs/services/dedupe.py`.
```python
find_existing_job(draft: CanonicalJobDraft) -> DedupeDecision
```
`DedupeDecision` bevat:
- `job`: bestaand canoniek object of `None`;
- `reason`: machineleesbare reden, bijvoorbeeld `exact_canonical_key`, `exact_external_id` of `fuzzy_*`;
- `confidence`: getal tussen `0.0` en `1.0`.
Een fuzzy match mag alleen plaatsvinden wanneer werkgever, titel, locatie en periode voldoende bewijs leveren. Een rechtstreekse werkgeversbron krijgt canonieke voorkeur boven een platform- of recruiterlink; bronaliassen blijven behouden.
## Scoring
Implementatie: `apps/jobs/services/scoring.py`.
```python
score_job(job: JobPosting, profile: SearchProfile) -> ScoreResult
score_and_save(job: JobPosting, profile: SearchProfile) -> ScoreRun
```
`ScoreResult` levert:
- `score`: begrensd op 0100;
- `hard_exclusions`: stabiele regelcodes;
- `explanation`: korte positieve en negatieve factoren;
- `feature_values`: auditbare, niet-persoonsgevoelige featurewaarden;
- `model_version`: versie van regels/gewichten.
Harde uitsluitingen worden nooit door een hoge zachte score opgeheven. AI-output mag hoogstens aanvullende kenmerken leveren nadat schema- en confidencevalidatie zijn geslaagd.
## Feedback
Implementatie: `apps/jobs/services/feedback.py`.
Toegestane acties zijn `interesting`, `save`, `hide` en `applied`. Iedere mutatie is user-scoped en CSRF-beschermd. Feedback mag gewichten slechts binnen de in het profiel vastgelegde grenzen aanpassen. De gebruiker moet de wijziging kunnen resetten en een vacature handmatig terugvinden, ook wanneer ze is verborgen.
## Notificaties
Implementatie: `apps/notifications/services.py`.
Een digest wordt eerst als `DigestOutbox` aangemaakt en daarna verzonden. Uniciteit per profiel en digestdatum voorkomt dubbele mails. Verzending verandert alleen de outboxstatus; het opnieuw genereren van de selectie moet reproduceerbaar zijn uit opgeslagen `ScoreRun`-records.
## Nieuwe service toevoegen
Een nieuwe interne service is pas een stabiel contract wanneer:
- publieke types en foutcategorieën zijn gedocumenteerd;
- unit- en integratietests succes, lege invoer en negatieve paden dekken;
- netwerk- of secretvereisten injecteerbaar zijn;
- er geen databasewrite in parser- of pure normalisatielogica zit;
- traceability en backlog zijn bijgewerkt.