Files
zarinpal-analytics-backend/docs/decisions/0007-calendar-aware-analytics-projections.md

3.1 KiB

ADR 0007: Calendar-aware analytics projections without initial table partitioning

  • Status: Accepted
  • Date: 2026-08-20

Context

The source contains approximately 2.2 million payment-attempt rows, but attempts are only one domain entity. Merchant, terminal, session, attempt, import, identity, and calendar concepts require separate normalized relations connected by constraints. The product also needs daily, Jalali-month, Persian-season, and Iranian holiday/event analysis, including evidence that may later support bounded AI recommendations.

Calculating every dashboard result from normalized attempt rows on every request creates avoidable latency. Conversely, splitting attempts into unrelated tables by month/category or adding PostgreSQL partitions before measuring query and maintenance behavior introduces complexity without evidence. Some metrics are additive across days, while distinct payer-token counts and percentiles are not.

Decision

Keep normalized business/source-of-truth relations: categories, merchants, merchant users, terminals, payment sessions, payment attempts, and import provenance. The attempt relation retains one row per source attempt; normalization does not arbitrarily distribute a single entity's rows across category/month tables.

Add a versioned calendar model containing Gregorian and Jalali date attributes, Persian seasons, weekdays, and curated named events such as Nowruz. Calendar-event definitions have explicit provenance/version and are deterministic inputs to analytics, not LLM-generated labels.

Build idempotent, rebuildable daily base projections containing additive components such as session count, attempt count, successful count, amount sum, and fee sum as approved by the metric catalogue. Derive month/season/event totals by grouping these components through the calendar model and recompute ratios from their numerators and denominators.

Create separate metric-specific period projections only for non-additive metrics or query paths proven expensive—for example, exact unique payer-token counts cannot be obtained by summing daily distinct counts. Projection rows record source import/version and freshness.

Keep normalized fact tables unpartitioned initially. Add targeted indexes from approved API access paths and verified query plans. Introduce PostgreSQL partitioning only through a future ADR supported by measured table growth, query latency, vacuum/maintenance behavior, or retention operations.

Consequences

  • The normalized ERD remains semantically correct while dashboards read smaller, purpose-built projections.
  • Daily components support many calendar groupings without precomputing every possible period.
  • Calendar definitions and metric provenance make future AI observations traceable.
  • A single observed Nowruz period may be reported as an observation but not claimed as recurring seasonality; multiple comparable years are needed for that claim.
  • Projection refresh, failure, stale-state visibility, and reconciliation tests become required operational behavior.
  • Schema/index/projection details remain subject to data profiling and approved metric/API requirements.