2026-03-21 16:28:48 +08:00

14 KiB
Raw Blame History

Feature Research

Domain: Profit/Loss analytics functions — platform-perspective P&L aggregation for a game/e-commerce platform Researched: 2026-03-21 Confidence: HIGH (based on direct codebase analysis of existing analytics + domain patterns)


Feature Landscape

Table Stakes (Users Expect These)

These are the non-negotiable capabilities that any reusable P&L service function must have. Operators calling these functions expect all of the following to "just work."

Feature Why Expected Complexity Notes
Revenue calculation (actual_amount + discount_amount) Core platform-perspective income; existing Dashboard already does this LOW Coupon discount must be added back: it's real value received
Game-pass order classification Orders with source_type=4, order_no LIKE 'GP%', or remark containing 'use_game_pass' need separate treatment LOW Logic already exists in finance.IsGamePassOrder — must be reused, not reimplemented
Game-pass value derivation (draw_count × activity_price) Zero-cash orders have economic value; existing logic computes it correctly LOW finance.ComputeGamePassValue exists; new functions must call it
Prize cost calculation with item-card multiplier Item cards double/triple prize value output; omitting multiplier understates cost MEDIUM finance.ComputePrizeCostWithMultiplier exists; value comes from system_item_cards.reward_multiplier_x1000
Profit = spending - prize_cost Core formula; operators see profit and profit_rate LOW finance.ComputeProfit exists and returns (int64, float64)
Time-range filter (optional) All existing dashboard analytics support time scoping LOW Must accept *time.Time for start/end; nil = all-time
User-dimension aggregation (one or many user IDs) Operators look up whale users; existing GetUserSpendingDashboard does single-user only MEDIUM New function must accept []int64; empty = all users
Activity-dimension aggregation (one activity ID) Per-activity P&L is the primary ops view; DashboardActivityProfitLoss does this at handler level MEDIUM New function wraps the same logic as a reusable service method
"All asset types" as default (nil asset type = all) PROJECT.md requires all params optional LOW Asset-type filter is additive; absence means no filter
Summary + per-asset-type breakdown in return value Operators need total AND split by asset class MEDIUM Return struct must carry both Summary and []AssetBreakdown
Refund/cancelled order exclusion Orders in status 3 (cancelled) or 4 (refunded) must NOT count as revenue LOW Already enforced in existing Dashboard SQL; must be replicated
Voided inventory exclusion Inventory with remark LIKE '%void%' or status=2 represents decomposed assets; must be excluded from prize cost LOW Pattern already established in existing queries

Differentiators (Competitive Advantage)

Features that go beyond what the existing dashboard provides, making the new service layer genuinely more reusable.

Feature Value Proposition Complexity Notes
Multi-user batch support ([]int64 user IDs) Existing dashboard only handles single user at a time; batch enables cross-user analytics (e.g., cohort P&L) MEDIUM Accept empty slice as "all users"; pass through as SQL IN clause
Composable filter struct (asset type, dimension ID, time range all optional) Callers can mix and match filters without writing bespoke queries MEDIUM Use a ProfitLossFilter options struct with pointer fields for optionality
Canonical AssetType enum covering all 5 types Points, coupon, item-card, physical-good, fragment — each type maps to different source tables MEDIUM Defining the enum properly prevents future callers guessing string/int values
Per-asset-type cost tracking (not just total) Operators want to see "how much did item-card prizes cost vs physical goods" — the Dashboard conflates them HIGH Requires separate GROUP BY legs or CASE-based aggregation per type
Canonical spending classification reuse New functions must call finance.ClassifyOrderSpending — not re-derive the rule — so calculation stays consistent everywhere LOW This is a correctness feature; prevents drift from the Dashboard numbers
Read-only DB enforcement (DbR) Statistics queries must route to the read replica; new functions must accept a *gorm.DB injected from the caller (already DbR-aware) LOW Function signature should accept db *gorm.DB so callers can pass h.repo.GetDbR()

