5.3 KiB
PostgreSQL Physical Schema Proposal
- Status: Draft for decision batch
- Scope: Data foundation only
- Implementation authorization: Not granted
Principles
- Normalize business entities and enforce relationships with foreign keys.
- Preserve source identifiers as text and use internal surrogate keys for joins.
- Keep payment session and payment attempt grains separate.
- Use UTC
timestamptzfor canonical instants andbigintfor Rial amounts. - Keep normalized facts as source of truth; analytics projections are rebuildable.
- No initial table partitioning. Add indexes from approved access paths and measured query plans.
- The CSV importer is a one-time administrative bootstrap, not a production ingestion service.
Proposed tables
Provenance and reference
| Table | Purpose | Key constraints |
|---|---|---|
import_runs |
One bootstrap/recovery execution | unique source checksum + schema/importer policy; explicit lifecycle |
import_issues |
Aggregated validation/quality evidence | FK to import run; severity/rule/count/evidence |
merchant_categories |
Five source business categories | unique source category key |
psps |
Opaque PSP reference codes | unique source PSP code |
issuer_banks |
Opaque bank reference codes | unique source bank code |
calendar_dates |
Gregorian/Jalali reporting attributes | Gregorian date PK; unique Jalali date tuple |
calendar_events |
Versioned holidays/events | stable event code + source version |
calendar_date_events |
Many-to-many event membership | unique date/event pair; FKs |
Switch response codes remain nullable text on attempts until an authoritative description catalogue exists. Normalizing an undocumented 97.4%-missing field into a lookup table adds little value.
Merchant identity
| Table | Purpose | Key constraints |
|---|---|---|
merchants |
Merchant profile and category membership | unique source merchant key; FK category |
terminals |
Merchant-owned payment terminals | unique source terminal key; FK merchant |
merchant_users |
Login identities | unique normalized username; FK merchant; password hash only |
auth_sessions |
Revocable authenticated sessions | hashed opaque session token; FK user; expiry/revocation |
Synthetic profile fields carry an explicit profile_is_synthetic marker. Credentials are generated outside Git.
Payment facts
| Table | Grain and important fields | Key constraints/index direction |
|---|---|---|
payment_sessions |
One source session: merchant, terminal, amount in Rial, source status, created/expires instants, import provenance | unique source session key; FKs; merchant/time and terminal/time indexes |
payment_attempts |
One try: session, sequence, attempt status, PSP/bank, response, opaque payer token, verification type, timings, fee, attempt/verified/settled instants | unique (payment_session_id, try_seq); FKs; session index; measured analytical indexes later |
adjusted_fee uses fixed precision and is labeled as an obfuscated relative value. The physical precision is finalized from exact decimal profiling before migration approval.
Rebuildable analytics projections
| Table family | Grain | Purpose |
|---|---|---|
merchant_day_metrics |
merchant + Tehran local date | Additive counts/sums and metric numerators/denominators |
merchant_day_*_breakdowns |
merchant + date + approved dimension | Only metric-catalogue breakdowns proven useful, such as PSP/failure groups |
merchant_period_* |
merchant + approved period/event | Non-additive or measured expensive metrics only |
cohort_period_* |
approved cohort + period | Suppressed peer benchmark inputs/outputs with provenance and freshness |
Projection names/columns are finalized by the analytics metric catalogue. Avoid a generic key/value metrics table because it weakens types, constraints, and discoverability.
Initial index policy
- Unique indexes implement source identities and natural relationship constraints.
payment_sessions (merchant_id, created_at DESC)supports merchant period queries.payment_sessions (terminal_id, created_at DESC)supports terminal drill-down.payment_attempts (payment_session_id, try_seq)is unique and supports session traceability.- Projection primary keys start with merchant/cohort and period.
- Additional status, PSP, bank, payer-token, or timestamp indexes require an approved API query and
EXPLAIN (ANALYZE, BUFFERS)evidence; do not index every CSV column.
Deletion and mutation policy
- Payment/import provenance uses restrictive foreign keys; no cascading deletion of analytical facts from merchant/profile operations.
- Imported source facts are immutable after a completed import. Corrections create a new versioned recovery/import decision.
- Synthetic display profiles and credentials can change independently from source facts.
- Derived projections may be rebuilt and atomically replaced; freshness/provenance prevents stale data from appearing current.
Production population boundary
After the one-time CSV bootstrap, ZarinPal—not this project—writes future production payment records to PostgreSQL. The exact contract is still open: ZarinPal may write this normalized schema directly or expose an existing authoritative schema for read-only analytics. This project will not invent recurring ETL until that boundary is answered.