Commercial analytics
Agree on the numbers before discussing the results.
It started with…
In a weekly recruitment review, a contact, an admission or an enrolment must mean the same thing across campuses and campaign years.
So we built…
A dashboard whose indicators read from a shared status reference. Campus views, weekly curves and previous-year comparisons use those same definitions.
Take a look inside
Screen 1 of 6Weekly consolidated review
Status distribution, period comparisons and objectives share the same reporting view.
What we set out to change
Help the team see where recruitment progresses or stalls using comparable totals, periods and targets.
How it is builtData model, code, controls and team ownership
The user journey
- 01
Map source statuses into the agreed business indicators.
- 02
Aggregate by campaign, campus and week.
- 03
Review progress against targets and compare equivalent periods.
Base structure
| Entity / table | Key fields | Relationships |
|---|---|---|
| Weekly facts | Year · week · campus · status · value | Facts are aggregated through status definitions |
| Statuses | Flags for contacts · interviews · admissions · enrolments | One status can contribute to the relevant indicators |
| Promotion targets | Campus · intake · target | Targets are read from the shared offer reference where configured |
Technical implementation
- A pure aggregation module turns the facts into campus and consolidated snapshots.
- Business flags are read from the Statuses reference rather than embedded throughout presentation components.
- Consolidated rates are recalculated from summed volumes; campaign helpers organise weekly comparisons.
Engineering decision
Aggregate by reference rules, then recalculate consolidated rates
Adapted from the indicator engine. Status membership is maintained in Airtable, facts are indexed by year, campus and week, and consolidated rates are recalculated from volumes. The illustration exposes unmapped facts as a diagnostic instead of silently dropping them. A reference change can affect the historical interpretation, so definitions need an agreed change policy.
const METRICS = ["contacts", "interviews", "enrolments"];const empty = () => Object.fromEntries(METRICS.map(key => [key, 0]));export function aggregateFacts(facts, definitions) { const cells = new Map(); const unmapped = new Set(); for (const fact of facts) { const flags = definitions[fact.statusId]; if (!flags || !fact.campusId) { unmapped.add(fact.statusId); continue; } const key = JSON.stringify([fact.year, fact.campusId, fact.week]); const totals = cells.get(key) ?? empty(); for (const metric of METRICS) { if (flags[metric]) totals[metric] += fact.value; } cells.set(key, totals); } return { cells, unmapped: [...unmapped] };}export function consolidate(snapshots) { const totals = empty(); for (const snapshot of snapshots) { for (const metric of METRICS) totals[metric] += snapshot[metric]; } return { ...totals, conversion: totals.contacts > 0 ? totals.enrolments / totals.contacts : null, };}Adapted implementation excerpt
Controls and boundaries
- Definitions and historical comparisons require business validation.
- A fallback target in code is not the same as a target maintained in the current reference; the configured source should remain explicit.
Who can contribute
- Commercial teams: use campus and weekly review views.
- Builders: maintain status classifications and promotion targets.
- Developers: evolve the aggregation rules and date logic in dedicated modules.
What supports this case
Live consolidated reporting and its definitions panel were inspected alongside aggregation and pace-curve modules. Displayed volumes are operational data, not an attributed business result.