Anti-Features (Commonly Requested, Often Problematic)

Feature Why Requested Why Problematic Alternative
Caching / memoization inside the service function "Stats queries are slow" The service layer is not the right place for caching; it would break test isolation and caller control over staleness Let the HTTP handler or a future cache layer wrap the call; the function stays pure
Real-time streaming / push notifications for P&L changes "Alert me when profit drops" Out of scope for v1 per PROJECT.md; adds event infrastructure complexity Defer to a future monitoring milestone
Automatic pagination inside the aggregate function "Return page X of users by profit" Pagination belongs at the API layer; the service function returning a flat result set is more composable Callers receive the full aggregated slice and paginate themselves
Reusing DashboardActivityProfitLoss handler logic directly "Don't duplicate code" The handler is tightly coupled to HTTP context, request parsing, and response formatting; pulling it into service layer would invert the dependency New functions in internal/service/finance/ are fresh implementations using shared finance.* primitives
Storing computed P&L in a materialized table "Pre-compute for speed" Requires write access and schema migration; risks stale data bugs Query on demand from DbR; optimize with indexes if needed later
Returning string-formatted amounts (e.g. "¥12.50") "UI-ready output" Formatting belongs in the presentation layer; service functions should return raw int64 cents Callers convert cents to display strings

Feature Dependencies

[Time-range filter]
    └──requires──> [Optional *time.Time parameters]

[Multi-user aggregation]
    └──requires──> [Revenue calculation]
    └──requires──> [Game-pass classification]
    └──requires──> [Prize cost with multiplier]
    └──requires──> [Refund/void exclusion]

[Activity-dimension aggregation]
    └──requires──> [Revenue calculation]
    └──requires──> [Game-pass classification]
    └──requires──> [Prize cost with multiplier]
    └──requires──> [Refund/void exclusion]

[Per-asset-type breakdown]
    └──requires──> [Canonical AssetType enum]
    └──enhances──> [User-dimension aggregation]
    └──enhances──> [Activity-dimension aggregation]

[Composable filter struct]
    └──enhances──> [User-dimension aggregation]
    └──enhances──> [Activity-dimension aggregation]

[Canonical spending classification reuse]
    └──requires──> [finance.ClassifyOrderSpending (existing)]
    └──prevents-conflict──> [Game-pass classification (must not re-derive)]

[Read-only DB enforcement]
    └──requires──> [Caller passes *gorm.DB from DbR]

Dependency Notes

  • Per-asset-type breakdown requires AssetType enum: Without a canonical type definition, callers and implementations will use ad-hoc int/string values that drift.
  • Multi-user aggregation requires all revenue/cost sub-features: The aggregation is just a GROUP BY wrapper around the same revenue and cost logic.
  • Canonical spending classification must reuse existing finance.* functions: The existing Dashboard and the new service functions must produce identical numbers for the same data. Any divergence in classification logic breaks operator trust in the analytics.
  • Composable filter struct enhances both dimension functions: A ProfitLossFilter struct with optional fields (asset types, IDs, time range) is shared between the user-dimension and activity-dimension functions — same struct, different dimension-ID field used.

MVP Definition

Launch With (v1)

The minimum that makes both service functions useful and correct.

  • ProfitLossFilter struct — optional asset types, optional user/activity IDs, optional time range
  • QueryUserProfitLoss(db, filter) (ProfitLossResult, error) — aggregates across specified user IDs
  • QueryActivityProfitLoss(db, filter) (ProfitLossResult, error) — aggregates for a single activity ID
  • ProfitLossResult struct — total revenue, total cost, profit, profit_rate, plus []AssetBreakdown
  • Canonical AssetType constants: Points, Coupon, ItemCard, PhysicalGood, Fragment
  • Revenue calculation reusing finance.ClassifyOrderSpending (existing)
  • Prize cost calculation reusing finance.ComputePrizeCostWithMultiplier (existing)
  • Refund (status 3/4) and voided inventory exclusion
  • Time-range filter applied consistently to both orders and inventory tables
  • Unit tests covering: normal order, game-pass order, mixed, empty result, nil filter

