One Metric, Two Revenue Models: Distinct Counting in DAX

Head of BIHead of BI
WeLearn · Commercial reportingWeLearn · Reporting commerciale
Power BIDAXPower QueryCRMGoogle Sheets
BusinessContesto
An EdTech scaleup taking a share of what its partner creators earn. Account managers each carry a book of creators and a quarterly revenue target; the report is scoped to one team at a time.Una scaleup EdTech che trattiene una quota di quanto guadagnano i creator partner. Ogni account manager gestisce un portafoglio di creator e un obiettivo di ricavo trimestrale; il report è filtrato su un team alla volta.
ComplicationsComplessità
Two revenue mechanisms (one-off and multi-month installments), revenue shared back to the creator, covered costs stored as negative transactions, and commercial truth split across a CRM and operational spreadsheets.Due meccanismi di ricavo (una tantum e rateizzati su più mesi), ricavi condivisi con il creator, costi coperti registrati come transazioni negative e il dato commerciale diviso tra un CRM e fogli di calcolo operativi.
Key distinctionDistinzione chiave
Commercial performance measures revenue generated, dated at transaction creation. Target attainment measures revenue collected, dated at receipt. Both correct, both needed, and the source of most debugging.La performance commerciale misura il ricavo generato, datato alla creazione della transazione. Il raggiungimento del target misura il ricavo incassato, datato alla ricezione. Entrambi corretti, entrambi necessari, e all'origine della maggior parte del debugging.
DeliveredRisultato
A leadership view of quarter-to-date collections against target plus active-partner counts, then the same model rebuilt for account managers with a per-creator table and a rollout walkthrough.Una vista per la direzione sugli incassi del trimestre rispetto al target e sul numero di partner attivi, poi lo stesso modello ricostruito per gli account manager con una tabella per creator e una guida al lancio.

The business

WeLearn is an EdTech scaleup that partners with content creators and takes a share of what they earn. The commercial side of the company is organised around account managers, each responsible for a book of creators, grouped into teams. Every quarter, each account manager carries a revenue target.

That sounds like a straightforward sales report. It is not, because of how the money actually moves.

Why the reporting was hard

Four things complicated every number on the report.

  • Two revenue mechanisms. Some revenue arrives as one-off transactions. Some arrives as installment plans agreed once and paid across several months — so a plan signed in January is still producing revenue in April with no April transaction to prove it.
  • Revenue is shared. A percentage goes back to the creator. If the share is 50%, both the revenue and the costs are split 50/50, and only the remainder is WeLearn's.
  • Costs are stored as transactions. Covered expenses sit in the same table as revenue, carrying a negative sign — so any measure that forgets them overstates performance, and any measure that subtracts them twice understates it.
  • Two systems held the truth. Part of the commercial data lived in the CRM, part in operational spreadsheets, and some creators appeared in both. Their totals had to agree exactly.

What the report had to answer

Two audiences, wanting different things from the same model.

Leadership needed the quarter at a glance: how much has been collected against target, how many partners are actively generating revenue, and whether the trend is holding. Account managers needed their own book — their targets, their creators, and enough warning to act before the quarter closed rather than explain afterwards.

The report is scoped to one team at a time, with account manager and team selections driving everything on the page.

What got built

Five pages, refreshed nightly, filterable by account manager, team, creator and period.

  • Creators — performance per individual creator, with attainment against the current quarter's target and a rolling twelve-month revenue trend.
  • Launches — launch quality rather than volume: what share cleared the high-performing threshold, plus first-launch tracking for new creators.
  • Webinars — per-event profitability, sortable to surface loss-making events, with targets tracked on both value and count.
  • Leaderboard — account managers ranked, revenue broken down by product type (courses, webinars, memberships, affiliate), with a margin benchmark.
  • Cross-team — the whole-company view: revenue and margin per creator across every team, live roster status, and revenue by niche over time.

The same six KPIs appear on every page, defined once: revenue generated, operative margin, margin percentage, net achieved, target, and target achievement. Consistency across pages was a deliberate constraint — the fastest way to lose a commercial team's trust is to show them two pages where the same word means two different things.

