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
- Ontsluiten: APIs en scrapers leveren dagelijkse of wekelijkse reeksen. We bewaren elke bron in zijn eigen map onder
src/sourceszodat updates traceerbaar blijven. - Kalenderen:
_canonicalize_weekly_priceszet álle weekreeksen op dezelfde ISO-week (maandag) en dwingt unieke sleutelweek_startaf. Hierdoor missen we nooit een horizon doordat er een RVO-week “ontbreekt”. - Lags en gaps: Via
_calendar_lag_mergevoegen 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. - CV-pakketten:
create_experiment_datasetslevert 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.jsonbevat 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?
- Nieuwsfeatures bijwerken:
ensure_news_features_up_to_datecheckt of de geconsolideerde JSON alle ISO-weken dekt. Ontbrekende weken triggeren automatisch fetch → clean → dedup → sample → extract → convert. - Dataset sanity:
create_experiment_datasetswordt voor elke horizon één keer gedraaid zodat alle metingen gelijk “vers” zijn (zelfde window, zelfde lagregels). - Training + evaluatie:
train_and_predict_production.pystart CV, fit het volledige model, exporteert predictions + rapporten en logt alles inproduction_models/<timestamp>. - Weekly predictions service:
services/weekly_predictionskan hetzelfde proces in Docker draaien en de outputs rechtstreeks naarservice_outputsdumpen 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.