← EnergyViewEnergyView: vangnet en eerste baseline
Status: geen optimalisatie toegepast. Gemeten applicatiebron: 22499676faccb94fe100218c7a449809ac401851. Alle fixtures zijn synthetisch; geen gebruikersbestanden of screenshots in de repository.
Afbakening en betrouwbaarheid
Dit is een onvolledige browserbaseline. Playwright 1.62.1 is aanwezig, maar Chromium ontbreekt. De officiële browserdownload leverde herhaaldelijk een onbruikbaar/niet-ZIP-bestand (0 MiB). De browserrunner eindigt daarom met exitcode 2. Desktop-/mobiele laadtijd, echte transportbytes, browser parse/compile, UI-respons, Long Tasks en browserheap zijn niet gemeten. De doelen <2 s / <100 ms / <50 ms zijn dus niet bewezen en niet als behaald aangemerkt. Node-tijden zijn geen vervanging voor telefoonmetingen en zijn niet met vier vermenigvuldigd.
Golden master
Acht vaste scenario's, met onder meer 35.040 Belgische kwartieren per elektrisch jaar, echte gebundelde tariefkaarten en een vaste toekomstcurve. Referentie-uitkomsten staan in tests/perf/golden.json. Elke bedragafwijking > €0,01 (fysieke waarden maximaal 1e-8), gewijzigde volgorde, contractidentiteit, geselecteerde variant, ontbrekend veld of ontbrekend scenario faalt de controle. Voor een gewijzigde tariefdataset/curve is expliciete herbeoordeling van het vangnet nodig. Het vastleggen gebeurt uitsluitend met --record; een bestaande golden vereist daarnaast --replace-golden.
Machine en methode
- Node v24.22.1; linux; CPU INTEL(R) XEON(R) PLATINUM 8573C; 9 zichtbare logische CPU's. Geen CPU-throttling.
- Ieder scenario draait in een nieuw Node-proces. De klok voor de modeldatum staat op de 23e van de gebundelde curvemaand; Date.now()/looptijdbeveiligingen blijven echt lopen.
- V8 CPU sampling via
node:inspector, interval 1.000 µs. De berekening wordt geprofileerd, inclusief fixture-opbouw binnen run(); module-imports worden apart in de totale tijd meegeteld. Profiling heeft overhead. - Dit eerste overzicht is één koude procesmeting per scenario, geen mediaan/p95. Een tweede volledige onafhankelijke run controleert reproduceerbaarheid en het vangnet; timings daarvan worden apart bewaard. Geen parallelle scenario's in een run. De machine kan wel ander werk uitvoeren.
- RSS is het gehele Node-proces, inclusief datasets en profiler; heap is de eindwaarde, geen piek. Browsergeheugen en hoofdthreadblokkering zijn niet hieruit af te leiden.
Rekenbaseline
| Scenario | Rekenen + fixture (ms) | Met imports (ms) | Eindheap (MiB) | Piek-RSS (MiB) |
|---|
| electricity-single | 1063 | 1338 | 113 | 290 |
| electricity-dual | 1079 | 1340 | 84 | 285 |
| pv-single | 1115 | 1382 | 116 | 296 |
| pv-dual | 1171 | 1531 | 109 | 296 |
| dynamic-ev-battery | 40121 | 40417 | 631 | 1134 |
| gas | 29 | 326 | 55 | 176 |
| battery-study | 14891 | 15178 | 600 | 814 |
| purchase-plan | 72449 | 72808 | 1025 | 1328 |
Top 10 tijdsvreters vóór productiewijzigingen
Geaggregeerde self time over de acht Node-scenario's; aandeel van alle geprofileerde samples, geen inclusief-kinderen-tijd. GC/runtime-frames zijn bewust zichtbaar. De oorspronkelijke .cpuprofile-bestanden blijven lokaal onder .perf-results/.
| # | Functie | Bestand:regel | Self time (ms) | Aandeel |
|---|
| 1 | dynamicBattery | dist/battery-dispatch.js:2 | 61773 | 46.6% |
| 2 | repriceHistorical | dist/historical.js:19 | 6954 | 5.2% |
| 3 | (garbage collector) | :0 | 5814 | 4.4% |
| 4 | sessionFlow | dist/fair-charging.js:10 | 5098 | 3.8% |
| 5 | dayAheadDispatch | dist/day-ahead-dispatch.js:40 | 5015 | 3.8% |
| 6 | repriceForward | dist/forward.js:43 | 3650 | 2.8% |
| 7 | fairCharging | dist/fair-charging.js:106 | 3613 | 2.7% |
| 8 | completePeakPeriods | dist/monthly-peaks.js:6 | 3202 | 2.4% |
| 9 | (anonymous) | dist/dynamic.js:56 | 3161 | 2.4% |
| 10 | externalCostDelta | dist/day-ahead-dispatch.js:61 | 2105 | 1.6% |
Gegevensvolume: bron is niet hetzelfde als levering
| Bestand | Bronbytes | Aangeboden bytes, uitgepakt | gzip-schatting | Brotli-schatting | JSON.parse Node mediaan (ms, n=7) |
|---|
| data.json | 18125981 | 1989352 | 64472 | 25358 | 8.21 |
| gas-data.json | 1976696 | 1976696 | 94332 | 40011 | 8.60 |
| xlsx.full.min.js | 951904 | 951904 | 335530 | 216927 | n.v.t. |
| app.js | 178913 | 178913 | 49834 | 41918 | n.v.t. |
| style.css | 103730 | 103730 | 21685 | 18694 | n.v.t. |
| index.html | 45159 | 45159 | 14727 | 13132 | n.v.t. |
Er zijn 22 afzonderlijke historie-URL's. gzip/Brotli-cijfers zijn lokaal berekende compressieschattingen, geen gemeten bytes over 4G. De Worker pakt ingebedde gzip zelf uit en retourneert een ongecomprimeerde Response; daadwerkelijke Cloudflare edgecompressie moet nog via het netwerk worden vastgesteld. JSON heeft Cache-Control: no-cache; overige bestanden public, max-age=0, must-revalidate. Er zijn geen immutable+hash-bestandsnamen voor deze assets. CSP staat self-scripts/connecties toe; worker-src ontbreekt en valt terug op script-src self.
Hypothesen: bevestigd versus nog te meten
| Hypothese | Huidige evidentie | Beperking |
|---|
| A: volume/parse | Brondata 18 MB; levering splitst historie al. Gasdata blijft apart groot. SheetJS is geen los script in index.html en wordt door de importcode op verzoek geladen. Cacheheaders/compressie hierboven gecontroleerd. | Effect op browserparse/4G nog niet gemeten. |
| B: hoofdthread/workers | report-runner gebruikt bestaande module-Web Workers, voortgang, timeout, termination en gedeeld taakbudget. Zware rekenkosten staan in de CPU-profielen. | Structured-clone-kosten en werkelijk UI-blocking nog niet gemeten. |
| C: herberekening/DOM | Veel invoerhandlers roepen render() aan; rapport- en profielcaches bestaan al. | Geen bewezen browseraandeel voor render/layout; geen DOM-optimalisatie zonder browsertrace. |
| D: algoritmen | Top-10 geeft gemeten CPU-kosten; jaarprofielen en batterijvarianten domineren de lange scenario's. | Geen aanname dat alle Map/Set/Intl-gebruik traag is. |
| E: API | IndexedDB-prijscache, maandreparatie, request-guards, timeout/retry zichtbaar in bron. | Cache-hitratio, dubbele netwerkrequests en externe API-latency niet gemeten. Browserharness gebruikt expliciete marktfixtures. |
| F: laadvolgorde | Module-entry, maandhistorie-splitsing en aparte gasfetch bestaan. | Browsertraces nodig voor kritieke laadketen, parse/compile en CSS/layout. |
| G: mobiel | Reproduceerbare CDP-browserrunner geleverd met 4x CPU en expliciete 4G-instellingen. | Chromium-installatie blokkeert uitvoering hier; geen mobiele baseline. |
Open inhoudelijk issue uit de screenshots
Het automatische hoofdadvies wordt gefilterd met de bestaande €20 extra batterijvoordeel per nominale kWh per jaar-regel, gemeten op hetzelfde contract. De batterijvergelijking toont ook niet-kwalificerende varianten. Daardoor is 'beste automatisch toegelaten voorstel' niet noodzakelijk 'laagste jaarfactuur in de tabel'. Deze policy is in deze performancefase niet veranderd; veranderen zou de uitkomsten wijzigen. Het label/advies en de bedoelde optimalisatiedoelstelling moeten afzonderlijk worden rechtgetrokken. Screenshots zijn niet in de fixtures opgenomen.
Doelen en budgets
Browserdoelen blijven ongewijzigd maar onbeoordeeld: invoer <2.000 ms, contractresultaat <2.000 ms, respons <100 ms, normale Long Tasks <=50 ms. De browserrunner rapporteert expliciete CDP-emulatie (150 ms RTT, 1,6 Mbit/s down, 750 Kbit/s up, 4x CPU), geen Lighthouse-score of geteste gemiddelde telefoon. Het 100 ms round-trip-budget is conservatief en bevat Playwright-overhead; een echte EventTiming/INP-meting is aanvullend nodig.
Node-regressiebudgets zijn initiële referentiehost-limieten: eerste baseline +30% +250 ms, geheugen +25%. Ze staan vast in tests/perf/budgets.json en worden niet automatisch bijgewerkt. Dit zijn vangrails, geen geslaagde UX-doelen. Een volledige npm run perf faalt als de browser ontbreekt of een doel wordt overschreden; --node-only is expliciet een deelmeting.
Volgende fase (nog niet uitgevoerd)
Eerst de browserbaseline op een werkende Chromium-installatie aanvullen. Daarna pas optimalisatie van het hoogste gemeten CPU-aandeel, per wijziging één commit met een nieuwe goldenvergelijking en vóór/na-metingen. Geen functies, contracten of scenario's overslaan. De screenshots tonen daarnaast een inhoudelijke beleids-/presentatievraag die geen performancefix is.
Herhaalde verificatie en spreiding
De tweede volledige run is groen voor alle acht golden-scenario's. Deze draaide gedeeltelijk tegelijk met de volledige testsuite: EV/batterij duurde 64.911 ms en overschreed het vastgelegde 52.500 ms CPU-budget. Dat is expliciet een budgetfalen, geen gewijzigde rekenuitkomst. Zonder applicatiewijziging en nadat de testsuite klaar was duurde diezelfde case 39.693 ms; ook deze goldenvergelijking slaagde. De eerste waarde was 40.121 ms. Deze spreiding is geen optimalisatiewinst.
Alle overige cases bleven binnen hun Node-regressiegrens in de tweede run. Details staan in verification-node.json en isolated-dynamic.json. De oorspronkelijke volledige suite telde 166 geslaagde tests; de nadien toegevoegde afzonderlijke policy-regressie slaagt ook. De vergelijker is aanvullend gecontroleerd op maximaal 1e-8 afwijking voor fysieke waarden.
Alle bestanden onder dist/ en worker/ zijn byte-ongewijzigd ten opzichte van de begincommit. Er is geen productiepublicatie gedaan.
Aanvullende investeringsbevinding uit de latere screenshot
De investeringsstudie is niet uitsluitend vastgepind op het analysecontract. investment-search.js bouwt baselineContracts met meerdere contracten en behoudt opties zonder aankoop. finishInvestmentRisk kiest per aanname de goedkoopste 15-jaarsreferentie zonder aankoop; tijdelijke kortingen worden in futureModel uitgezet. Het voorzichtige voorstel verwijdert positieve injectie-inkomsten en past opnieuw de batterijregel toe. Een jaar-1-tabel en een voorzichtige 15-jaarsuitkomst hoeven daarom niet dezelfde winnaar te hebben. Een synthetische regressie in perf-policy.mjs bewaart dit onderscheid.
Er is wél een scopebeperking: de kandidaatpool wordt vooraf beperkt tot de huidige referentie en de goedkoopste beschikbare kandidaat per tarieftype op de eerstejaarsbasis. Daarmee is niet bewezen dat het globale goedkoopste contract over 15 jaar aanwezig is. De screenshot alleen bevat onvoldoende gegevens om te bepalen of de getoonde jaarprijs onjuist is. De voorkeur voor de duidelijkere Expert-tabel is vastgelegd voor een latere inhoudelijke/UI-wijziging; er is nu geen tabel of uitkomst veranderd.