The Creators page, reconstructed

No screenshots or recordings of the live report appear anywhere on this site. The real dashboard carries company financials, named account managers and individually identifiable creators with their earnings, so it cannot be published — and redacting it would leave an empty frame rather than anything worth looking at. What follows is a rebuild of the layout with entirely invented data, so the structure can be shown without the contents.

Il contesto

WeLearn è una scaleup EdTech che collabora con content creator e trattiene una quota di quello che guadagnano. La parte commerciale dell'azienda è organizzata attorno agli account manager, ciascuno responsabile di un portafoglio di creator, raggruppati in team. Ogni trimestre, ogni account manager ha un obiettivo di ricavo.

Sembra un normale report commerciale. Non lo è, per come si muovono davvero i soldi.

Perché la reportistica era complessa

Quattro elementi complicavano ogni numero del report.

  • Due meccanismi di ricavo. Una parte arriva come transazioni una tantum. Un'altra come piani rateali concordati una volta e pagati su più mesi: un piano firmato a gennaio genera ancora ricavo ad aprile, senza che esista alcuna transazione di aprile a dimostrarlo.
  • Il ricavo è condiviso. Una percentuale torna al creator. Se la quota è del 50%, ricavi e costi si dividono a metà, e solo il resto è di WeLearn.
  • I costi sono registrati come transazioni. Le spese coperte stanno nella stessa tabella dei ricavi, con segno negativo: qualsiasi misura che le dimentichi sovrastima la performance, e qualsiasi misura che le sottragga due volte la sottostima.
  • Due sistemi contenevano la verità. Parte del dato commerciale viveva nel CRM, parte in fogli di calcolo operativi, e alcuni creator comparivano in entrambi. I totali dovevano coincidere esattamente.

A cosa doveva rispondere il report

Due destinatari, con esigenze diverse sullo stesso modello.

La direzione aveva bisogno del trimestre a colpo d'occhio: quanto è stato incassato rispetto al target, quanti partner stanno generando ricavo e se il trend regge. Gli account manager avevano bisogno del proprio portafoglio: i loro target, i loro creator, e abbastanza preavviso per agire prima della chiusura del trimestre invece di spiegare dopo.

Il report è filtrato su un team alla volta, con le selezioni di account manager e team che governano tutta la pagina.

Cosa è stato costruito

Cinque pagine, aggiornate ogni notte, filtrabili per account manager, team, creator e periodo.

  • Creator — performance del singolo creator, con il raggiungimento del target del trimestre corrente e il trend dei ricavi sui dodici mesi.
  • Lanci — la qualità dei lanci, non il volume: quanti hanno superato la soglia di alta performance, più il tracciamento del primo lancio per i creator nuovi.
  • Webinar — redditività per singolo evento, ordinabile per far emergere quelli in perdita, con target monitorati sia a valore sia a numero.
  • Classifica — account manager in graduatoria, ricavi suddivisi per tipo di prodotto (corsi, webinar, membership, affiliazione), con un benchmark di margine.
  • Vista aziendale — il quadro complessivo: ricavi e margine per creator su tutti i team, stato del roster in tempo reale e ricavi per nicchia nel tempo.

Gli stessi sei KPI compaiono su ogni pagina, definiti una volta sola: ricavo generato, margine operativo, margine percentuale, incassato netto, target e raggiungimento del target. La coerenza tra le pagine è stata un vincolo scelto apposta: il modo più rapido per perdere la fiducia di un team commerciale è mostrargli due pagine in cui la stessa parola significa due cose diverse.

Il margine operativo è il ricavo al netto di rimborsi e costi operativi — pubblicità, commissioni di piattaforma e compensi dei creator — ed è per questo che il dato di margine e quello di ricavo raccontano storie davvero diverse sullo stesso creator.

La pagina Creator, ricostruita

