12 KiB
12 KiB
Analytics Engine Capability Spec
- Status: Draft — not approved for implementation
- Capability: Analytics engine
- Depends on: Approved Data Foundation specification
- Last updated: 2026-08-20
Objective
Turn normalized ZarinPal payment facts into trustworthy, merchant-scoped analytics that explain payment performance, verification gaps, retry outcomes, processing time, fees, trends, and same-category position. Deterministic calculations are the product source of truth; later AI assistance may explain or recommend actions from sanitized aggregates but cannot define or alter metrics.
Actors and consumers
- Authenticated merchant: sees detailed metrics and drill-downs only for its merchant boundary.
- Merchant API: requests versioned analytics sections and exposes typed results/events.
- Dashboard: renders sections independently as they become ready.
- Analytics worker: calculates idempotent section results from PostgreSQL and caches approved aggregates in Redis.
- Future AI adapter: post-MVP consumer of minimized, approved aggregate facts only.
In scope
- Daily, Saturday–Friday weekly, Jalali-month, and Persian-season analytics in
Asia/Tehran - Overview, payment performance, fees, and peer-comparison sections
- Payment success and volume with correct
Paid/Verifiedsemantics - Verification completion and unverified-paid gaps
- Retry recovery and attempt/session diagnostics
- Processing-time p50/p95 with sample and coverage
- Real merchant fee amounts and paid fee rate at session grain
- Current-period trends against an approved equivalent baseline
- Same-category merchant-weighted peer median and performance percentile
- Lazy, bounded parallel calculation with partial SSE delivery
- Versioned 30-day Redis cache and PostgreSQL revision invalidation
- Traceability from aggregate metrics to permitted merchant-owned evidence
Out of scope for MVP
- Holiday or named-event reporting
- AI-generated recommendations or free-form LLM analysis
- Forecasting, causal claims, or automated campaign execution
- Arbitrary merchant-created formulas or cross-merchant filters
- Real-time streaming payment ingestion owned by this service
- Persisting calculated analytics as PostgreSQL business facts
- Public or unauthenticated analytics
Canonical sections
| Section id | Responsibility | Minimum result |
|---|---|---|
overview |
Merchant-level outcome and trend summary | bank-paid sessions/amount, success rate, verification completion, selected period, prior-period comparison |
payment_performance |
Status, retry, and timing analysis | paid/verified/unverified/failed/reversed counts, retry recovery, init/verify p50/p95 with coverage |
fees |
Merchant fee analysis | paid fee amount, paid amount denominator, paid fee rate, period trend |
peer_comparison |
Privacy-bounded same-category benchmark | eligible peer count/session count, cohort median, direction-aware merchant percentile or typed suppression |
Every section uses a versioned typed contract and has independent queued, calculating, ready, failed, and where applicable suppressed states.
Metric and period contract
METRIC-CATALOGUE.mdversion0.3.0is canonical for formulas, units, grains, missing-data behavior, and peer direction.PaidandVerifiedboth mean bank payment succeeded; onlyVerifiedis finalized. Never labelPaidas failure.- Amount and fee calculations count each eligible payment session once. Attempt rows cannot multiply session values.
- Rates are calculated from summed numerator and denominator components for the selected period, never averaged from daily percentages.
- Timing percentiles exclude null values and expose sample size and non-null coverage; missing time never becomes zero.
- Every result states the exact Tehran-local half-open period, Jalali label, unit, sample/denominator, source revision, metric-definition version, and calculation timestamp.
- Approved period types are one Tehran-local day, one Saturday–Friday week, one Jalali month, or one Persian season. Holiday/event dimensions do not exist in the MVP contract.
Peer comparison contract
- Eligible peers are other merchants in the active merchant's category for the same selected period.
- Suppress the peer result unless at least 10 other merchants and 1,000 combined peer sessions contribute.
- Calculate each peer merchant's metric independently and take the median of those merchant values; do not transaction-weight the cohort.
- Return the cohort median and active merchant's performance percentile. Higher displayed percentile always means better, so processing-time and fee-rate ranking is inverted.
- Benchmark payment success, verification completion, retry recovery, init/verify p50/p95, and paid fee rate.
- Never return peer identities, individual values/ranks, rows, or arbitrary filters that enable differencing attacks.
Calculation and cache lifecycle
- An idempotent authenticated start request resolves merchant, selected period, merchant revision, cohort revision, and metric-definition version.
- Equivalent requests map to one current analytics run and do not enqueue duplicate work.
- Missing section results are enqueued together and calculated independently by the Go worker through Asynq
v0.26.0, explicitly a pre-1.0 dependency pinned for compatibility. - Worker limits default to 16 jobs globally and four per merchant. All four core sections begin in bounded parallel and become visible independently.
- Each attempt times out after 60 seconds. Transient failures receive at most two retries using exponential backoff of approximately one then two seconds plus jitter; deterministic failures are not retried.
- Successful section results are published atomically to Redis under revisioned/versioned keys with a 30-day TTL. PostgreSQL facts and revision rows remain authoritative.
- Relevant fact inserts advance the affected merchant and category/cohort revisions in PostgreSQL. New requests use new keys; old keys expire without scans.
- Redis loss causes safe lazy recomputation. It never causes loss of payment facts or metric definitions.
Partial delivery contract
- A separate authenticated SSE connection streams run/section state and each completed section immediately.
- SSE sends a heartbeat every 15 seconds and retains authorized replay events for one hour.
- Reconnect uses the last event id where retained; otherwise the client fetches the current run snapshot and reopens the stream.
- One failed section never hides ready siblings. Manual retry targets only an exhausted failed section and cannot clear successful results.
- Start limits are 10 requests/minute with burst 3; manual retry limits are 3 requests/10 minutes with burst 1; SSE limits are two concurrent connections/user and four/merchant. Independent user and merchant buckets return
429withRetry-Afterbefore work is queued.
Traceability and privacy
- Merchant-owned aggregate cards link to definitions and, where approved, tenant-scoped session/attempt evidence that reconciles to the aggregate.
- Payer-card tokens remain internal, opaque, merchant-scoped inputs and never appear in APIs, logs, exports, caches, peer results, or AI payloads.
- Redis values contain approved aggregates and trace metadata only, not raw payment rows or credentials.
- Cross-merchant calculations execute server-side and return only approved aggregate/suppressed contracts.
- Future AvalAI prompts may receive minimized aggregates only after a separate AI capability approval.
Failure and edge behavior
- Zero, undefined denominator, unavailable source, peer suppression, calculation failure, and not-yet-ready are distinct typed states.
- Unknown future payment statuses are excluded from success numerator/denominator and reported through coverage evidence until classified.
Reversedremains outside success and is displayed separately when non-zero.- Empty approved periods return a valid empty result, not fabricated zero rates or a system failure.
- Section publication is atomic; partial/failed calculations never overwrite a valid ready cache entry.
- Database, Redis, queue, timeout, and authorization errors are distinguishable internally while merchant-facing errors remain safe and actionable.
Observability
- Measure section queue delay, calculation duration, query duration, cache hit/miss, retry count, failure class, SSE delivery/reconnect, and suppression rate.
- Correlate start request, analytics run, section job, cache key version, and SSE event without logging sensitive merchant rows or payer tokens.
- Record PostgreSQL query plans during performance verification and add indexes only from measured access paths.
- Define service-level objectives and alert thresholds in the Platform Operations spec, not implicitly in worker code.
Acceptance criteria
- Every metric reconciles to an independent PostgreSQL query over normalized facts for representative merchants and all approved period types.
- Session-grain amount/fee totals remain unchanged when a session has multiple attempts.
PaidplusVerifiedsuccess, verification completion, retry recovery, and timing coverage match catalogue definitions.- Saturday–Friday, Jalali-month, and Persian-season boundaries match
calendar_datesand API half-open ranges. - First access starts all missing sections once; ready sections stream independently and equivalent concurrent starts deduplicate.
- Cache hits avoid repeating source aggregation; merchant/cohort revision changes select fresh keys and recompute only missing sections.
- Redis flush causes correct cold-cache rebuilding without source-data loss.
- Timeouts, transient retry limits/backoff, failed-section isolation, manual retry, rate limits, heartbeat, replay, and snapshot fallback behave as specified.
- Peer results exclude the active merchant, use equal merchant weighting, obey both suppression thresholds, and never expose individual peers.
- Tenant-bound drill-downs reconcile to aggregates and cannot expose another merchant through parameter manipulation.
- No raw payer token, credential, cross-tenant row, or AvalAI secret enters results, logs, cache payloads, or browser data.
Boundaries
- Always: use catalogue definitions, preserve grain, enforce tenant scope, expose period/unit/coverage/version, calculate idempotently, and keep results reproducible.
- Ask first: add/change a metric, period, peer cohort, threshold, cache key/version, drill-down, AI input, or cross-service interface.
- Never: average rates, multiply session money by attempts, classify
Paidas failure, expose peer rows, trust client tenant ids, or let AI become the metric source of truth.
Resolved decisions
- Normalized PostgreSQL facts are authoritative; Redis is a 30-day disposable analytics cache.
- Four sections calculate lazily and in bounded parallel, with partial authenticated SSE delivery.
- Reporting covers day, Iranian week, Jalali month, and Persian season only.
- Peer comparison uses same-category merchant-weighted medians and direction-aware percentiles with the approved suppression threshold.
- Payment lifecycle, retry, timing, and real-fee definitions follow
METRIC-CATALOGUE.mdand ADR 0013. - Asynq
v0.26.0is pinned and acknowledged as pre-1.0; jobs have bounded concurrency, timeouts, rate limits, and exponential-backoff retries.
Open product decisions
- Default reporting period on first dashboard access, especially when the current period has no source data
- Default previous-period comparison behavior when an equivalent baseline is empty or incomplete
- Exact overview KPI order and which deterministic insight appears first
- Which tenant-scoped drill-down tables are MVP-visible versus definition-only
- Deterministic threshold/severity rules for surfacing actionable findings