Methodiek & Validatie

Een Nederlandstalige gids over hoe onze multi-horizon ARDL-modellen worden samengesteld, welke kruisvalidering daarop draait en hoe je de prestaties (MAE, MAPE en trendscore bij |Δ| ≥ €2) moet interpreteren.

Laatst gevalideerde run

Manifest: service_outputs/manifest.json

Brondata: Landbouw + Boerderij, CBS, Open-Meteo, Gemini-nieuwsfeatures.

Hoe trainen we de modellen?

  • Gegevensopbouw · We laden canonieke weekprijzen uit de gecombineerde Landbouw + Boerderij-feed, synchroniseren ze via `_canonicalize_weekly_prices` en koppelen externe bronnen (CBS diesel, Open-Meteo, Boerderij-sentiment en Gemini-nieuwsfeatures).
  • Feature-engineering · Per horizon gebruiken we de samengestelde ‘mixed v4’-sets (diesel MA's, kwaliteit/bewaarsentiment, verwerkingsbenutting, cumulatieve groeigraden, week-of-year en feestdag-afstanden) met prijs-lags (0,1,8) en exogene lags (0,1,8,12).
  • Modeltraining · `train_and_predict_production.py` bouwt één consistent ARDL-datasetpakket, draait rolling-origin CV per horizon en traint vervolgens `GradientBoostingRegressor` op alle complete rijen met dezelfde scaler/pipeline als in productie.
  • Export & Controle · Elke horizon levert `model.joblib`, `scaler.joblib`, CV-predictions, live voorspellingen, feature-insights en een manifest zodat de dashboardservice exact dezelfde artefacten kan renderen.

Bronnen in één oogopslag: Landbouw + Boerderij weekprijzen, CBS pompprijzen voor diesel, Open-Meteo weerarchieven, Boerderij-sentiment over verwerking en bewaarbaarheid, en wekelijkse nieuwsfeatures die we met Gemini samenvatten. Alles wordt via dataset_factory.py naar één kalender gebracht voordat er ook maar één model wordt getraind.

Van ruwe data naar modelklare dataset

  1. Ontsluiten: APIs en scrapers leveren dagelijkse of wekelijkse reeksen. We bewaren elke bron in zijn eigen map onder src/sources zodat updates traceerbaar blijven.
  2. Kalenderen: _canonicalize_weekly_prices zet álle weekreeksen op dezelfde ISO-week (maandag) en dwingt unieke sleutel week_start af. Hierdoor missen we nooit een horizon doordat er een RVO-week “ontbreekt”.
  3. Lags en gaps: Via _calendar_lag_merge voegen we prijs- en exogene lags toe zónder shift-trucs. Korte gaten (≤16 weken) worden forward-filled, langere gaten blijven NaN en worden uitgesloten zodat we geen verzonnen data creëren.
  4. CV-pakketten: create_experiment_datasets levert meteen AR- én ARDL-matrices plus metadata (laglijsten, aantal weeks). Deze bestanden gaan rechtstreeks de training, de productie-run en het dashboard in.

Het gevolg: dezelfde dataset voedt zowel onderzoek, productie als visualisatie. Geen “it worked on my notebook”-effecten.

Hoe ziet de modellering er exact uit?

  • Modelbibliotheek: Naast GradientBoosting trainen we tijdens onderzoek ook RandomForest, Ridge en LinearRegression varianten (de code staat in predictions_multiple_horizons_ARDL.py). In productie houden we het bij GradientBoosting omdat die het beste presteert en robuust is voor kleine feature-sets.
  • Train/test scheiding: Rolling-origin CV (expanderend venster, 4-weekse stappen) simuleert een realistische live omgeving. Elke fold levert één voorspelling per horizon, zodat we statistische toetsen (Diebold-Mariano, Harvey-Leybourne-Newbold) kunnen uitvoeren.
  • Bewaren van modellen: De recepten voor de laatste fold (scalers, pipelines, modelobjecten) worden opgeslagen in production_models/<timestamp>. Het dashboard gebruikt exact dezelfde bestanden voor zowel historische als actuele voorspellingen.
  • Uitlegbaarheid: feature_insights.json bevat importances, correlaties en de laatste featurewaarden. Deze bestanden voeden direct de driver-tabellen en toelichtingen op het dashboard.

Omdat alles (datasets, CV-resultaten, feature-insights) als Parquet/JSON wordt weggeschreven, kunnen we elke run achteraf volledig reproduceren of inspecteren. Geen verborgen “handmatige fixes”.

Belangrijkste databronnen en wat ze betekenen

Bron Wat meten we? Waarom relevant?
Landbouw & Boerderij Werkelijke weekprijzen (bovenkant/onderkant). Doelvariabele en momentum-indicator (lags 0/1/8).
CBS pompprijzen Dagelijkse dieselprijzen → rollende 4w/8w gemiddelden. Transport- en bewaarkosten drukken door in spotprijzen.
Open-Meteo Cumulatieve groeigraden, hitte-/stressdagen. Verklaart vroeg in het seizoen aanboddruk of vertraagde oogst.
Gemini LLM-features Tekstanalyse van vakpers → “processing_utilization”, “quality_storability”. Snel sentiment-signaal over fabrieksuitval, bewaarbaarheid en contractdruk.
Seizoenskalender Week-of-year, afstand tot feestdagen, ag-season labels. Vangt terugkerende patronen (kerstgraan, frietvraag, opslagstress).

Wat doen we vóór een productie-run?

  1. Nieuwsfeatures bijwerken: ensure_news_features_up_to_date checkt of de geconsolideerde JSON alle ISO-weken dekt. Ontbrekende weken triggeren automatisch fetch → clean → dedup → sample → extract → convert.
  2. Dataset sanity: create_experiment_datasets wordt voor elke horizon één keer gedraaid zodat alle metingen gelijk “vers” zijn (zelfde window, zelfde lagregels).
  3. Training + evaluatie: train_and_predict_production.py start CV, fit het volledige model, exporteert predictions + rapporten en logt alles in production_models/<timestamp>.
  4. Weekly predictions service: services/weekly_predictions kan hetzelfde proces in Docker draaien en de outputs rechtstreeks naar service_outputs dumpen voor het dashboard.

Zo houden we research, productie en visualisatie in één lijn en kunnen we exact zien welke data en modellen een dashboard-run bevatten.

Kruisvalideringsopzet

Initieel trainvenster
104 weken (expanderend)
Stapgrootte
4 weken
CV-folds
70–95 (afhankelijk van horizon)
Doel
Prijsniveau (EUR/100 kg) op h weken vooruit
Baseline
GradientBoosting met alleen prijs-lags
Naïeve check
Vandaag = morgen (referentie voor sanity checks)
Trendscore
Correcte richting t.o.v. actuele prijs, alleen wanneer |Δ| ≥ €2

De trendscore die je hieronder ziet is afgeleid van de CV-voorspellingen: we vergelijken de voorspelde prijsverandering versus de laatste bekende prijs en tellen alleen weken mee waarin de gerealiseerde beweging groter is dan €2. Dat sluit de ruis rond vlakke periodes uit.

Horizonresultaten (laatste productie-run)

Horizon MAE (GB + features) MAPE Trendscore (Δ ≥ €2) MAE baseline (GB + prijs) Trendscore baseline MAE naïef
H04W €2.23 15.2% 88.6% €3.07 75.0% €3.18
H08W €2.84 23.9% 90.7% €4.17 65.8% €4.87
H12W €4.14 45.0% 89.5% €5.28 48.8% €6.82
H16W €4.27 45.6% 82.5% €5.71 57.5% €9.12
H20W €4.27 41.9% 93.4% €6.63 61.5% €10.34
H24W €5.51 55.0% 82.0% €6.8 74.3% €11.33
H28W €3.87 25.1% 89.2% €6.0 63.6% €11.7

Merk vooral het verschil tussen 24 w en 28 w op: dezelfde featurefamilie, maar 28 w ontwijkt de drukke decemberperiode en blijft daardoor op MAE ≈ €3,9 met een trendscore van 89%. Voor 24 w werken we aan extra vraag-/feestdagfeatures om de staartfouten te verkleinen.

Hoe interpreteer je de cijfers?

  • MAE vertelt hoeveel euro de voorspelling gemiddeld naast de realisatie zat. Onder €4 betekent praktisch bruikbare signalen voor onderhandelingen.
  • MAPE stijgt bij lange horizons omdat het prijsbereik enorm is (van €5 tot €60). Kijk daarom altijd ook naar de absolute fout.
  • Trendscore is dé beslismetric: het aandeel weken waarin we de richting correct hebben zodra de markt écht beweegt (> €2). Waarden boven 80% geven vertrouwen voor alerts en scenario’s.
  • Baseline en naïef dienen als sanity check. Als de productie-run ooit slechter presteert dan “vandaag = morgen”, stoppen we de deployment automatisch.

Short vs. mid vs. long:

  • 4–12 weken: momentum + diesel + actuele marktverhalen. Ze signaleren bij >88% van de grote bewegingen de juiste richting.
  • 16–20 weken: fundamentals (verwerking, energie, bewaring) nemen het stokje over; absolute fout blijft rond €4 terwijl de trendscore naar 93% klimt.
  • 24–28 weken: dezelfde features, maar 28 w valt na de feestdagen en kent dus veel minder uitschieters dan 24 w. De tabel hierboven laat het verschil zien.

Featurefamilies per horizon

Horizon Belangrijkste signalen
4–8 weken Prijs-lags (0/1/8), diesel 4w/8w, week-of-year, holiday proximity, LLM-sentiment (“quality_storability”, “processing_utilization”).
12–20 weken Idem + cumulatieve groeigraden (GDD) met lags 8/12 en transportkosten-index die oogstdruk representeert.
24–28 weken Fundamentals lags (12 weken terug) en lange-termijn GDD domineren; directe prijs-lag is vooral een ankerpunt.

De exacte kolomnamen vind je in feature_sets_config.py en in de feature_insights.json per horizon. Alles wat we tonen in het dashboard komt rechtstreeks uit die bestanden.

Wat betekenen de extra metrics?

  • Trendscore (Δ ≥ €2): alleen weken waarin het verschil tussen actuele prijs en doelprijs groter is dan €2 tellen mee. Een vlakke markt wordt zo niet overschat.
  • Naïeve benchmark: “Vandaag = morgen”. Geeft direct aan hoeveel absolute fout er puur door ruis ontstaat. In onze CV-run blijft deze regel steevast boven €11 voor de lange horizons.
  • Baseline (GB + prijs): laat zien wat er gebeurt als we alle exogene signalen weglaten. Als de ARDL-variant ooit slechter scoort dan deze baseline, gaat er iets mis in de feature-pipeline.

Tip: combineer de trendscore met MAE voor beslissingen. Een horizon met MAE ±€4 maar 90% richtingsnauwkeurigheid is bruikbaarder dan een horizon met MAE ±€3 maar slechts 60% juiste richting.

Verder lezen

Wil je de volledige beschrijving inclusief testscripts, exact dezelfde tabel en een samenvatting van de 24 w vs. 28 w-analyse meekrijgen? Bekijk dan docs/model_training_report.md in de repo. Daar linken we ook naar tmp_model_cv_summary.csv en tmp_model_cv_direction.csv, zodat je onze tabel makkelijk kunt importeren in eigen rapportages.

Daarnaast vind je in docs/model_performance_summary.md een compacte versie van de tabellen en in docs/methodology-secties een Engelstalige breakdown. Alles draait rond dezelfde artefacten die dit dashboard ook gebruikt – er is geen verborgen logica buiten de repo.