Skip to content

PRD: Merchant Health Analysis

ModuleReportsPRD IDPRD-MAN-002
StatusReady to devFEATMAN · BANA-1440
EpicBANA-1337PlaneBANA-1736
Date2026-07-07Versionv1.2
Packages@nx/saleURDMAN
SurfaceBO · Ops
OwnerPhát Nguyễn

TL;DR

Gives our operations team (NEXPANDO & VNPAY) a cross-merchant health view in the Back Office: how many merchants are Active vs Inactive (with a comparison to the immediately preceding period), the platform's total sales value (GMV) and average order value (AOV), how GMV moves over the period's natural time buckets, and a per-merchant table with each merchant's revenue, orders, and activity - filterable down to one specific merchant via a searchable dropdown. One calendar-based period + province + merchant filter drives every panel. It is the companion to Merchant Growth Analysis; the growth screen answers "how many and from where", this one answers "how well are they doing".

1. Context & Problem

Merchant Growth Analysis (PRD-MAN-001) shows the size and shape of the merchant base - counts, industries, provinces. It does not show whether those merchants are transacting: who is active, who has gone quiet, how much value flows through the platform, and which merchants drive it.

Our operations team needs that to steer the rollout - to spot merchants slipping into inactivity, to report platform GMV, and to see the per-merchant transaction picture without an ad-hoc data pull. This increment is the second screen under Merchant Analytics (MAN); it delivers operational health and the transactions-by-merchant view.

2. Goals & Non-Goals

Goals

  • Show health KPIs at a glance: Active merchants, Inactive merchants, total GMV, average order value (AOV) - each with a comparison to the immediately preceding period and a "within period" context label.
  • Show the Active vs Inactive split of the merchant base.
  • Show the GMV trend over the period's natural time buckets (day/week/month depending on period length).
  • Show a per-merchant table - each merchant's revenue, orders, and activity - with pagination, sort, and a mandatory filter down to one specific merchant via a searchable dropdown (matching on merchant code or name).
  • Drive every panel from one filter: a calendar-based period (4 presets + a custom date range), a province, and (optionally) one specific merchant.

Non-Goals

  • Merchant adoption counts, industry / province distribution - owned by PRD-MAN-001.
  • A single merchant's own revenue / shift reports (SLS, SHF - surface = client).
  • A composite "health score" formula - this screen shows activity and value directly; a scored model, if wanted, is a separate increment (see Open Questions).
  • The unified operations home landing (not yet built).

3. Success Metrics

MetricTarget / signal
Inactivity visibilityAn operator can tell, for a period, how many merchants are Inactive and how that moved (%) vs the prior period
GMV reportingPlatform GMV and AOV for a period are read from the screen, not an ad-hoc query
Fast per-merchant lookupSupport/Sales types a merchant's code or name into the search dropdown and immediately gets its revenue/orders/activity, without paging through the table
Single-filter consistencyChanging period, province, or merchant re-reads every panel together; reopening the same preset within the same period always yields the same date range (calendar-based, not rolling)

4. Personas & Use Cases

PersonaGoal in this feature
Operations team (NEXPANDO & VNPAY)Watch merchant activity and value; find quiet or high-value merchants
Leadership / partnerReport platform GMV and the active/inactive trend

Core scenario: an operator opens Merchant Health Analysis, picks a period and (optionally) a province; the 4 KPI cards show Active/Inactive counts, GMV and AOV with up/down arrows vs the prior period; the donut shows the Active vs Inactive split; the GMV chart shows value over the period's natural time buckets; and whenever they need to look at one specific merchant (e.g. support handling a ticket), the operator types the merchant's code or name into the search dropdown - every panel narrows down to that merchant.

5. User Stories

  • As an operator, I want Active vs Inactive counts plus GMV and AOV on one row, with % change vs the prior period, so I judge platform health at a glance.
  • As an operator, I want the Active/Inactive split visualised as a donut with percentages, so I see how much of the base is dormant.
  • As an operator, I want the GMV trend over the period's natural time buckets, so I see whether value is rising or falling without the chart getting noisy on long periods.
  • As an operator, I want a per-merchant table of revenue and orders, so I can inspect any single merchant.
  • As a Support/Sales user, I want to type a code or name to find one specific merchant via a dropdown, so I can look up that merchant's situation instantly without paging through the table.
  • As an operator, I want one period + province + merchant filter to drive every panel, so the screen tells one consistent story.