Add After Validation (v1.x)

  • Per-asset-type breakdown populated (requires extending SQL GROUP BY or running separate legs per type)
    • Trigger: ops team requests drill-down beyond total numbers
  • Fragment asset type cost integration via fragment_synthesis_logs
    • Trigger: fragment economy becomes significant in platform revenue reports
  • Batch activity IDs support ([]int64 activity IDs, not just one)
    • Trigger: ops needs cross-activity comparison in a single call

Future Consideration (v2+)

  • Caching wrapper (Redis TTL-based) around the query functions
    • Defer: not needed until query latency becomes user-visible (>2s)
  • Incremental / time-bucketed aggregation (daily snapshots stored in a stats table)
    • Defer: requires schema additions and migration planning
  • Douyin (livestream) order integration into the user-dimension function
    • Defer: currently only in the HTTP-layer spending leaderboard; integrating it requires joining douyin_orders which adds complexity and is outside the 5 declared asset types

Feature Prioritization Matrix

Feature Operator Value Implementation Cost Priority
Revenue calculation (reuse existing finance.*) HIGH LOW P1
Game-pass classification (reuse existing) HIGH LOW P1
Prize cost with multiplier (reuse existing) HIGH LOW P1
Refund/void exclusion HIGH LOW P1
Time-range filter HIGH LOW P1
User-dimension aggregation HIGH MEDIUM P1
Activity-dimension aggregation HIGH MEDIUM P1
ProfitLossFilter composable struct HIGH LOW P1
ProfitLossResult with Summary + Breakdown HIGH LOW P1
Canonical AssetType enum MEDIUM LOW P1
Multi-user batch ([]int64) MEDIUM LOW P1
Per-asset-type breakdown (5 types) MEDIUM HIGH P2
Fragment synthesis cost integration LOW MEDIUM P2
Batch activity IDs support LOW LOW P2
Read-only DB routing enforcement HIGH LOW P1 (design constraint, not optional)

Priority key:

  • P1: Must have for launch — without these the functions are not useful or correct
  • P2: Should have — adds analytical depth, add when P1 is proven
  • P3: Nice to have — future milestone

Competitor Feature Analysis

This is an internal platform analytics function, not a user-facing product. The relevant "competition" is the existing Dashboard code that this service layer must be consistent with and eventually replace as the canonical source of truth.

Feature Existing Dashboard (DashboardActivityProfitLoss) Existing Dashboard (GetUserSpendingDashboard) New Service Functions
Reusability None — HTTP handler only None — HTTP handler only Core goal: callable from anywhere
Multi-user support No — activity-scoped No — single user ID only Yes — []int64 user IDs
Asset-type breakdown Implicit (physical goods via inventory) Implicit Explicit enum + breakdown slice
Time-range Not supported Supported Supported (optional)
Spending classification Inline SQL CASE Inline SQL CASE Calls finance.ClassifyOrderSpending
Douyin/livestream Not included Included (separate leg) Out of scope for v1
Calculation consistency Source of truth today Source of truth today Must match exactly
Fragment asset type Not supported Not supported Enum defined; cost TBD in v1.x

Sources

  • Direct analysis of /internal/service/finance/profit_metrics.go — existing shared primitives
  • Direct analysis of /internal/api/admin/dashboard_activity.go — activity P&L implementation
  • Direct analysis of /internal/api/admin/dashboard_spending.go — user spending leaderboard
  • Direct analysis of /internal/api/admin/dashboard_user_spending.go — per-user spending drill-down
  • Direct analysis of GORM models: orders, user_inventory, user_points_ledger, user_coupon_ledger, fragment_synthesis_logs
  • PROJECT.md requirements (validated requirements section)

Feature research for: Bindbox Game profit/loss analytics service layer Researched: 2026-03-21