Design for review · 05 / 11

Proposed architecture. Pilot products, privacy policy and cost-risk acceptance remain open.

Reward calculation and historical revisions

Separate the questions

One evaluator produces captured spend, minimum qualification, cap consumption, estimated base rewards, estimated bonus rewards and reporting portions. These are distinct outputs. A bank MCC, inferred MCC and user category have distinct provenance.

The engine accepts immutable rule data with a schema version and a finite set of supported operators. Rules specify eligibility, date basis, aggregation, rounding order, reward components, cap-sharing groups, selection conditions and qualification gates. Rule data cannot execute code. A new operator requires a reviewed engine release; a new card using existing operators can be a reviewed data publication.

Evaluation order

  1. Select the exact product, effective rule versions and dated account settings. If date basis or rule coverage is unresolved, retain facts and return unavailable results for affected components.
  2. Resolve evidence into known, excluded or conditional predicates. A guessed MCC never upgrades evidence to bank-confirmed.
  3. Build separate minimum, count and reward ledgers using each component's date basis and eligibility terms.
  4. Apply issuer-specific aggregation, then the specified rounding and base/bonus calculations. Never infer transaction blocking from an advertised rate denominator.
  5. Allocate shared and nested caps in the verified ordering. If ordering is unknown, withhold affected capacity rather than invent first-come allocation. Resolve ties deterministically from the approved rule.
  6. Evaluate all monthly or statement-period gates required by a quarter. Retrospective tiers recompute affected periods. An unmet future gate remains conditional.
  7. Process credits according to verified clawback and capacity-reopening rules. Unknown credit linkage or terms can make rewards uncertain without hiding the credit from net spend.
  8. Build per-purchase disjoint reporting portions, projections and explanation reasons. Publish them as one revision.

Period model

Store UTC timestamps with original offset evidence and Singapore-local date derivation. Reporting months use Asia/Singapore and half-open intervals [start, next_start). A statement cycle ending on day D runs from the day after the prior closing date through D inclusive. Clamp D to the last day in a short month; date selection changes are versioned. A Miles card closing on 22 Oct places both 7 Oct and 18 Oct in 23 Sep–22 Oct.

Purchase date drives captured-spend views. Reward allocation may require posting date or an issuer-specific date. Preserve both. Never substitute purchase date for an unknown posting date and declare a reward period settled. Calendar, statement, quarter and campaign periods have separate keys.

Reporting invariants

For positive captured purchases P, disjoint bonus-covered B, no-bonus N and unclear/conditional U satisfy P = B + N + U. Covered share is round(100 × B / P). A zero denominator yields no percentage. Credits C are separate and net spend is P − C; credits do not shrink the share denominator. Unreliable foreign conversion stays outside a falsely complete SGD total, with original currency and a completeness flag visible.

Overlapping bonus programmes may produce several reward components, but their covered amount contributes only once to B. Points, miles and cashback remain separate units. An exhausted category never implies all card categories are exhausted.

Required fixtures

Illustrative input Required output
S$14.99, whole-S$5 blocks, 10 points per block S$10 bonus-bearing, 20 points, S$4.99 known block remainder; captured spend S$14.99
Two S$2.50 purchases under per-purchase blocking Zero blocks; never combine into S$5
S$50 crossing a cap: S$20 at total 4 miles/S$1, S$30 at base 0.4 92 miles total; 40% covered, no duplicate base addition
Minimum S$1,393 of S$1,600, S$98 qualification unknown S$207 provisional; unknown amount remains captured
Online capacity S$180; mobile capacity S$400 Two allowances; no pooled S$580 headline
Quarter requires S$600 and 10 purchases each period; current count nine Conditional quarter, one purchase outstanding; later period cannot repair failure
October B=2,380, N=220, U=50 P=2,650; rounded covered share 90%; U remains visible after month end
Category correction Insurance, MCC unknown User category changes; bank MCC and eligibility remain unknown
Late refund with unknown reopening terms Credit reduces net once; affected reward capacity stays uncertain

These fixtures do not prove any real card's terms. Each enabled product needs dated official terms and verified fixtures for every applicable operator.

Revision reasons

A revision records affected transaction IDs, previous and new rule versions, correction IDs, engine version and reason codes. For example, withdrawing an inferred code for a S$50 Marketplace purchase moves its portion from no-bonus to unclear while leaving captured spend unchanged. Current rules do not silently overwrite historical rules. Late alerts recalculate their affected periods and dependent quarterly gates through bounded jobs.