Een nette grafiek is gevaarlijker dan een kapotte
Een Fabric Community thread uit mei 2025 laat PBIGenie hun Hammerhead Variance Bar presenteren: een custom visual die actual-versus-budget vergelijkingen weergeeft als horizontale balken met positieve en negatieve afwijkingscues. De community-reacties waren enthousiast. De visual is overzichtelijk, geen DAX-trucs nodig en de opmaak maakt variantie-interpretatie direct zichtbaar.
Dat enthousiasme is terecht voor de visualisatielaag. Custom visuals zoals de Hammerhead, IBCS-conforme bullet charts en de native Power BI watervalgrafiek lossen een echt probleem op: afwijkingsdata in één oogopslag leesbaar maken. Maar elke van deze visuals heeft dezelfde fundamentele beperking. Ze renderen wat het datamodel bevat. Ze kunnen geen onderscheid maken tussen een dataset die om 05:00 uur is gerefresht met actuele cijfers en één die om 04:47 uur is mislukt en de cijfers van gisteren serveert.
Dat is belangrijk omdat afwijkingsrapporten onevenredig veel organisatorisch gewicht dragen. Als een CFO ziet dat de omzet 3,2% onder budget zit, handelt hij daarnaar. Als dat cijfer feitelijk gebaseerd is op actuals van 36 uur geleden omdat de refresh van de bron is mislukt na een gateway time-out, handelt hij op basis van fictie. De visual zag er perfect uit. De data was dat niet.
De kloof tussen visuele kwaliteit en datakwaliteit is waar productie-incidenten zich schuilhouden. Een grafiek die een renderingsfout gooit, wordt meteen gemeld. Een grafiek die zelfverzekerd onjuiste cijfers toont, kan dagenlang blijven staan.
Custom visuals voegen een renderlaag toe die data freshness verhult
De native visuals van Power BI nemen deel aan de ingebouwde indicators voor data freshness van de service. Als je over een native tabel of staafdiagram hovert, kun je inspecteren wanneer het dataset voor het laatst is gerefresht. Custom visuals, inclusief gecertificeerde visuals via AppSource, tonen deze metadata niet altijd consistent.
Het certificeringsproces voor Power BI custom visuals, gedocumenteerd in de vereisten van Microsoft's Partner Center, richt zich op beveiligingsbeperkingen: geen externe HTTP-verzoeken via fetch of XMLHttpRequest, geen eval() en geen toegang tot externe services. Dat zijn zinvolle grenzen. Maar certificering beoordeelt niet of de visual data staleness communiceert naar gebruikers. Het VisualUpdateOptions-object dat wordt doorgegeven aan de update()-methode van een custom visual bevat viewport-afmetingen en de DataView, maar de DataView zelf bevat geen refresh-tijdstempels of failure-status.
Dit betekent dat een custom visual-ontwikkelaar die een afwijkingsbalk bouwt, op API-niveau geen mechanisme heeft om een waarschuwing zoals "deze data is 14 uur oud" te tonen. De visual ontvangt rijen en measures, rendert die en vertrouwt op de host. De host (Power BI Service of Desktop) kent de refresh-status, maar die kennis stroomt niet door naar de rendercontext van de visual.
Voor standaard rapportage is dit een klein gemis. Voor afwijkingsrapportage, waarbij het volledige doel van de visual is om discrepanties tussen werkelijke en verwachte waarden te tonen, toont een stale dataset niet alleen verkeerde cijfers. Hij toont verkeerde afwijkingen. Een negatieve afwijking wordt positief, of een materieel gemis ziet eruit als een kwartaal op koers. De visual is er om anomalieën te signaleren, en hij kan die taak niet vervullen als de onderliggende anomalie in de data pipeline zit en niet in de data zelf.
Budget tables refreshen anders dan actuals en mislukken onafhankelijk van elkaar
De meeste actual-versus-budget modellen in Power BI gebruiken minstens twee afzonderlijke databronnen. Actuals komen doorgaans uit een transactioneel systeem: een ERP, een data warehouse fact table of een Lakehouse-tabel in Fabric. Budget data komt uit een planningssysteem, een spreadsheet geüpload naar SharePoint of een apart data warehouse. Deze bronnen refreshen op verschillende schema's, via verschillende pipelines en mislukken om verschillende redenen.
Neem een veelvoorkomende architectuur: actuals worden nachtelijks geladen via een Azure Data Factory pipeline die extraheert uit Dynamics 365 naar een Lakehouse, waarna een Power BI dataset om 06:00 uur refresht op basis van zowel het Lakehouse (actuals) als een in SharePoint gehoste Excel-bestand (budget). Als de ADF pipeline om 02:00 uur mislukt door een API throttling-limiet van Dynamics, behoudt de Lakehouse-tabel de actuals van de vorige nacht. De Power BI refresh om 06:00 uur slaagt: hij kan de Lakehouse-tabel en het Excel-bestand nog steeds lezen. Het dataset rapporteert als "succesvol gerefresht" omdat vanuit Power BI's perspectief beide bronnen hebben gereageerd.
De afwijkingsvisual toont nu het budget van het huidige jaar tegen stale actuals. De delta is onjuist, maar niets in Power BI meldt dit. De dataset refresh is geslaagd. De visual rendert. Het enige signaal dat er iets mis is gegaan, staat in de ADF pipeline run history die de Power BI rapport-consument nooit ziet.
Dit failure mode verergert in composite models. Een DirectQuery-verbinding naar actuals kan actuele data retourneren terwijl een Import-mode budget table data behoudt van de laatste geslaagde refresh drie dagen geleden. De LASTDATE() in je actuals wijkt af van het effectieve datumbereik in je budget, en de afwijkingsbalk toont een vergelijking over niet-overeenkomende tijdvensters zonder enige indicatie dat de periodes niet overeenkomen.
Stale afwijkingsdata detecteren vereist signalen buiten de visuallaag
De oplossing zit niet in de visual. PBIGenie's Hammerhead, of welke custom afwijkingsbalk dan ook, doet zijn werk correct: hij rendert de data die hij ontvangt. Het probleem zit stroomopwaarts, in de kloof tussen een pipeline run en dataset-consumptie.
Eén aanpak is freshness checks inbouwen in je DAX-model. Een measure zoals Actuals Data Age = DATEDIFF(MAX(FactSales[LoadTimestamp]), NOW(), HOUR) geeft je een numeriek signaal dat je naast je afwijkingsbalk kunt tonen in een kaartvisual. Als dat getal je verwachte refresh-venster overschrijdt, weet de rapport-consument dat er iets mis is. Dit vereist wel dat de brontabel een load timestamp bijhoudt, wat veel ERP-extracten niet standaard bevatten.
Een andere aanpak gebruikt de Power BI REST API. Het endpoint GET /groups/{groupId}/datasets/{datasetId}/refreshes retourneert refresh history met statuscodes: Completed, Failed en Disabled. Je kunt dit programmatisch pollen, maar je moet de alerting-logica zelf bouwen: een Logic App, een Power Automate flow of een geplande Azure Function die het endpoint controleert en bij een failure een Teams-melding stuurt. Elk van deze opties introduceert zijn eigen failure surface: de Logic App kan mislukken, het service principal-token kan verlopen en de Teams webhook kan worden throttled.
MetricSign monitort Power BI dataset refreshes en de upstream pipelines die ze voeden (ADF, Databricks, dbt) als een verbonden graaf. Wanneer een ADF pipeline run om 02:00 uur mislukt, correleert MetricSign die failure aan de downstream Power BI datasets die afhankelijk zijn van de getroffen Lakehouse-tabel en meldt een incident voordat de refresh van 06:00 uur überhaupt loopt. De afwijkingsvisual krijgt nooit de kans om stale data te tonen, omdat de pipeline-onderbreking aan de bron wordt opgepakt.
Productiechecklist: afwijkingsvisuals betrouwbaar houden
Een betrouwbaar actual-versus-budget rapport in Power BI vereist werk op elke laag, niet alleen de visual. Begin bij de bron. Elke fact table die in een afwijkingsberekening wordt gebruikt, moet een kolom LoadTimestamp of RefreshDate bevatten, gevuld tijdens het ETL-proces. In ADF gebruik je pipeline().TriggerTime als expressie in een Derived Column-activiteit. In Databricks voeg je current_timestamp() toe aan je schrijfoperatie. In dbt gebruik je {{ run_started_at }} als kolomwaarde in je staging-model.
Op dataset-niveau maak je een DAX measure die de leeftijd van de meest recente load timestamp berekent voor elke brontabel. Toon deze als kaartvisuals op de rapportpagina, of beter nog, op een verborgen pagina die je datateam dagelijks bekijkt. Stel conditional formatting in zodat de kaart rood wordt als de leeftijd je SLA overschrijdt: doorgaans het refresh-schema plus een buffer.
Op Power BI Service-niveau configureer je meldingen bij dataset refresh failures. Ga naar de dataset-instellingen, navigeer naar Scheduled Refresh en voeg e-mailadressen toe onder "Send refresh failure notification to." Dit is een minimumvereiste. Het vangt alleen Power BI-niveau refresh failures op, niet upstream pipeline failures die stale maar technisch geldige data produceren.
Op pipeline-niveau instrumenteer je je orchestratietool om failure-signalen uit te sturen die verbinden met je rapportagelaag. Hier hebben de meeste teams een gat. De eigenaar van de ADF pipeline en de eigenaar van het Power BI rapport zijn vaak verschillende mensen, in verschillende teams, die verschillende monitoringtools gebruiken. Het failure-signaal bestaat, maar bereikt de persoon die erop moet handelen niet: de analist die om 09:00 uur een afwijkingsgrafiek aan de CFO gaat presenteren op basis van data van twee dagen geleden.