Nessuno screenshot o video del report reale compare in nessun punto di questo sito. La dashboard vera contiene dati economici aziendali, account manager con nome e cognome e creator identificabili con i loro guadagni, quindi non è pubblicabile — e oscurarla lascerebbe una cornice vuota invece di qualcosa che valga la pena guardare. Quella che segue è una ricostruzione del layout con dati interamente inventati, per mostrare la struttura senza il contenuto.

Layout reconstruction — every name and figure below is invented sample dataRicostruzione del layout — ogni nome e ogni cifra qui sotto sono dati inventati

Three things drove that layout. The KPI row stays fixed at the top so the same three numbers frame every question asked underneath. The gauge answers "where am I against this quarter" in one glance, with the quarterly history beside it so a good quarter cannot be mistaken for a good year. And the twelve-month trend sits along the bottom of every relevant page, because a single month means very little on its own.

Operative margin is revenue after refunds and operating costs — advertising, platform fees and creator salaries — which is why the margin figure and the revenue figure tell genuinely different stories about the same creator.

Targets: generated versus collected

The single most important distinction on the whole report. The commercial view measures revenue generated — dated when a transaction is created. Target attainment measures revenue collected — dated when the money is actually received.

Those two dates disagree by design, and both are correct. A quarter can look strong on generated revenue and weak on collections, and an account manager needs to see which. Most of the debugging on this project traced back to a measure silently using the wrong one.

Targets themselves come from a quarterly planning sheet — period, year, creator, target value, mapped account manager and team — loaded through Power Query and joined into the model. Attainment is then evaluated per quarter against collections, net of the creator's share, expenses and any discount rates.

Order of operations mattered more than the formulas. Net first, then the revenue share, then discounts. Applied in any other sequence, two systems holding the same underlying truth still disagree by a few percent — which is worse than disagreeing by a lot, because nobody notices.

Counting active partners across both revenue models

Leadership wanted one number: how many partners actually generated revenue this month. A partner might appear in either revenue stream or both, and must be counted once. A partner whose installment plan runs through the month counts, even though nothing was transacted in it.

The measures below are generalised — table and column names are illustrative, not WeLearn's.

Deciding who counts as active

"Active" could not simply be read off a flag. A partner counts if their status is current, or if they were dropped but the drop happened after the month being viewed.

That second clause is the whole point. Filtering on today's status makes history unstable: run the report in June and again in September and they disagree about March, because someone churned in between. Evaluating status as at the selected period freezes history.

VAR _lastDay = EOMONTH( MAXX( ALLSELECTED( 'Calendar' ), 'Calendar'[Date] ), 0 )
VAR _firstDayNextMonth = _lastDay + 1

VAR _activePartners =
    FILTER(
        ALL( 'Partner'[Partner_ID] ),
        CALCULATE( MAX( 'Partner'[Status] ) ) = "Active"
            || CALCULATE( MAX( 'Partner'[Dropped_Date] ) ) >= _firstDayNextMonth
    )

The installment window

A plan is live in the selected month if it started on or before the month ends, and its final installment falls on or after the month begins. Both conditions are needed: the first alone counts plans that finished years ago, the second alone counts plans that have not started.

FILTER(
    'RecurringSales',
    'RecurringSales'[Start_Date] <= _lastDay
        && EOMONTH( 'RecurringSales'[Start_Date], 'RecurringSales'[Installments] - 1 ) >= _firstDaySelectedMonth
        && 'RecurringSales'[Total_Value] >= _threshold
)

EOMONTH(start, installments - 1) derives the plan's final month from its start and length, so no end date has to be stored or kept in step with the source. The - 1 matters: a three-month plan starting in January ends in March, not April.

Filters across an unrelated table

Account manager and team selections came from the transactional fact table, but the recurring-sales table carried its own copies of those columns and no physical relationship. Building one would have introduced ambiguity — two paths to the same dimension at different grains. TREATAS applies the selection as a filter without any modelled relationship existing.

CALCULATETABLE(
    DISTINCT( 'RecurringSales'[Partner_ID] ),
    TREATAS( _selectedManagers, 'RecurringSales'[Account_Manager] ),
    TREATAS( _selectedTeams,    'RecurringSales'[Team] ),
    ...
)

