Dashboard / Telemetry — Core concepts
| Concept | Meaning |
|---|---|
| Dashboard (aggregate) | Read-only query aggregate scoped by BusinessUnit: reads many domain entities, owns no state, emits no domain events. |
| DashboardQueryContext | Sub-aggregate for Staff/Admin, root BusinessUnit, scoped by BusinessUnitId. |
| CustomerDashboardView | Sub-aggregate for customers, root Account, scoped by the user's account/session. |
| Eventual consistency | Results come from a TTL-only cache, lagging real data by at most the endpoint's TTL. |
| AuthenticatedUserId | User identity in App Insights telemetry, taken from the request's claims. |
userName | Custom telemetry property, same value as AuthenticatedUserId, used to query by username. |
The two sub-aggregates
| Sub-Aggregate | Consumer | Root | Scope |
|---|---|---|---|
DashboardQueryContext | Staff / Admin | BusinessUnit | BusinessUnitId |
CustomerDashboardView | Customer | Account | account + session |
Metric formulas
| Metric | Formula |
|---|---|
| EstimatedRevenue | TermBooking StatusId=500 (Confirmed) + BillingId IS NOT NULL; excludes 450/475. |
| TotalBookings | Cumulative up to toDate (not a range count); excludes 100/900; includes TermBooking + TermBookingOrder. |
| NewBookings | New bookings by status set + pending 300 (from TermBookingOrder only). |
| OnsiteRange | SignedInRange − SignedOutRange; both COUNT(DISTINCT AttendeeId). |
| HourlyFlow | Attendees with SignInKeyedOn IS NOT NULL, bucketed by hour 0..23 in BU local time. |
| PeakStartHour / PeakEndHour | Peak hours, same timezone logic as HourlyFlow. |
| RegistrationTrend | Total Accounts + Attendees over time, groupBy = day/week/month. |
| SiteDistribution | Cumulative Accounts + Attendees at toDate; fromDate is ignored. |
Key details:
EstimatedRevenue:450(ConfirmedAttendance) and475(ConfirmedInvoice) are not counted even though close to confirmed.TotalBookings: excludes100(draft) and900(cancelled); adds both tables.HourlyFlow: an attendee present but without a digital sign-in record is not counted.
Real-world example: A booking with
StatusId=500butBillingId=nullis not revenue. → Only confirmed bookings with an invoice count. →EstimatedRevenueexcludes that booking.
Real-world example: BU timezone
Pacific/Auckland, a child signs in at20:30Z= 09:30 NZ. → Do not bucket by UTC. →HourlyFlowrecords local hour 9.
Rules (summary of 16 invariants)
| # | Rule | Error on violation |
|---|---|---|
| 001 | Every query is scoped to an active BusinessUnit. | unknown/inactive → NOT_FOUND |
| 002 | fromDate ≤ toDate. | otherwise → INVALID_PARAMS |
| 003 | siteIds must belong to the BU and be active. | 1 bad site → reject whole request INVALID_ID |
| 004 | EstimatedRevenue only StatusId=500 + BillingId not null. | — |
| 005 | TotalBookings cumulative, excludes 100/900, both tables. | — |
| 006 | NewBookings by status set + pending 300 from TermBookingOrder. | — |
| 007 | OnsiteRange = SignedInRange − SignedOutRange. | — |
| 008 | HourlyFlow only counts SignInKeyedOn non-null. | — |
| 009 | groupBy only day/week/month. | otherwise → INVALID_PARAMS |
| 010 | SiteDistribution cumulative at toDate; fromDate has no effect. | — |
| 011 | Results may lag by up to the endpoint TTL. | — |
| 012 | Dashboard endpoints require JWT. | unauth → reject |
| 013 | With siteIds, account/attendee join switches to INNER JOIN. | — |
| 014 | HourlyFlow.CheckInHour in BU local time (IANA). | — |
| 015 | Peak hour same timezone logic as HourlyFlow. | — |
| 016 | DST per standard rules; no synthetic hour buckets. | — |
Real-world example: An account belongs to the BU but no site; filter
siteIds=[2,3]. → Must not leak into the table. →INNER JOIN, account not shown.
Real-world example: Staff confirm a booking then revisit the widget. → No realtime promise. →
BookingStatisticscache TTL 300s, correct within 5 minutes.
Real-world example: NZ BU, spring-forward skips 02:00–03:00. → No fabricated hours. → No synthetic bucket in that window.
Telemetry user attribution
AuthenticatedUserIdfrom claimname; missing → fallbackemail→preferred_username→upn→sub.- No matching claim → no empty value written; request still runs.
- Also attaches custom property
userNamewith the same value.
| Table type | How to find the username |
|---|---|
Classic (requests) | user_AuthenticatedId or customDimensions.userName |
Workspace (AppRequests) | UserAuthenticatedId or Properties.userName |
- Observational only: does not change auth/routing/response, does not log tokens/credentials.
- Auth-failed requests still return the original response.
Real-world example: Request claim
name = "staff@example.com". → Attached to telemetry. →AuthenticatedUserId = userName = "staff@example.com".