Initial deploy setup
deploy / deploy (push) Canceled after 0s

This commit is contained in:
Jens
2026-07-21 14:00:00 +02:00
commit b8091e59bd
285 changed files with 27854 additions and 0 deletions
+50
View File
@@ -0,0 +1,50 @@
# Systeemacceptatiecriteria
## A. Gebruik met minimale handelingen
- **AC-001** Een nieuwe installatie kan met één bootstrapcommando lokaal starten.
- **AC-002** Na onboarding kan de gebruiker zonder bronhandwerk het dashboard en de digest gebruiken.
- **AC-003** De normale vacatureactie vereist maximaal één klik plus optionele reden/notitie.
- **AC-004** Geen standaardflow vraagt een platformwachtwoord.
## B. Verzameling
- **AC-010** Dezelfde fixture tweemaal verwerken creëert één canonieke vacature en één bronalias per identieke bronidentiteit.
- **AC-011** Een private/loopback/link-local URL, embedded credential of niet-standaardpoort wordt geblokkeerd.
- **AC-012** Een redirect naar een denylist- of niet-publiek doel wordt geblokkeerd vóór de tweede request.
- **AC-013** Een niet-toegestaan contenttype en te groot document worden afgewezen.
- **AC-014** Een Message-ID tweemaal importeren creëert één e-mailrecord.
- **AC-015** JSON-LD, generieke HTML, RSS en e-mailfixtures leveren geldige `ExtractionResult`-objecten.
## C. Data en matching
- **AC-020** `m/v/x`, whitespace, contracttypes en datums worden canoniek genormaliseerd.
- **AC-021** Exacte ID/URL/key wint van fuzzy matching.
- **AC-022** Een uitgesloten titel, regio, contractvorm, afstand of verplichte skill levert recommendation `hidden` met reden.
- **AC-023** Een niet-uitgesloten vacature bewaart alle scorecomponenten en evidence.
- **AC-024** Ontbrekende afstand levert een concern/confidence-effect maar geen verzonnen kilometerwaarde.
- **AC-025** AI-uitval verandert de deterministische kern niet.
## D. Meldingen en lifecycle
- **AC-030** Een digestvenster maakt maximaal één outboxrecord per profiel/dag.
- **AC-031** Een verborgen vacature verschijnt niet in de digest.
- **AC-032** Een verzonden outbox wordt bij herhaling niet opnieuw verstuurd.
- **AC-033** `valid_through` in het verleden markeert een actieve vacature verlopen.
- **AC-034** Lang niet geziene vacatures gaan eerst naar onzeker en daarna verwijderd.
## E. Sollicitaties
- **AC-040** Feedback `applied` creëert één user/job-dossier met snapshot en opvolgdatum.
- **AC-041** Een bestaande voorbereiding wordt bij `applied` bijgewerkt zonder duplicaatdossier.
- **AC-042** Een gebruiker kan geen dossier van een andere gebruiker openen of wijzigen.
- **AC-043** Er bestaat geen endpoint of task die een sollicitatie extern verstuurt.
## F. Beheer en kwaliteit
- **AC-050** `/health/live/` antwoordt zonder databaseafhankelijkheid; `/health/ready/` controleert databaseconnectiviteit.
- **AC-051** `./scripts/codex_verify.sh` slaagt op een schone checkout na bootstrap.
- **AC-052** De standaardtestset doet geen live internetrequests.
- **AC-053** Codedekking blijft minimaal 70% en securitykritieke services hebben negatieve tests.
- **AC-054** `BACKLOG.yaml` is valide, uniek en dependency-consistent.
- **AC-055** Back-up/restorecommando's weigeren onveilige of ontbrekende configuratie met duidelijke fout.
+164
View File
@@ -0,0 +1,164 @@
# Product Requirements Document — VacatureRadar
## 1. Productvisie
VacatureRadar vermindert de tijd en mentale belasting van persoonlijk vacaturezoeken. Na een eenmalige configuratie verzamelt het systeem zelfstandig vacatures uit toegestane bronnen, verwijdert het duplicaten en duidelijke mismatches, verklaart het waarom een vacature relevant is en helpt het de opvolging structureren.
Het product is geen massale scraper en geen automatische sollicitatiebot. Het is een persoonlijke vacancy-intelligencehub met menselijke eindcontrole.
## 2. Doelgebruiker
Primaire gebruiker: één persoon in België die gericht werk zoekt en slechts enkele keren per week de beste resultaten wil bekijken. De implementatie blijft technisch user-scoped zodat later meerdere geïsoleerde gebruikers mogelijk zijn, maar multi-tenancy, billing en teams zijn geen MVP-doel.
## 3. Succesdefinitie
Het product is succesvol wanneer:
- de gebruiker na onboarding geen zoekopdrachten of broncontroles handmatig hoeft te herhalen;
- nieuwe passende vacatures binnen 24 uur zichtbaar zijn voor dagelijks gecontroleerde bronnen;
- dubbele vermeldingen als één canonieke vacature verschijnen;
- iedere aanbeveling een begrijpelijke score, bewijs en aandachtspunten bevat;
- harde uitsluitregels nooit door AI worden genegeerd;
- bronfalen zichtbaar is zonder dat één fout de rest van de pipeline stopt;
- normale bediening beperkt blijft tot bekijken, interessant/bewaren/verbergen/gesolliciteerd;
- geen sollicitatie zonder bewuste gebruikersactie wordt verstuurd.
## 4. Scope
### 4.1 In scope
- zoekprofielen met titels, skills, regio, afstand, werkvorm en contract;
- harde uitsluitingen en gewogen voorkeuren;
- werkgeverspagina's, publieke ATS-pagina's, RSS/Atom, sitemaps en vacaturemails;
- handmatige/browserimport als veilige aanvulling;
- bronbeleid, rate limiting, health en quarantaine;
- extractie, normalisatie, taalherkenning en provenance;
- deduplicatie en canonieke bronselectie;
- uitlegbare deterministische score;
- optionele lokale AI voor begrensde classificatie/samenvatting;
- dagelijkse digest en sluitings-/opvolgherinneringen;
- sollicitatiedossiers, notities en statussen;
- self-hosting op Unraid via containers;
- back-up, herstel, tests en observability.
### 4.2 Buiten scope
- inloggen op of geautomatiseerd bedienen van vacatureplatformen;
- CAPTCHA-, paywall- of blokkadeomzeiling;
- massaal herpubliceren van vacaturedatabanken;
- automatisch invullen/versturen van sollicitaties;
- recruitment-CRM, multi-tenant SaaS of werkgeverstoegang;
- garanties dat letterlijk iedere vacature wordt gevonden;
- juridisch advies over een bron;
- een taalmodel dat definitief bepaalt of iets wordt verwijderd.
## 5. Functionele requirements
De IDs zijn stabiel en worden gebruikt in tests en traceability.
### Profiel en onboarding
- **PR-001** — De gebruiker kan minstens één zoekprofiel aanmaken en één actief profiel kiezen.
- **PR-002** — Het profiel ondersteunt gewenste/uitgesloten titels en skills, regio's, maximale afstand, contracttypes, werkvormen, drempels en digesttijd.
- **PR-003** — Iedere betekenisvolle profielwijziging krijgt een versioned snapshot.
- **PR-004** — Feedbackleren is opt-in/uitschakelbaar, begrensd en wijzigt geen harde regels.
- **PR-005** — De onboarding levert bruikbare defaults zonder dat AI of externe API's nodig zijn.
### Bronnen en collectie
- **PR-010** — Iedere bron heeft type, domein, status, policy, parser, planning en healthmetadata.
- **PR-011** — Alleen expliciet toegestane bronnen worden automatisch opgehaald.
- **PR-012** — Denylistplatformen worden nooit rechtstreeks gecrawld; vacaturemails mogen wel worden verwerkt.
- **PR-013** — Iedere URL en redirect wordt vóór toegang op schema, credentials, poort, DNS en IP-bereik gevalideerd.
- **PR-014** — Fetches hanteren timeouts, contenttype- en groottelimieten en conditionele headers.
- **PR-015** — Bronnen worden idempotent gepland met begrensde retries en backoff.
- **PR-016** — Vacaturemails worden idempotent op Message-ID of contenthash verwerkt.
- **PR-017** — Nieuwe kandidaatbronnen kunnen worden ontdekt, maar gaan pas na policychecks naar trial/active.
- **PR-018** — Een bron met herhaald falen wordt gedegradeerd of in quarantaine geplaatst zonder andere bronnen te stoppen.
### Extractie en data
- **PR-020** — JSON-LD `JobPosting` is de voorkeursparser voor individuele vacaturepagina's.
- **PR-021** — HTML, RSS/Atom en e-mail hebben deterministische fallbackadapters.
- **PR-022** — Iedere parser levert een gemeenschappelijk extractiecontract en confidence.
- **PR-023** — Externe HTML wordt gesanitized vóór opslag/rendering.
- **PR-024** — Canonieke velden omvatten titel, werkgever, URL, locatie, tekst, contract, werkvorm, data, skills en bronbewijs.
- **PR-025** — Wijzigingen aan een bestaande vacature bewaren een versie/snapshot.
- **PR-026** — Ruwe documenten hebben een korte instelbare retentie en kunnen worden gequarantained.
- **PR-027** — Veldherkomst is zichtbaar voor audit en debugging.
### Deduplicatie
- **PR-030** — Exacte external ID, canonieke URL en canonieke sleutel hebben voorrang.
- **PR-031** — Fuzzy matching gebruikt titel, werkgever, locatie en inhoud met conservatieve drempel.
- **PR-032** — De directe werkgeversbron krijgt waar mogelijk canonieke voorkeur boven recruiter/platformalias.
- **PR-033** — Deduplicatie is herhaalbaar en creëert geen extra canonieke vacature bij dezelfde input.
### Matching en ranking
- **PR-040** — Harde regels worden vóór scoring toegepast en zijn uitlegbaar.
- **PR-041** — Scorecomponenten omvatten inhoud, skills, locatie, voorwaarden, werkgever, senioriteit en voorkeuren.
- **PR-042** — Onbekende data verlaagt confidence en wordt niet automatisch als negatief feit behandeld.
- **PR-043** — Iedere score bewaart componenten, positives, concerns, uitsluitingen en evidence.
- **PR-044** — Profielversie en model-/promptversie worden aan een score gekoppeld.
- **PR-045** — AI is optioneel, temperatuurarm/gestructureerd en mag alleen aanvullende features/samenvatting leveren.
- **PR-046** — Prompt injection in vacaturetekst mag geen tool- of beleidsactie veroorzaken.
### Gebruik en opvolging
- **PR-050** — Het dashboard toont een kleine lijst van de beste actuele niet-uitgesloten vacatures.
- **PR-051** — De detailpagina toont score, redenen, bronaliassen, provenance en vacaturestatus.
- **PR-052** — De gebruiker kan interessant, bewaren, verbergen en gesolliciteerd registreren.
- **PR-053** — Gesolliciteerd maakt of actualiseert een sollicitatiedossier met snapshot en opvolgdatum.
- **PR-054** — Een dagelijkse digest bevat sterke/mogelijke matches en verzendt idempotent via outbox.
- **PR-055** — Verlopen, onzekere en verwijderde vacatures worden automatisch gemarkeerd.
- **PR-056** — Geen enkele gebruikersactie verstuurt automatisch een sollicitatie naar een werkgever.
### Beheer
- **PR-060** — Liveness/readiness endpoints zijn beschikbaar.
- **PR-061** — Bronruns bewaren aantallen, HTTP-status en foutcategorie.
- **PR-062** — Back-up en restore zijn scriptbaar en gedocumenteerd.
- **PR-063** — De app kan volledig lokaal zonder externe AI draaien.
- **PR-064** — Configuratie komt uit environmentvariables; secrets staan niet in de repository.
## 6. Niet-functionele requirements
- **NFR-001 Beveiliging** — Fail-closed voor bronbeleid en URL-validatie.
- **NFR-002 Privacy** — Dataminimalisatie, korte ruwe retentie en eenvoudige verwijderbaarheid.
- **NFR-003 Betrouwbaarheid** — Imports en meldingen zijn idempotent; fouten blijven per bron/taak geïsoleerd.
- **NFR-004 Uitlegbaarheid** — Iedere zichtbare beslissing heeft bewijs en een onderscheid tussen feit, inferentie en onbekend.
- **NFR-005 Onderhoudbaarheid** — Modulaire monoliet, servicescheiding, versieerbare adapters en fixtures.
- **NFR-006 Testbaarheid** — Geen live internet nodig voor de standaardtests; minimaal 70% branch-aware dekking.
- **NFR-007 Performance** — Dashboard p95 onder 1 seconde bij 25.000 vacatures op aanbevolen hardware; bronwerk asynchroon.
- **NFR-008 Toegankelijkheid** — Toetsenbordbruikbaar, semantische HTML, zichtbare focus, voldoende contrast en reduced-motionrespect.
- **NFR-009 Portabiliteit** — Lokale Pythonmodus en containerdeployment op x86_64; geen cloudvendorlock-in.
- **NFR-010 Observability** — Gestructureerde logs, healthstatus, bronmetrics en herstelbare foutcontext zonder secrets.
## 7. Productdefaults
- taal: `nl-BE`;
- tijdzone: `Europe/Brussels`;
- dagelijkse digest: 07:30 lokale tijd;
- ruwe-documentretentie: 7 dagen;
- standaard maximumafstand: 45 km;
- aanbevelingsdrempel: 65;
- topmatchdrempel: 90;
- onbekende bronpolicy: `review`;
- AI: uit;
- browserautomatisering: uit;
- automatisch solliciteren: niet beschikbaar.
## 8. Productmetrics
Te meten zonder tracking naar derden:
- nieuwe canonieke vacatures per bron/dag;
- duplicaatratio;
- parserconfidence en velddichtheid;
- aandeel vacatures per recommendation;
- hide/interessant/sollicitatiefeedback per scoreband;
- tijd van publicatie/first-seen tot digest;
- bronfoutpercentage en herstelduur;
- gemiste vacatures in een handmatige benchmarksteekproef;
- false-positive harde uitsluitingen via gebruikersundo.
+41
View File
@@ -0,0 +1,41 @@
# Scope, grenzen en aannames
## Kernbelofte
VacatureRadar levert een zo compleet mogelijke, persoonlijke selectie binnen toegestane en technisch bereikbare bronnen. Het belooft niet letterlijk alle vacatures te vinden.
## Brondekking
Voorkeursvolgorde:
1. oorspronkelijke werkgeverspagina;
2. publieke ATS-carrièrepagina;
3. gestructureerde RSS/Atom/sitemap;
4. vacaturemail met link;
5. handmatige/browserimport;
6. platformalias uitsluitend voor herkomst en ontdekking, niet voor direct crawlen wanneer denylist/policy dat blokkeert.
## Aannames
- één beheerder/eindgebruiker per installatie;
- server heeft betrouwbare klok en tijdzoneconfiguratie;
- DNS en outbound internet zijn beschikbaar voor goedgekeurde bronnen;
- mailbox wordt specifiek voor vacaturealerts gebruikt;
- de gebruiker beoordeelt live bronvoorwaarden en sollicitaties;
- Unraid-opslag is persistent en wordt extern geback-upt;
- lokale AI is optioneel en niet noodzakelijk voor kernfunctionaliteit.
## Niet-doelen
- stealthscraping, proxyrotatie of anti-botomzeiling;
- het nabouwen van LinkedIn/Indeed zoekresultaten;
- persoonlijkheids- of discriminatoire profiling;
- autonome carrièrebeslissingen;
- cv-ranking voor werkgevers;
- geautomatiseerde massacommunicatie;
- bewaren van complete mailboxen of onnodige persoonsgegevens;
- generieke workflowengine of microserviceplatform.
## Productrisico
De grootste productrisico's zijn bronverandering, gemiste vacatures, foutpositieve matches en onderhoudslast. Daarom kiest het ontwerp voor adapters met fixtures, confidence, provenance, bronhealth, conservatieve dedupe en een dagelijkse menselijke review van alleen de beste resultaten.
+112
View File
@@ -0,0 +1,112 @@
# User stories en primaire flows
## Persona
De gebruiker wil geen dagelijkse zoekmachine bedienen. Hij wil enkele sterke vacatures zien, begrijpen waarom ze passen en snel opvolgen wat hij ermee deed.
## Onboarding
### US-001 — Zoekprofiel instellen
Als gebruiker wil ik mijn gewenste rollen, skills, regio en harde uitsluitingen één keer instellen zodat de tool daarna zelfstandig zoekt.
Acceptatie:
- defaults zijn direct bruikbaar;
- titel/skillvelden ondersteunen meerdere regels;
- locatie kan eerst zonder coördinaten worden opgeslagen;
- ongeldige drempels of coördinaten geven veldgerichte feedback;
- opslaan maakt een profielrevisie.
### US-002 — Veilige bronkanalen kiezen
Als gebruiker wil ik vacaturemails en publieke werkgeversbronnen kunnen activeren zonder platformlogins aan de tool te geven.
Acceptatie:
- mailboxconfiguratie staat alleen in secrets/environment;
- denylistlinks uit mails worden als aliassen opgeslagen maar niet door de fetcher bezocht;
- onbekende bron blijft in review.
## Dagelijkse lus
### US-010 — Beste matches bekijken
Als gebruiker wil ik bij het openen maximaal een overzichtelijke selectie zien zodat ik niet opnieuw honderden resultaten hoef te filteren.
Acceptatie:
- actieve, niet-hard-uitgesloten vacatures eerst;
- één kaart per canonieke vacature;
- score, werkgever, locatie, werkvorm en kernreden zichtbaar;
- sterke en mogelijke matches duidelijk onderscheiden.
### US-011 — Begrijpen waarom iets past
Als gebruiker wil ik bewijs, pluspunten en aandachtspunten zien zodat ik de score kan vertrouwen en corrigeren.
Acceptatie:
- scorecomponenten en confidence zijn zichtbaar;
- onbekende afstand/salaris wordt als onbekend gemeld;
- herkomstlinks blijven bereikbaar;
- gesanitized vacaturetekst bevat geen uitvoerbare scripts/forms.
### US-012 — Snelle feedback
Als gebruiker wil ik met één actie interessant, bewaren, verbergen of gesolliciteerd registreren.
Acceptatie:
- actie is CSRF-beschermd en user-scoped;
- verbergen verwijdert de vacature uit toekomstige digests;
- gesolliciteerd maakt een dossier en bewaart een snapshot;
- geen actie verstuurt iets naar de werkgever.
## Meldingen
### US-020 — Dagelijkse digest
Als gebruiker wil ik één samenvatting ontvangen zodat ik de interface niet voortdurend hoef te controleren.
Acceptatie:
- één outboxrecord per profiel/dag;
- herhaald uitvoeren verzendt niet dubbel;
- sterke en mogelijke matches worden geteld;
- zonder ontvanger wordt veilig overgeslagen;
- verzendfouten blijven retrybaar en zichtbaar.
### US-021 — Deadline en opvolging
Als gebruiker wil ik herinnerd worden aan sluitings- en opvolgdata zodat kansen niet ongemerkt verlopen.
Acceptatie:
- reminders respecteren quiet hours;
- verlopen vacature wordt niet als nieuwe match gemeld;
- herinnering bevat bronlink en dossierstatus;
- gebruiker kan herinnering uitschakelen.
## Beheer en herstel
### US-030 — Bronproblemen zien
Als gebruiker wil ik zien welke bronnen gezond, vertraagd of geblokkeerd zijn zonder logs te lezen.
Acceptatie:
- status, laatste succes/fout, volgende run en reden zichtbaar;
- handmatige retry respecteert denybeleid;
- herhaalde parsefout degradeert bron, niet hele app.
### US-031 — Herstellen na storing
Als beheerder wil ik back-up en restore kunnen uitvoeren zodat profiel, vacatures en sollicitatiedossiers niet verloren gaan.
Acceptatie:
- database- en mediaback-up zijn apart;
- restore vereist expliciete bevestiging;
- hersteltest is gedocumenteerd;
- secrets worden niet in gewone databack-up opgenomen.