Set logic instead of arithmetic

Counting each stream and adding them double-counts every partner in both. Subtracting an overlap is fragile, because the overlap must be recomputed under exactly the same filter context — and it breaks quietly the first time a slicer is added.

Set operations remove the problem. INTERSECT each revenue stream with the active list, UNION the results, count the distinct rows. Single-counting becomes a property of the structure rather than a correction someone has to maintain.

RETURN
COUNTROWS(
    DISTINCT(
        UNION(
            INTERSECT( _activePartners, _transactionSellers ),
            INTERSECT( _activePartners, _recurringSellers )
        )
    )
)

Reconciling the two source systems

Some creators existed in the CRM, some in operational spreadsheets, some in both. The requirement was blunt: where a creator appears in both, the totals must agree exactly.

Reaching that meant aligning definitions before aligning numbers — which figure is gross, which is net of the creator's share, where covered expenses are subtracted, and at what point discount rates apply. The reconciliation surfaced genuine discrepancies in both directions, and the fix was almost always a definition, not a formula.

Two audiences, one model

The first build served leadership: quarter-to-date collections against target on a gauge, active partner counts, and trend. Once that was trusted, the same model was rebuilt for account managers — their own book, their own targets, and a per-creator table showing who is tracking behind while there is still quarter left to act.

Same measures, same grain, different entry point. Rebuilding the presentation rather than the model is what keeps two audiences arguing about strategy instead of arguing about whose number is right.

Getting it adopted

A dashboard nobody opens is worth nothing, so the rollout came with a walkthrough deck for the account-manager team: what each page answers, how the six KPIs are defined, and three habits to start with — set your filters first, read the gauge for quarter progress, and never judge on revenue alone without checking margin and collections beside it.

It also included a short primer on Power BI itself, which turned out to matter more than expected. Ctrl-click for multi-select. Cross-highlighting by clicking into a visual. Exporting to Excel. And resetting your filters when you finish, because these are shared live reports and the next person inherits whatever view you left behind.

Tre scelte hanno guidato quel layout. La riga dei KPI resta fissa in alto, così gli stessi tre numeri fanno da cornice a ogni domanda posta sotto. Il tachimetro risponde a «a che punto sono in questo trimestre» in un colpo d'occhio, con lo storico trimestrale accanto perché un buon trimestre non venga scambiato per un buon anno. E il trend sui dodici mesi sta in fondo a ogni pagina rilevante, perché un singolo mese da solo dice molto poco.

Target: generato contro incassato

La distinzione più importante di tutto il report. La vista commerciale misura il ricavo generato, datato alla creazione della transazione. Il raggiungimento del target misura il ricavo incassato, datato a quando i soldi arrivano davvero.

Le due date non coincidono per definizione, ed entrambe sono corrette. Un trimestre può sembrare forte sul ricavo generato e debole sugli incassi, e un account manager deve vedere quale dei due. La maggior parte del debugging di questo progetto si è ricondotta a una misura che usava silenziosamente quella sbagliata.

I target arrivano da un foglio di pianificazione trimestrale — periodo, anno, creator, valore obiettivo, account manager e team associati — caricato con Power Query e collegato al modello. Il raggiungimento viene poi valutato per trimestre sugli incassi, al netto della quota del creator, delle spese e di eventuali sconti.

L'ordine delle operazioni contava più delle formule. Prima il netto, poi la quota di ricavo, poi gli sconti. Applicati in qualsiasi altra sequenza, due sistemi che contengono la stessa verità continuano a divergere di qualche punto percentuale — che è peggio che divergere di molto, perché nessuno se ne accorge.

Contare i partner attivi su entrambi i modelli di ricavo

La direzione voleva un solo numero: quanti partner hanno effettivamente generato ricavo in questo mese. Un partner può comparire in uno dei due flussi o in entrambi, e va contato una volta sola. Un partner con un piano rateale attivo nel mese conta, anche se in quel mese non è stata fatta nessuna transazione.