6. Functional Requirements

Locked definition: a merchant carries the label "Active" in a period = it has at least 1 completed transaction within the selected period; otherwise it is "Inactive". The label displays exactly as "Active" / "Inactive" per the mockup - it is not localized to another phrase. This one definition applies uniformly to the KPIs (FR-1), the donut (FR-2), and the table's Status column (FR-4). Note: this is a measure of transaction activity within the period, distinct from the merchant's license/lifecycle status (see the Total card tooltip in Merchant Growth Analysis) - a merchant with a valid license can still be Inactive if it had no transactions in the period.

#RequirementStatusURD ref
FR-1Health KPIs - 4 cards (Active · Inactive · GMV · AOV) with prior-period comparison + "within period" label✅ built, 🚧 date label to addURD-MAN-004
FR-2Active vs Inactive split - 2-slice donut with % in the legend✅ built, 🚧 relabelURD-MAN-004
FR-3GMV trend - area chart, bucketed naturally by period length🚧 bucketing logic needs rebuildURD-MAN-004
FR-4Per-merchant table - 7 columns, server-side pagination, Active/Inactive badge✅ built, 🚧 relabelURD-MAN-002
FR-5Merchant search - searchable dropdown in the shared filter, by code/name, mandatory🚧 not builtURD-MAN-002
FR-6Shared filter - 4 calendar-based period presets + province + merchant, resets on leaving🚧 period computation needs reviewURD-MAN-006
FR-7Ops scope & empty states - cross-merchant, permission-gated, empty states✅ builtURD-MAN-005

FR-1 - Health KPIs (✅ built, 🚧 date label to add)

Four cards on one row, each with the metric name, value, prior-period comparison, and a short description line:

KPICalculationFormat
Active merchantsMerchants with ≥ 1 completed transaction in the selected periodInteger, green text
Inactive merchantsMerchants in the base with no transaction in the periodInteger, red text; the card has a warning (amber) background so it stands out
GMVTotal sales value of all completed transactions in the period, cross-merchantCurrency
AOVGMV ÷ total order count in the periodCurrency
  • Prior-period comparison on all 4 cards: the comparison period is the one immediately preceding the selected period, same length; each card shows an up/down arrow + % change (up = green, down = red) with a "vs previous period" caption.
  • All 4 cards are flow metrics - each always carries a date context label "within DD/MM/YYYY - DD/MM/YYYY" right under the prior-period comparison, to distinguish them from the snapshot cards in Merchant Growth Analysis.
  • A missing value renders as a dash "-"; while loading, skeleton placeholders show.

FR-2 - Active vs Inactive split (✅ built, 🚧 relabel)

  • Chart type: 2-slice donut - Active (green) and Inactive (red).
  • The legend under the chart lists both labels "Active" / "Inactive" with their % share (1 decimal place); the two shares sum to 100%.
  • Uses the exact same two Active/Inactive figures as FR-1 - the donut and the KPIs never diverge.

FR-3 - GMV trend (🚧 bucketing logic needs rebuild)

  • Chart type: area chart (unchanged).

  • The horizontal axis no longer fixes 1 point per day - it divides into the natural time buckets of the selected period, using the same bucketing rule as the growth panel (PRD-MAN-001 FR-2):

    PeriodBucketing rule
    This month / Last monthBy week
    This quarterBy month (exactly 3 points)
    This yearBy month, from January through the current month
    Custom range < 31 daysBy day
    Custom range 31-90 daysBy week
    Custom range > 90 daysBy month
  • Vertical axis: each bucket's GMV.

  • Hovering a point shows a tooltip with the bucket's actual date range + GMV in currency format.

  • This bucketing rule fully replaces the previous approach (fixed 1 point/day, up to ~365 points for "This year") - see Risks.

FR-4 - Per-merchant table (✅ built, 🚧 relabel)

The "transactions by merchant" table, one row per merchant, with 7 columns:

ColumnContent
OrganizerThe organizer that owns the merchant, in the viewer's language
MerchantThe merchant's name, in the viewer's language
Business typeThe merchant's business type (badge)
OrdersThe merchant's completed transaction count in the period
GMVThe merchant's total sales value in the period
Last transactionDate-time of the most recent transaction
StatusActive / Inactive - per the locked definition at the top of §6 (badge)
  • Server-side pagination: 25 / 50 / 75 / 100 rows per page, default 50; the total row count is shown.
  • Server-side sort per column; no per-column filters.
  • The table follows the same period + province + merchant filter as the other panels.

FR-5 - Merchant search (🚧 not built, mandatory before launch)

  • A merchant filter in the form of a searchable dropdown, placed in the shared filter bar next to period + province (not a separate search box on the table) - type to match on merchant code or merchant name.
  • Selecting one merchant → every panel (all 4 KPI cards, donut, GMV chart, table) narrows to that exact merchant; left empty = all merchants within the selected period + province scope.
  • Clearing the selection → every panel returns to the full scope.
  • This is a mandatory filter, not a nice-to-have: Support/Sales needs to look up a specific merchant's situation immediately (e.g. on a support ticket or account follow-up) without paging through the table.

FR-6 - Shared filter (🚧 period computation needs review)

  • Shares the same calendar-based logic as PRD-MAN-001: every period has fixed calendar boundaries, no rolling logic. 4 fixed presets (This month - default · Last month · This quarter · This year; running presets have a calendar-fixed start date, ending today due to the data-availability limit) plus a custom date range (shared requirement FR-7 🚧 over in PRD-MAN-001).
  • Single-select province, defaulting to "All provinces".
  • Merchant selection via the searchable dropdown (FR-5) - the filter's third dimension, defaulting to empty (all merchants).
  • Default on entry: This month + all provinces + no merchant selected; leaving the screen resets the filter to the default.

FR-7 - Ops scope & empty states (✅ built)

  • The screen is cross-merchant, lives in the Back Office "Reports & Analytics" section beside the growth screen, and is permission-gated to the operations team; a merchant's own users can never reach it.
  • No data → per-panel empty states, not errors; while loading → placeholders.

Acceptance criteria

  • For a period with transactions: all 4 KPI cards show values; Active count + Inactive count = total merchants within the filter scope.
  • AOV × total order count ≈ GMV (within currency rounding).
  • Every KPI card shows an arrow + % vs the immediately preceding period of the same length, with a "within DD/MM/YYYY-DD/MM/YYYY" label; the "Inactive merchants" card has a warning background clearly distinct from the other 3.
  • The two donut shares match the KPI Active/Inactive figures exactly and sum to 100%; the labels display exactly "Active"/"Inactive".
  • The GMV chart buckets correctly per the rule table in FR-3 (e.g. This quarter yields exactly 3 points by month, This year yields points by month rather than by day); the sum of every bucket's GMV equals the KPI GMV.
  • Reopening the same preset on two different days within the same period → the period's start date is unchanged; there is no "last 30 days" rolling-window behaviour.
  • A merchant with ≥ 1 completed transaction in the period → its table row shows Status "Active"; with none → "Inactive".
  • The table has all 7 columns; changing rows-per-page from 50 to 25 → the table reloads from page 1 with the same total row count; sorting a column reorders server-side.
  • Changing the period, province, or merchant → KPIs, donut, GMV chart, and table re-read together.
  • Leaving and returning → the filter is back to "This month + all provinces + no merchant selected".
  • Signing in as a merchant's own user → the screen cannot be opened.
  • A period with no transactions → the panels show empty states, not errors.
  • (Once FR-5 is built) Typing a merchant's code or name into the search dropdown and selecting it → all 4 KPI cards, the donut, the GMV chart, and the table narrow to that exact merchant; clearing the selection → every panel returns to the full scope.

7. Non-Functional Requirements

AreaRequirement
Read-onlyThe screen never changes data - it only summarises activity and value
Tenancy & authzCross-merchant scope, permission-gated to operator roles; never reachable by a merchant's own users
ConsistencyKPIs, donut, GMV chart, and table all reflect the same filter (period + province + merchant) and the same "Active"/"Inactive" definition
ScaleThe per-merchant table pages (and searches, once built) server-side so it stays responsive as merchants grow
i18nLabels and money formatting are bilingual (EN/VI); organizer/merchant names follow the viewer's language; the "Active"/"Inactive" labels stay in English in both languages per the mockup
Empty & loadingNo data → an empty state; while loading → a placeholder; partial data never breaks the screen

