Happy House - Ecommerce Docs

Reports & Analytics Features and Flows

Complete feature list, actor journeys, state flows, business rules, edge cases, and diagrams for Reports & Analytics.

Reports & Analytics - Features and Flows

1. Documentation Evidence

Source TypeFiles or DocsWhat Was Extracted
Technical design.omc/plans/Reports-analytics/master-plan.md, consumer-handoff.mdDesign rationale, the frozen contract
APIadmin/dashboard/analytics-dashboard-admin.controller.ts, admin/reports/analytics-reports-admin.controller.ts, admin/exports/analytics-exports-admin.controller.ts, admin/sync/analytics-sync-admin.controller.tsRoutes, permissions, rate limits
Backendshared/*.service.ts, shared/sync/*.ts, shared/reports/*.tsBehavior, ETL, rollups, exports
Schemapackages/mongodb/src/schemas/analytics/*.schema.ts18 collections, access patterns, retention
Jobspackages/jobs/src/index.tsQueueName.ANALYTICS, AnalyticsJob, payloads

2. Feature Summary

FieldValue
Moduleanalytics
Submoduledashboard, reports, exports, sync (all admin)
Primary user valueA shop answers "why is the business performing this way" and "what happened" without queries over the live order book
Actorsadmin (reader), admin (operator), system (ETL crons, export workers)
Main entry points19 admin routes under /api/admin/analytics/; 5 cron jobs; 8 queue job kinds
Main outputsDashboard figures, report rows, export files, sync-state documents, Mongo facts and rollups
Related docsBackend, API

3. Actor Matrix

ActorCan DoCannot DoAuth RequirementNotes
Admin (reader)Read every dashboard/analytics route, list and run permitted reports, request/read/download their own exports, read sync stateTrigger syncs, backfills, reconciles; see reports outside their Reports*_READ grants; see others' exportsAnalytics_READ (+ the report's own for reports/exports)GET /reports returns only what they may run
Admin (operator)Everything a reader can, plus `POST /sync/refreshbackfillreconcile`Delete or reset analytics data
System (cron)Sync all streams (2 min), rollup (5 min), drain pending (5 min), reconcile orders (hourly), sweep exports (hourly)Move money or stockqueue/internalFailures log and skip — analytics never affects orders
System (worker)Project streams, rebuild rollups, build exports, sweep filesqueue/internalOne @Processor per queue

4. Capability Matrix

CapabilitySurfaceActorRoute/TriggerState ReadState WrittenLinked API Section
Dashboard summaryAdminReaderGET /api/admin/analytics/dashboarddaily rollups§8.1
KPIs with comparisonAdminReaderGET /api/admin/analytics/kpisdaily rollups§8.2
Daily trendAdminReaderGET /api/admin/analytics/trendsdaily rollups§8.3
Conversion funnelAdminReaderGET /api/admin/analytics/funneldaily rollups§8.4
Channel splitAdminReaderGET /api/admin/analytics/channelsdaily rollups§8.5
Payment-method splitAdminReaderGET /api/admin/analytics/payment-methodsdaily rollups§8.6
Top productsAdminReaderGET /api/admin/analytics/top-productsorder line facts§8.7
GeographyAdminReaderGET /api/admin/analytics/geographyorder facts§8.8
Review sentimentAdminReaderGET /api/admin/analytics/reviewsreview facts§8.9
List permitted reportsAdminReaderGET /api/admin/analytics/reportsreport registry§8.10
Run a reportAdminReaderGET /api/admin/analytics/reports/{reportId}fact collections / rollups§8.11
Request an exportAdminReaderPOST /api/admin/analytics/exportsregistry, existing exportsexport record (Mongo) + queue job§8.12
List own exportsAdminReaderGET /api/admin/analytics/exportsexport records§8.13
Poll an exportAdminReaderGET /api/admin/analytics/exports/{exportId}export record§8.14
Download an exportAdminReaderGET .../exports/{exportId}/downloadexport record, file§8.15
Read sync stateAdminReaderGET /api/admin/analytics/syncsync-state documents§8.16
Force a syncAdminOperatorPOST /api/admin/analytics/sync/refreshsync-statequeue job§8.17
Backfill a streamAdminOperatorPOST /api/admin/analytics/sync/backfillsync-statequeue job§8.18
Reconcile a windowAdminOperatorPOST /api/admin/analytics/sync/reconcilesync-statequeue job§8.19
Incremental syncSystemCron 2 minanalytics.sync_allPostgreSQL, sync-stateMongo facts§9
Rollup rebuildSystemCron 5 minanalytics.rollupfactsdaily rollups§9
Pending drainSystemCron 5 minanalytics.drain_pendingpending workorder facts, rollups§9
Reconcile sweepSystemCron hourlyanalytics.reconcilefactsfacts (in place)§9
Export sweepSystemCron hourlyanalytics.sweep_exportsexport records, filesfiles (deleted), re-enqueued jobs§9

5. User-Facing Flows

Nothing on this surface is customer-facing. There is no /mobile route and no storefront impact. The flows below are the administrator's.

5.1 Reading the dashboard

Summary

The dashboard is split into independent widget endpoints so one slow ranking widget never blocks the whole page. The summary endpoint returns rollup-backed tiles (cheap); rankings read the fact collections on their own routes.

Preconditions

  • The first sync has completed — otherwise every endpoint returns 503 ANALYTICS_NOT_YET_SYNCED rather than zeros.

Main Flow

StepActor/SystemActionResultSource
1AdminGET /dashboardTiles for today / week-to-date / month-to-date, stock and moderation countsanalytics-dashboard.service.ts
2AdminGET /kpis?period=this_monthKPIs with like-for-like comparisonsame
3AdminGET /trends, /funnel, /channels, …Independent widgetssame

Sequence Diagram

Branches and Edge Cases

BranchConditionBehaviorError/Result
Never syncedNo lastSuccessAt on a contributing stream503 ANALYTICS_NOT_YET_SYNCEDRetry shortly; never render zeros
Stream behindlastBatchTruncatedbehind: true in metaFigures valid but incomplete — subtle indicator
Yesterday had no revenueZero denominatortodayVsYesterdayPercent: nullThe first sale after a blank day is not "0% growth"
No checkouts startedZero denominatorcheckoutConversionPercent: nullUndefined, not zero

5.2 Running a report

Summary

One generic route runs any of the 19 registry reports. Filters travel as a single JSON parameter; the report's own allowlist validates them by name — an unsupported key is refused, never silently dropped.

Preconditions

  • The caller holds the report's Reports*_READ permission. GET /reports lists only what they may run.

Main Flow

StepActor/SystemActionResultSource
1AdminGET /reportsDescriptors the caller may run, with columns and filtersanalytics-report.service.ts
2AdminGET /reports/{reportId}?period=…&filters={...}&sortBy=…Report rows + meta (period, paging, dataAsOf)same

Branches and Edge Cases

BranchConditionBehaviorError/Result
Unsupported filter keyKey not on the report400 ANALYTICS_FILTER_NOT_SUPPORTED, message names the key and lists supportedFix the filter
Unsupported sortsortBy not in sortable400 ANALYTICS_SORT_NOT_SUPPORTEDUse a listed key
No such report / not permitted404 ANALYTICS_REPORT_NOT_FOUNDDeliberately indistinguishable — ids cannot be enumerated
Period invalidfrom after to / custom incomplete400 ANALYTICS_INVALID_PERIODFix dates
Period too longOver 3 years400 ANALYTICS_PERIOD_TOO_LONGNarrow, or export it
Point-in-time reportpointInTime: truemeta.period: nullDo not show a date range
Search termprefix-anchoredfilters={"customerName":"ram"}Matches "Ram Bahadur", not "Sita Ram"

5.3 Exporting a report

Summary

Asynchronous by design — a year of orders can aggregate for minutes. POST queues the build and returns an exportId; poll until ready, then download.

Preconditions

  • Caller holds the report's permission; idempotencyKey is required.

Main Flow

StepActor/SystemActionResultSource
1AdminPOST /exports with reportId, format, period, filters, idempotencyKeyexportId, status: queuedanalytics-export.service.ts
2Workeranalytics.build_exportFile written; record readyanalytics-export-builder.service.ts
3AdminPoll GET /exports/{exportId}Status ready, downloadUrl setsame
4AdminGET .../downloadFile streamed; permission re-checkedcontroller

Branches and Edge Cases

BranchConditionBehaviorError/Result
Double-clickSame idempotencyKeyReturns the existing exportNo second aggregation
Row capOver 50,000 rowstruncated: true on record AND warning line in the fileShow the flag
Still buildingqueued/processing400 ANALYTICS_EXPORT_NOT_READYKeep polling
Failed buildWorker error400 ANALYTICS_EXPORT_FAILED, error carries the reasonRequest again
Expired filePast 48h, swept410 ANALYTICS_EXPORT_EXPIREDRequest the export again
Not yoursAnother admin's export404 ANALYTICS_EXPORT_NOT_FOUNDSame as nonexistent — no probing
Permission revokedRole changed after queueingDownload 404sRe-check is against the LIVE role

6. Admin Flows

6.1 The operator sync surface

POST /sync/refresh (stream or all), /backfill (resumable, walks slices, seeds the watermark on completion), /reconcile (re-projects a window ignoring the watermark, reports driftCount). All three return as soon as the job is queued — poll GET /sync and watch lastSuccessAt/driftCount move. 409 ANALYTICS_SYNC_ALREADY_RUNNING is an answer, not a failure: the work is already happening.

6.2 Export lifecycle

7. Lifecycle and State Transitions

7.1 Export record

EntityFromEvent/ActionToGuard ConditionSide Effects
analytics_report_exportqueuedWorker claimsprocessingFile build starts
analytics_report_exportprocessingBuild completesreadyFile writtendownloadUrl becomes available
analytics_report_exportprocessingBuild throwsfailederror carries the reason
analytics_report_exportreadyHourly sweepexpiredPast TTL (48h)File deleted from disk
analytics_report_exportqueuedHourly sweepre-enqueuedqueued > 15 min, startedAt nullanalytics.build_export re-added (job-id deduped)

7.2 Stream state

EntityFromEvent/ActionToGuard ConditionSide Effects
analytics_sync_stateidleRun claims lockrunningLock free or expiredWatermark scan starts
analytics_sync_staterunningRun completesidleWatermark advanced (conditional); lastSuccessAt set; failures reset
analytics_sync_staterunningRun throwsfailedconsecutiveFailures++; watermark NOT advanced
analytics_sync_statefailedNext run succeedsidleFailures reset to 0
analytics_sync_staterunningBackfill slicebackfillingMore slices remainbackfillCursor records the resume point
analytics_sync_statebackfillingFinal sliceidledoneWatermark seeded where the backfill finished

9. Data and Side Effects by Flow

FlowDB WritesCache EffectsJobsRealtimeAnalyticsNotifications
Incremental syncMongo facts per streamanalytics.sync_stream fan-out
Rollup rebuilddaily rollups (replaceOne)analytics.rollup
Pending drainorder facts, rollup daysanalytics.drain_pending
Export requestexport record (Mongo)analytics.build_export
Export sweepexport records, filesanalytics.sweep_exports
Sync triggerssync_stream / sync_all / backfill / reconcile

No PostgreSQL table is written by this module. Mongo is the only store it writes.

10. Error and Recovery Flows

ScenarioTriggerUser/System ExperienceRecoverySource
Never syncedFirst sync not done503 ANALYTICS_NOT_YET_SYNCEDRetry shortly or trigger refreshanalytics-freshness.service.ts
Stream failedMongo/DB errorconsecutiveFailures++, figures ageNext run re-scans the same range (idempotent upserts)analytics-sync-state.service.ts
Stream behindPage budget hitbehind: true on responsesNext tick continues; no data lost (watermark only advanced to processed)same
Export enqueue lostRedis blip after record writeRecord stuck queuedHourly sweep re-enqueuesanalytics-export.service.ts
Cron enqueue failedRedis blipLogged and skippedNext tick (2 min); watermark did not moveanalytics-maintenance.scheduler.ts
Rollup half-projectedDuplicate/out-of-order eventsWrong day for one tickFull recompute next tick — no counter to corruptanalytics-rollup.service.ts
Refund settled lateOld order touchedJanuary net revenue staleanalytics_pending_work queues the day; drain fixes itpending drain

12. Mandatory Feature and Flow Deep-Dive Pack

12.1 Feature Inventory With Minor Behaviors

FeatureMinor BehaviorActorTriggerUser/System ResultBackend Side EffectSource
DashboardIndependent widget endpointsAdminGET /dashboard + 8 moreNo widget blocks anotherRollup-backed tiles vs fact-backed rankingsdashboard service
FreshnessdataAsOf on every responseAdminany readAge visible next to the figureOldest contributing stream winsfreshness service
ComparisontruncatedForFairnessAdminGET /kpisSame-elapsed-days baselineCalendar-aligned windowsperiod util
Report registryDescriptor as dataAdminGET /reportsPickers built from the response19 descriptors, one code pathreport-registry.ts
FiltersRefused by nameAdminGET /reports/{id}No confident wrong answersAllowlist + coercion per kindreport service
SearchPrefix-anchoredAdminreport filter"Starts with" semantics^escaped regex on lowercased indexsame
Point-in-timemeta.period: nullAdminnever-purchased / low-stock / top-customersNo false date rangepointInTime: true on descriptorregistry types
Export idempotencyUnique per adminAdminPOST /exportsDouble-click safeUnique index + duplicate-key returnexport service
Export truncationIn-file warningAdminbig exportCap visibletruncated flag + warning rowbuilder service
Export permission re-checkLive roleAdmindownloadRevoked access dies at downloadassertStillPermittedexport service
Sync stateparent_driven streamOperatorGET /syncorder_lines explained, not alarmingANALYTICS_PARENT_DRIVEN_STREAMSoperator service
BackfillResumable slicesOperatorPOST /sync/backfillPress twice, continuesbackfillCursorsame
ReconcileNever advances watermarkOperatorPOST /sync/reconcileCan't mask a stuck streamcompleteReconcilesync-state service

12.2 Business Process Diagram Pack

The ETL pipeline:

Export pipeline:

12.3 Business Rules and Policy Traceability

RuleBusiness ReasonActor ImpactEnforced InAPI ImpactBackend ImpactTests
Net revenue deducts settled refunds onlyApproved = liability, not deductionTwo refund figures differrollup aggregateapprovedRefundedAmount vs settledRefundedAmountnet = gross - settledRefundedrollup spec
Never-synced returns 503, not zerosZero revenue ≠ no tradingDashboard refuses to liefreshness serviceANALYTICS_NOT_YET_SYNCEDfreshness spec
Watermark overlap + conditional advanceupdated_at predates commitNo silent row losssync-state service900s overlap; Math.max advancesync int spec
Pending work for child-touched parentsSettlement never touches the order rowRefunds reach net revenuepending drainenqueuePending from projectorssync int spec
Rollups recomputed, never incrementedStandalone Mongo, at-least-onceDuplicates harmlessrollup servicereplaceOne(upsert)rollup spec
Filters refused by nameDropped filter = wrong answerNo confident liesreport service400 ANALYTICS_FILTER_NOT_SUPPORTEDallowlistreport spec
Permission checked before the pipeline runsCost controlNo paid-for refusalsreport serviceassertPermitted firstreport spec
Report 404 = no-such OR not-permittedIds must not be enumerableOps can't probe revenue reportsreport serviceone 404 shapereport spec
Export permission re-checked at downloadQueueing ≠ snapshot accessRevocation sticksexport service + controllerassertStillPermittedexport spec
Export cap writes into the fileSilent truncation is worseCap visiblebuilder servicetruncated flagwarning rowbuilder spec
No reset or delete actionRepair = re-projectNo destructive surfaceoperator serviceno such route
Day boundaries Asia/KathmanduLocal midnight is the business dayClient must not UTC-bucketperiod utiltimeZone in metapinned at projectionperiod spec

12.4 Tradeoffs and Product Rationale

Product DecisionUser BenefitEngineering BenefitAlternativeTradeoffRisk
MongoDB projection over PostgreSQLCheap reads, no load on the live storeAnalytics never competes with ordersDirect queriesUp to 2 min stalenessMitigated by visible dataAsOf
Rollups instead of live aggregationInstant dashboardsBounded reads$group over factsRecomputed on a 5-min tickBrief staleness, self-correcting
Registry instead of per-report endpointsOne code pathNo 60-copy driftController per reportSwagger can't enumerate shapesColumns exposed via GET /reports
Report-permission splitOps without revenue, finance without stockLeast privilegeOne Reports_READMore grants to administerThe point of the module
Async capped exportsNo proxy timeoutsQueued buildsSynchronous stream50k row cap; 48h TTLBoth surfaced loudly
No product-view funnel stageHonest funnelFabricated denominatorProduct→cart rate missingStated in unavailableStages
Prefix-only searchIndex-servedBounded costSubstring"starts with" semanticsStated in docs and handoff

12.5 Flow Edge-Case Matrix

FlowEdge CaseTriggerExpected BehaviorUser/System FeedbackSource
DashboardNever syncedFirst request503Retry or trigger refreshfreshness service
DashboardZero yesterdayBlank dayNull percentNo fake "0% growth"dashboard service
ReportUnknown filter keyTypo400 naming the keyFix the filterreport service
ReportUnknown sort keyTypo400 listing valid keysUse a listed keysame
ReportZero totalsEmpty period$count = 0, paging total 0Empty statesame
ExportDouble submitSame idempotencyKeyExisting record returnedNo duplicate workexport service
ExportStuck queuedEnqueue lostRe-enqueued by sweepStatus eventually movessame
ExportPast TTLLate download410 expiredRequest againcontroller
SyncTwo operators triggerBoth press refresh409 ALREADY_RUNNING"It's already running"operator service
SyncBackfill pressed twiceOperatorResumes from cursorNo restartsame
StreamBulk update stamps one timestamp1,500 rows, one now()Keyset tiebreaker holds the page boundaryNo lost rowssync-state service
ReconcileHealthy streamHourlydriftCount 0Diagnosis: incremental path finesame

12.6 Flow-to-Data Trace

FlowReadsWritesCacheJobs/EventsResponse Fields
Dashboard summarydaily rollups, product facts (stock)tiles + windows meta
KPIsdaily rollups (2 windows)metrics + comparison meta
Funneldaily rollupsstages, conversions, unavailableStages
Report runfact collection / rollupsitems, total, report meta
Export requestregistry, existing exportsexport recordbuild_exportexportId, status, expiresAt
Export downloadexport record, filefile bytes
Sync triggerssync-state4 job kindsqueued, correlationId

12.7 Experience Quality Checklist

  • The doc explains what the actor is trying to accomplish (understand the business, export what happened).
  • The doc explains what the backend does the actor does not see (ETL, watermark mechanics, idempotent upserts).
  • Every minor flow and branch is covered (freshness, truncation, permission re-check, resumable backfill).
  • Admin and system flows are included; there is no user flow by design.
  • Business logic, tradeoffs and rationale are explained (§12.3, §12.4).
  • Every flow maps to API routes and backend side effects (§4, §9, §12.6).
  • Diagrams fit each flow type (sequence, activity, state, pipeline).
  • Edge cases and failure recovery are covered (§5, §10, §12.5).

13. Completion Checklist

  • Every feature, minor action, and submodule capability is listed (§4, §12.1).
  • Every actor has allowed and forbidden behavior (§3).
  • Every major and minor flow includes steps, branches, and diagrams (§5, §6, §12.2).
  • Every lifecycle has a transition table and state diagram (§7).
  • Every flow links to the API and backend docs (§4, §12.6).
  • TDD dependencies are called out where they shape behavior (rollup/sync/freshness invariants, §12.3).

See Also

  • API doc: /docs/developer/analytics/api
  • Backend doc: /docs/developer/analytics/backend
  • TDD: not yet published