Le misure qui sotto sono generalizzate: i nomi di tabelle e colonne sono illustrativi, non quelli di WeLearn.

Decidere chi conta come attivo

«Attivo» non era leggibile da un semplice flag. Un partner conta se il suo stato è corrente, oppure se è stato rimosso ma la rimozione è avvenuta dopo il mese che si sta guardando.

La seconda condizione è tutto il punto. Filtrare sullo stato di oggi rende instabile lo storico: se esegui il report a giugno e poi a settembre, i due non concordano su marzo, perché nel frattempo qualcuno è uscito. Valutare lo stato alla data del periodo selezionato congela lo storico.

VAR _lastDay = EOMONTH( MAXX( ALLSELECTED( 'Calendar' ), 'Calendar'[Date] ), 0 )
VAR _firstDayNextMonth = _lastDay + 1

VAR _activePartners =
    FILTER(
        ALL( 'Partner'[Partner_ID] ),
        CALCULATE( MAX( 'Partner'[Status] ) ) = "Active"
            || CALCULATE( MAX( 'Partner'[Dropped_Date] ) ) >= _firstDayNextMonth
    )

La finestra delle rate

Un piano è attivo nel mese selezionato se è iniziato entro la fine del mese e se la sua ultima rata cade dopo l'inizio del mese. Servono entrambe le condizioni: la prima da sola conta piani chiusi anni fa, la seconda da sola conta piani non ancora iniziati.

FILTER(
    'RecurringSales',
    'RecurringSales'[Start_Date] <= _lastDay
        && EOMONTH( 'RecurringSales'[Start_Date], 'RecurringSales'[Installments] - 1 ) >= _firstDaySelectedMonth
        && 'RecurringSales'[Total_Value] >= _threshold
)

EOMONTH(inizio, rate - 1) ricava l'ultimo mese del piano dalla data di inizio e dalla durata, così nessuna data di fine deve essere salvata né tenuta allineata alla fonte. Il - 1 conta: un piano di tre mesi che parte a gennaio finisce a marzo, non ad aprile.

Filtri attraverso una tabella non collegata

Le selezioni di account manager e team arrivavano dalla tabella dei fatti transazionale, ma la tabella delle vendite ricorrenti aveva copie proprie di quelle colonne e nessuna relazione fisica. Crearne una avrebbe introdotto ambiguità: due percorsi verso la stessa dimensione, a granularità diverse. TREATAS applica la selezione come filtro senza che esista alcuna relazione nel modello.

CALCULATETABLE(
    DISTINCT( 'RecurringSales'[Partner_ID] ),
    TREATAS( _selectedManagers, 'RecurringSales'[Account_Manager] ),
    TREATAS( _selectedTeams,    'RecurringSales'[Team] ),
    ...
)

Logica sugli insiemi invece che aritmetica

Contare ogni flusso e sommarli conta due volte tutti i partner presenti in entrambi. Sottrarre la sovrapposizione è fragile, perché va ricalcolata nello stesso identico contesto di filtro — e si rompe silenziosamente la prima volta che si aggiunge uno slicer.

Le operazioni sugli insiemi eliminano il problema. INTERSECT di ciascun flusso con la lista degli attivi, UNION dei risultati, conteggio delle righe distinte. Il conteggio unico diventa una proprietà della struttura, non una correzione che qualcuno deve mantenere.

RETURN
COUNTROWS(
    DISTINCT(
        UNION(
            INTERSECT( _activePartners, _transactionSellers ),
            INTERSECT( _activePartners, _recurringSellers )
        )
    )
)

Riconciliare i due sistemi di origine

Alcuni creator esistevano nel CRM, altri nei fogli operativi, altri ancora in entrambi. Il requisito era netto: dove un creator compare in entrambi, i totali devono coincidere esattamente.