8. UX & Flows

How an operator uses the screen:

What the one filter drives:

The screen belongs in the Back Office "Reports & Analytics" section, beside Merchant Growth Analysis. It is live in the menu.

9. Data & Domain

ConceptRole in this screen
MerchantThe unit being measured - has an activity state (Active / Inactive) and its transactions
ActivityActive = ≥ 1 completed transaction within the selected period; otherwise Inactive - the single definition, shared by KPIs, donut, and table; distinct from the license/lifecycle status in Merchant Growth Analysis
GMVGross merchandise value - total sales value across merchants for the period
AOVGMV ÷ total order count for the period
Comparison periodThe period immediately preceding the selected one, same length - the source of the KPI arrows and % changes
Merchant rowA single merchant's revenue, order count, last transaction, and activity in the period (transactions by merchant)
Merchant filterSearchable dropdown by merchant code/name (FR-5); when selected, narrows every panel to that one merchant

Conceptual only - the figures are cross-merchant aggregates over completed transactions in the period; the data model lives in the developer docs.

10. Dependencies & Assumptions

Depends on

  • Merchant Growth Analysis (PRD-MAN-001) - shares the same filter and the MAN analytics surface.
  • Sales & orders - completed transactions are the source of GMV, AOV, activity, and the per-merchant table.
  • Commerce - the merchant base, its owning organizers, and business types (Commerce).

Assumptions

  • The operator is authenticated with a cross-merchant operations role.
  • Merchants belong to an organizer and carry a business type, so the table's Organizer / Business type columns are meaningful.

11. Risks & Open Questions

Risk / questionMitigation / status
Is operational health a single score or just the raw activity/value figures?This PRD surfaces the figures directly; a composite health-score model would be a separate increment
The merchant search filter (FR-5) is not built, but is now a mandatory requirement for Support/SalesBlocks launch until the merchant search dropdown works across every panel - track build progress closely
The currently-built period computation may still be rolling under the hoodAudit the period-computation layer before treating FR-6 as fully calendar-based
Large merchant counts in the tablePagination (and search, once built) is server-side to stay responsive

12. Release Plan & Launch Criteria

AspectPlan
PhaseP2 - live in the Back Office
RolloutAll operator users; no feature flag
MigrationNone - it summarises existing transactions
Launch criteriaThe filter (calendar-based period + province + merchant) drives every panel with no rolling behaviour; the 4 KPI cards show Active / Inactive / GMV / AOV with % vs the prior period and a "within period" label; the 2-slice donut matches the KPIs with labels exactly "Active"/"Inactive"; the GMV chart buckets naturally per FR-3; the 7-column table (Active/Inactive badge) pages server-side (25/50/75/100, default 50); the screen is reachable from the BO menu; no data shows an empty state. The merchant search filter by code/name (FR-5, dropdown in the shared filter bar) is mandatory and blocks launch - essential for Support/Sales
MonitoringHow often the report is opened; per-merchant table query latency

13. FAQ

How is this different from Merchant Growth Analysis? Growth answers "how many merchants and from where"; Health answers "how active and how much value", down to a per-merchant table.

What does "Active" mean? A merchant with at least 1 completed transaction in the selected period. No transaction in the period = "Inactive". One definition for the KPIs, the donut, and the table - distinct from the license/lifecycle status shown in Merchant Growth Analysis.

Is there a single health score? Not yet - the screen shows activity and value directly. A scored model can come later if operations want one.

Can I find a specific merchant? Yes - type a code or name into the search dropdown in the shared filter bar (FR-5); selecting a merchant narrows every panel (not just the table) to that exact merchant. This is a mandatory requirement, currently not built - the table can still be paged and sorted while it's being built.

Is this a single merchant's revenue report? No - it is cross-merchant. A merchant's own revenue is the client-side SLS reports.

Can a merchant's own user open this? No - it is cross-merchant and gated to the operations team.

References

Proprietary and Confidential. Unauthorized copying, distribution, or use of this software is strictly prohibited.