docs: define bootstrap and schema proposal
This commit is contained in:
@@ -16,6 +16,7 @@ For every Go-related task, always invoke and follow the `golang-how-to` skill, t
|
||||
- Use integer minor currency units or an explicitly documented fixed-precision representation; never floating point for money.
|
||||
- Interpret timezone-naive challenge timestamps as `Asia/Tehran`, preserve their source provenance, and normalize canonical database/API timestamps to UTC `timestamptz`/ISO 8601. Any change to this assumption requires a new import version and ADR.
|
||||
- Raw imports are immutable inputs; corrections are new import versions, not silent mutation.
|
||||
- CSV ingestion is a one-time bootstrap/recovery command only. Never add a file watcher, polling schedule, recurring CSV ETL, or ownership of ZarinPal's production writes without a new approved spec.
|
||||
- `adjusted_fee` is obfuscated and may be used only for relative analysis.
|
||||
|
||||
## Go baseline after approval
|
||||
|
||||
@@ -12,7 +12,7 @@ Place the provided archive at:
|
||||
data/incoming/challenge_data.csv.gz
|
||||
```
|
||||
|
||||
The entire `data/incoming/` payload is ignored by Git. Future import tooling will read from this location (or an explicitly supplied path), stream the compressed CSV, validate it, and write a tracked import manifest containing the source checksum, schema version, row counts, quality results, and importer revision. The raw file and resulting PostgreSQL data are not committed.
|
||||
The entire `data/incoming/` payload is ignored by Git. The one-time bootstrap importer will read from this location (or an explicitly supplied path), stream the compressed CSV, validate it, and write an import manifest containing the source checksum, schema version, row counts, quality results, and importer revision. It is an administrative backfill/recovery command, not a scheduled ingestion job. Future production payment data is populated in PostgreSQL by ZarinPal through an upstream contract outside this project's scope. The raw file and resulting PostgreSQL data are not committed.
|
||||
|
||||
## Planned runnable boundaries
|
||||
|
||||
@@ -24,4 +24,3 @@ One repository may expose several small `cmd/` entry points while sharing domain
|
||||
- analytics refresh/background job, only if the approved spec requires one
|
||||
|
||||
The exact package layout is deferred until the Go architecture and DI decisions are approved.
|
||||
|
||||
|
||||
@@ -6,11 +6,12 @@
|
||||
|
||||
## Objective
|
||||
|
||||
Repeatably ingest the supplied payment-attempt archive into PostgreSQL, preserve provenance and data quality evidence, model business entities separately, and provide a trustworthy base for authentication and deterministic analytics.
|
||||
Perform a one-time, repeatable bootstrap of the supplied payment-attempt archive into PostgreSQL, preserve provenance and data quality evidence, model business entities separately, and provide a trustworthy base for authentication and deterministic analytics. Future production payment records are written to PostgreSQL by ZarinPal under an external upstream contract; recurring source ingestion is not owned by this project.
|
||||
|
||||
## In scope
|
||||
|
||||
- Streamed `.csv.gz` ingestion from a local, Git-ignored input path
|
||||
- One-time administrative bootstrap/recovery execution; no ingestion scheduler or file watcher
|
||||
- Import runs, checksums, schema version, row counts, rejection/error reporting
|
||||
- Normalized merchant, category, terminal, payment-session, payment-attempt, and account/profile concepts
|
||||
- Versioned Gregorian/Jalali calendar dates and curated Iranian event/holiday periods
|
||||
@@ -26,6 +27,7 @@ Repeatably ingest the supplied payment-attempt archive into PostgreSQL, preserve
|
||||
- Merchant-facing API endpoints
|
||||
- AI recommendations
|
||||
- Production backup mechanics (owned by platform operations)
|
||||
- Recurring CSV ingestion, polling, CDC, or ownership of ZarinPal's production payment-write path
|
||||
- Treating the challenge archive or local database as version-controlled source
|
||||
|
||||
## Proposed logical model (technology-neutral)
|
||||
@@ -97,7 +99,7 @@ erDiagram
|
||||
timestamptz try_created_at
|
||||
timestamptz verified_at
|
||||
timestamptz settled_at
|
||||
int expire_in
|
||||
timestamptz expires_at
|
||||
}
|
||||
IMPORT_RUN {
|
||||
bigint id PK
|
||||
@@ -151,6 +153,8 @@ This ERD is a logical baseline, not an approved physical schema. In particular,
|
||||
|
||||
## Import contract
|
||||
|
||||
This contract applies only to the one-time challenge-data bootstrap and explicit recovery re-runs. It is not a periodic production pipeline.
|
||||
|
||||
1. Accept an explicit path, defaulting locally to `data/incoming/challenge_data.csv.gz`.
|
||||
2. Calculate SHA-256 before import and identify an already completed import of the same schema/checksum.
|
||||
3. Validate headers and supported schema version before database writes.
|
||||
@@ -158,7 +162,8 @@ This ERD is a logical baseline, not an approved physical schema. In particular,
|
||||
5. Validate uniqueness of `(session_key, try_seq)`, referential consistency, parseability, null rates, ranges, timestamp ordering, and merchant/terminal/category relationships.
|
||||
6. Promote accepted data transactionally or through a resumable, explicitly versioned workflow; a failed run cannot appear complete.
|
||||
7. Emit a machine-readable and human-readable import report. Do not log raw card keys or credentials.
|
||||
8. Populate and validate the approved calendar range, then rebuild affected analytics projections idempotently after source promotion. Projection failure must not make an incomplete projection appear fresh.
|
||||
8. Populate and validate the approved calendar range, then build affected analytics projections idempotently after source promotion. Projection failure must not make an incomplete projection appear fresh.
|
||||
9. Never schedule, watch, or poll for another CSV. After bootstrap, production source-row creation is an external ZarinPal responsibility. Analytics freshness over those future rows has a separate read/refresh contract.
|
||||
|
||||
## Synthetic merchant/account rules
|
||||
|
||||
|
||||
84
docs/PHYSICAL-SCHEMA-PROPOSAL.md
Normal file
84
docs/PHYSICAL-SCHEMA-PROPOSAL.md
Normal file
@@ -0,0 +1,84 @@
|
||||
# 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 `timestamptz` for canonical instants and `bigint` for 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.
|
||||
|
||||
Reference in New Issue
Block a user