Arrivarci ha significato allineare le definizioni prima dei numeri: quale importo è lordo, quale è al netto della quota del creator, dove si sottraggono le spese coperte e in che momento si applicano gli sconti. La riconciliazione ha fatto emergere discrepanze reali in entrambe le direzioni, e la correzione è stata quasi sempre una definizione, non una formula.

Due destinatari, un modello

La prima versione serviva la direzione: incassi da inizio trimestre rispetto al target su un tachimetro, numero di partner attivi e trend. Una volta che quella è diventata affidabile, lo stesso modello è stato ricostruito per gli account manager — il loro portafoglio, i loro target e una tabella per creator che mostra chi è in ritardo quando c'è ancora trimestre per agire.

Stesse misure, stessa granularità, punto di ingresso diverso. Ricostruire la presentazione invece del modello è ciò che fa sì che due destinatari discutano di strategia invece di discutere su chi ha il numero giusto.

Farlo adottare

Una dashboard che nessuno apre non vale niente, quindi il lancio è arrivato con una guida per il team account manager: a cosa risponde ogni pagina, come sono definiti i sei KPI e tre abitudini da cui partire — imposta prima i filtri, leggi il tachimetro per l'avanzamento del trimestre, e non giudicare mai solo dal ricavo senza guardare accanto margine e incassato.

Includeva anche una breve introduzione a Power BI, che si è rivelata più importante del previsto. Ctrl+clic per la selezione multipla. Il cross-highlighting cliccando dentro un grafico. L'esportazione in Excel. E il reset dei filtri quando si finisce, perché sono report condivisi e in tempo reale, e la persona dopo eredita la vista che hai lasciato.

How it was builtCome è stato costruito

Why status had to be evaluated as at the periodPerché lo stato andava valutato alla data del periodo

Reading a partner's current status makes every historical month unstable — run the report in June and again in September and they disagree about March, because someone churned in between. Treating a partner as active if they were dropped only after the selected month ends freezes history, which is the difference between a report people trust and one they re-check by hand.Leggere lo stato attuale di un partner rende instabile ogni mese storico: se esegui il report a giugno e poi a settembre, i due non concordano su marzo, perché nel frattempo qualcuno è uscito. Considerare attivo un partner se è stato rimosso solo dopo la fine del mese selezionato congela lo storico, ed è la differenza fra un report di cui le persone si fidano e uno che ricontrollano a mano.

Set operations beat add-then-subtractLe operazioni sugli insiemi battono somma-e-sottrai

Counting each revenue stream and summing them double-counts anyone present in both; subtracting the overlap means recomputing that overlap under exactly the same filter context, which breaks quietly the first time a new slicer is added. UNION over two INTERSECTs makes single-counting a property of the structure rather than something a correction term has to maintain.Contare separatamente i due flussi di ricavo e sommarli conta due volte chi è presente in entrambi; sottrarre la sovrapposizione significa ricalcolarla nello stesso identico contesto di filtro, e si rompe silenziosamente la prima volta che si aggiunge un nuovo slicer. UNION su due INTERSECT rende il conteggio unico una proprietà della struttura, invece di qualcosa che un termine correttivo deve mantenere.

The end date nobody had to storeLa data di fine che nessuno ha dovuto salvare

An installment plan's last month is derivable from its start and its length, so no end date needs to exist in the source or be kept in step with it. Everything then reduces to a window-overlap test: started on or before the month closes, still running when it opens.L'ultimo mese di un piano rateale è ricavabile dalla data di inizio e dal numero di rate, quindi nessuna data di fine deve esistere nella fonte né essere tenuta allineata. Tutto si riduce a un test di sovrapposizione di finestre: iniziato entro la chiusura del mese, ancora in corso alla sua apertura.

Want to talk about work like this?

BI Developer & Data Analyst, based in Italy and working remotely. Open to roles and freelance projects.

Vuoi parlare di un lavoro come questo?

BI Developer e Data Analyst, con base in Italia e operativo da remoto. Disponibile per assunzione e progetti freelance.

francescostara000@gmail.com LinkedIn · more work on the projects page. LinkedIn · altri lavori nella pagina progetti.