438 lines
21 KiB
Markdown
438 lines
21 KiB
Markdown
# Amazon A/R Aging Dashboard — System Guide
|
||
|
||
Complete reference for how the month-end Amazon Accounts Receivable system works: the flow,
|
||
every module, every screen, every calculation, and every rule.
|
||
|
||
> **Benchmark:** the engine reproduces the Finance team's manual workbook exactly —
|
||
> **USA January 2026 = 11,110,433** (`Detail!D11`), and all **13 marketplaces reconcile to the penny**.
|
||
|
||
---
|
||
|
||
## 1. What this system does
|
||
|
||
Finance downloads Amazon **Custom Unified Transaction** reports each month (one or more files per
|
||
marketplace). Previously they hand-built a 283 MB Excel workbook to answer one question:
|
||
|
||
> **How much does Amazon still owe us at month-end?**
|
||
|
||
This system automates that end to end: upload the files → it parses, classifies, calculates,
|
||
reconciles → you get a dashboard and a downloadable audit workbook.
|
||
|
||
**Scope:** Amazon only (no Walmart/TikTok/Temu/Shein/eBay/Shopify). 13 marketplaces supported.
|
||
|
||
---
|
||
|
||
## 2. Quick start
|
||
|
||
```bash
|
||
# both servers (from the project root)
|
||
python3 -m uvicorn app.api.main:app --app-dir ar-aging-app/backend --reload --port 8000
|
||
npm --prefix ar-aging-app/frontend run dev # http://localhost:5173
|
||
```
|
||
|
||
Or use the preview launcher, which starts both from `.claude/launch.json`.
|
||
|
||
```bash
|
||
# headless engine run
|
||
cd ar-aging-app/backend
|
||
python cli.py "/path/file1.xlsx" "/path/file2.xlsx" --month-end 2026-01-31 --lag 2
|
||
|
||
# tests
|
||
python -m pytest tests -m "not integration" # 64 fast tests
|
||
python -m pytest tests -m integration # 12 tests against the real sample files
|
||
```
|
||
|
||
---
|
||
|
||
## 3. End-to-end flow
|
||
|
||
```mermaid
|
||
flowchart TD
|
||
A["Amazon reports<br/>(.xlsx per marketplace)"] --> B[Upload & validate]
|
||
B --> C[Parse & normalize]
|
||
C --> D[Aggregate by settlement]
|
||
D --> E[Classify paid vs receivable]
|
||
E --> F[Compute receivable]
|
||
F --> G[Journal decomposition]
|
||
G --> H[(SQLite<br/>persisted results)]
|
||
H --> I[Dashboard tabs]
|
||
H --> J[Excel exports]
|
||
|
||
K[Finance inputs<br/>opening balance · FX · clearing-lag<br/>reserve · control amounts] --> E
|
||
K --> F
|
||
K --> I
|
||
|
||
style H fill:#ECEAFC,stroke:#6D5DE8
|
||
style I fill:#E7E5FB,stroke:#6D5DE8
|
||
style J fill:#E7E5FB,stroke:#6D5DE8
|
||
```
|
||
|
||
### The nine processing stages
|
||
|
||
Each stage reports live progress (rows done / total + ETA) to the dashboard.
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
S1[1 Reading<br/>workbook] --> S2[2 Validating<br/>columns]
|
||
S2 --> S3[3 Combining<br/>transactions]
|
||
S3 --> S4[4 Detecting<br/>duplicates]
|
||
S4 --> S5[5 Calculating<br/>settlements]
|
||
S5 --> S6[6 Creating<br/>receivable]
|
||
S6 --> S7[7 Reconciling<br/>totals]
|
||
S7 --> S8[8 Persisting<br/>transactions]
|
||
S8 --> S9[9 Building<br/>journal entry]
|
||
```
|
||
|
||
| # | Stage | What happens |
|
||
|---|---|---|
|
||
| 1 | **Reading workbook** | Opens each file, auto-detects the raw data sheet (ignores user-made pivot tabs) |
|
||
| 2 | **Validating columns** | Maps localized headers to canonical fields; flags unmapped/missing |
|
||
| 3 | **Combining transactions** | Streams all files into one normalized record set |
|
||
| 4 | **Detecting duplicates** | Fingerprints rows; counts repeats (never deletes them) |
|
||
| 5 | **Calculating settlements** | Groups by settlement id; marks each paid or receivable |
|
||
| 6 | **Creating receivable** | Sums open settlements; applies reserve, rounding and FX |
|
||
| 7 | **Reconciling totals** | Checks the identity `uploaded = receivable + paid + transfers` |
|
||
| 8 | **Persisting transactions** | Bulk-inserts every row with full source traceability |
|
||
| 9 | **Building journal entry** | Decomposes activity into GL accounts, per marketplace |
|
||
|
||
---
|
||
|
||
## 4. The calculation — four rules
|
||
|
||
Everything the dashboard shows derives from these. **`total` (column AD) is the net amount of each
|
||
row** and is the figure that gets summed.
|
||
|
||
### Rule 1 — Settlement classification (which money is still owed)
|
||
|
||
Amazon pays in settlement batches. When a batch closes, the next opens and Amazon disburses the
|
||
prior one via a **`Transfer`** row *tagged with the next batch's id*.
|
||
|
||
> A settlement stays **receivable** until the payout that clears it has actually **reached the bank**
|
||
> by month-end.
|
||
|
||
```mermaid
|
||
flowchart TD
|
||
T["Transfer row<br/>(a bank payout)"] --> D{"payout date ≤<br/>month_end − clearing_lag?"}
|
||
D -->|Yes| R["RECEIVED<br/>→ sets the paid boundary"]
|
||
D -->|No| I["IN TRANSIT<br/>→ its settlement stays receivable"]
|
||
R --> B["paid_boundary = max settlement_id<br/>over received payouts"]
|
||
B --> C{"settlement_id ≥ boundary?"}
|
||
C -->|Yes| RECV[RECEIVABLE]
|
||
C -->|No| PAID[PAID]
|
||
|
||
style RECV fill:#DCFCE7,stroke:#2FA875
|
||
style PAID fill:#F1F1F6,stroke:#868AA0
|
||
style I fill:#FAF0DA,stroke:#B37A1B
|
||
```
|
||
|
||
`clearing_lag` defaults to **2 days** and is adjustable per closing. Every payout is listed on the
|
||
**Settlement Reconciliation** tab where you can override its received/in-transit status.
|
||
|
||
### Rule 2 — The receivable base
|
||
|
||
```
|
||
additional_sales = Σ total
|
||
WHERE settlement is receivable
|
||
AND account_type ∈ {Standard Orders, Invoiced Orders, All Orders}
|
||
AND type ≠ Transfer
|
||
```
|
||
|
||
### Rule 3 — Closing receivable
|
||
|
||
Two independent methods, which must agree:
|
||
|
||
```
|
||
# Settlement method (the workbook's method — the benchmark)
|
||
receivable_local = ROUND(reserve + additional_sales, 0)
|
||
receivable_usd = receivable_local × fx_rate
|
||
|
||
# Roll-forward method (the AR Ledger)
|
||
closing = opening_balance + net_revenue − payouts_received
|
||
```
|
||
|
||
#### Opening balance — three modes
|
||
|
||
Chosen when the closing is created (and changeable any time on the AR Ledger tab):
|
||
|
||
| Mode | Behaviour |
|
||
|---|---|
|
||
| **Zero** *(default)* | Every marketplace opens at 0 — correct for a first-ever closing |
|
||
| **Carry forward** | Copies each marketplace's closing receivable from a chosen prior closing. Falls back to zero if no processed prior exists |
|
||
| **Manual** | Opens at 0; you type each marketplace's balance on the AR Ledger tab after processing |
|
||
|
||
### Rule 4 — Reconciliation identity
|
||
|
||
Every uploaded row lands in exactly one bucket, so this must always hold:
|
||
|
||
```
|
||
uploaded_total = receivable_orders + paid_orders + transfers_total
|
||
```
|
||
|
||
Status shows **Reconciled** when the difference is within tolerance (default **$0.01**).
|
||
|
||
---
|
||
|
||
## 5. Architecture
|
||
|
||
```mermaid
|
||
flowchart TB
|
||
subgraph FE["Frontend — React + TypeScript + Vite"]
|
||
P1[Dashboard] --- P2[New Closing] --- P3[Closing tabs] --- P4[Settings]
|
||
end
|
||
subgraph API["Backend — FastAPI"]
|
||
R1[sessions/files/processing] --- R2[results/ar/control] --- R3[analytics] --- R4[export/settings]
|
||
end
|
||
subgraph CORE["Engine — pure Python, no web deps"]
|
||
C1[readers] --> C2[column_map + i18n]
|
||
C2 --> C3[pipeline]
|
||
C3 --> C4[settlements]
|
||
C4 --> C5[receivable]
|
||
C5 --> C6[movement + journal]
|
||
C6 --> C7[reconciliation + validation]
|
||
C7 --> C8[excel_export + summary_export]
|
||
end
|
||
DB[(SQLite / Postgres)]
|
||
|
||
FE -->|"/api (Vite proxy)"| API
|
||
API --> CORE
|
||
API --> DB
|
||
CORE --> DB
|
||
|
||
style CORE fill:#ECEAFC,stroke:#6D5DE8
|
||
```
|
||
|
||
**Key principle:** the engine (`app/core/`) has no web or database dependencies. It can run headless
|
||
via `cli.py`, which is what the integration tests exercise.
|
||
|
||
---
|
||
|
||
## 6. Backend modules — each one defined
|
||
|
||
### `app/core/` — the engine
|
||
|
||
| Module | Purpose |
|
||
|---|---|
|
||
| **`readers.py`** | Factory choosing the parser: `python-calamine` (fast, Rust) by default, streaming fallback via `AR_USE_CALAMINE=0` |
|
||
| **`calamine_reader.py`** | Fast parser. Detects the data sheet, skips pivot/helper rows, yields normalized records |
|
||
| **`xlsx_reader.py`** | Low-memory streaming parser (`xml.etree.iterparse`) + `FileMeta`. Used for very large files |
|
||
| **`column_map.py`** | Canonical schema and header aliases for 8 languages. Maps by **normalized name, never position** |
|
||
| **`i18n.py`** | Marketplace config: transaction-type translations, currencies, default FX, storage keywords |
|
||
| **`dates.py`** | Multilingual date parsing (11 formats) with a prefix cache — turns millions of parses into dozens |
|
||
| **`regions.py`** | Maps `amazon.de` → `Germany` etc. |
|
||
| **`pipeline.py`** | Orchestrates parse → enrich → aggregate → classify → receivable → reconcile, with progress callbacks |
|
||
| **`settlements.py`** | Groups rows by settlement; owner attribution; the paid/receivable classification (Rule 1) |
|
||
| **`receivable.py`** | Rules 2 and 3: additional sales, reserve, rounding, FX, aging bands |
|
||
| **`movement.py`** | Roll-forward: opening + net revenue − payouts, and the AR ledger rows |
|
||
| **`journal.py`** | GL decomposition into 9 journal lines + 17 granular components, per marketplace |
|
||
| **`reconciliation.py`** | Rule 4 identity check and status |
|
||
| **`validation.py`** | File coverage: wrong month, overlapping ranges, date gaps |
|
||
| **`excel_export.py`** | Full audit workbook (streaming `write_only`, live formulas) |
|
||
| **`summary_export.py`** | Compact Finance pack (5 sheets, generates in seconds) |
|
||
| **`compare.py`** | Diffs a generated workbook against the sample for the comparison test |
|
||
|
||
### `app/api/routes/` — the web layer
|
||
|
||
| Route module | Responsibility |
|
||
|---|---|
|
||
| **`sessions.py`** | Create/list/update/delete month-end closings; seeds opening balances from the prior month |
|
||
| **`files.py`** | Chunked upload, filename sanitization, per-file parse validation |
|
||
| **`processing.py`** | Starts the background job; reports status, stage, rows done, ETA |
|
||
| **`results.py`** | Summary, settlements, transactions (filterable), aging, exceptions, journal |
|
||
| **`ar.py`** | Opening balances, `ar-movement` (roll-forward), `finance-summary` |
|
||
| **`control.py`** | Reconciliation control: dashboard vs Finance amounts, sign-off, completion gate |
|
||
| **`analytics.py`** | Date-filtered ledger, daily FX, sign-explained reconciliation detail, all-markets roll-up |
|
||
| **`export.py`** | Starts Summary/Full exports; lists and downloads them |
|
||
| **`settings.py`** | Reserves, FX rates, and global header-mapping rules |
|
||
|
||
### `app/services/`
|
||
|
||
| Module | Responsibility |
|
||
|---|---|
|
||
| **`jobs.py`** | Background processing and export jobs; owns its DB session; writes progress + ETA |
|
||
| **`store.py`** | Bulk transaction inserts (batched raw DBAPI), persists aggregates, builds exceptions |
|
||
|
||
---
|
||
|
||
## 7. Database tables
|
||
|
||
| Table | What it holds |
|
||
|---|---|
|
||
| `sessions` | One month-end closing: month, month-end date, clearing-lag, tolerance, status, progress |
|
||
| `session_files` | Uploaded files: name, size, SHA-256, sheet, rows, date range, currency, status |
|
||
| `transactions` | Every parsed row with `source_file`/`source_row` traceability, canonical type, flags |
|
||
| `settlements` | Per (marketplace, account, settlement id): order total, transfers, dates, paid/receivable |
|
||
| `receivable_results` | Per marketplace and account: additional sales, reserve, local + USD receivable |
|
||
| `reconciliation` | Identity check figures and status |
|
||
| `market_payouts` | Per-marketplace received / total payouts, attributed to each settlement's **owner** |
|
||
| `opening_balances` | Opening AR per marketplace, with source (manual / carried-forward) and reason |
|
||
| `fx_rates` | Month FX rate + currency per marketplace |
|
||
| `fx_rates_daily` | Optional per-date FX override |
|
||
| `reserves` | Net Closing Balance per marketplace and account |
|
||
| `journal_entries` | The GL decomposition JSON (primary + `per_marketplace`) and entry number |
|
||
| `finance_control` | Finance's control-sheet amounts, tolerance, sign-off and comments |
|
||
| `exceptions` | Every validation finding: category, severity, detail, source |
|
||
| `mapping_rules` | Admin header aliases (global, applied to all future parses) |
|
||
| `exports` | Generated workbooks: kind, path, SHA-256, size, timestamp |
|
||
|
||
---
|
||
|
||
## 8. The dashboard — each tab defined
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
OV[Overview] --> UP[Upload & Mapping]
|
||
UP --> VE[Validation & Exceptions]
|
||
VE --> FS[Finance Summary]
|
||
FS --> AL[AR Ledger]
|
||
AL --> SR[Settlement Reconciliation]
|
||
SR --> AG[A/R Aging]
|
||
AG --> TD[Transaction Details]
|
||
TD --> RE[Reconciliation]
|
||
RE --> JE[Journal Entry]
|
||
JE --> EX[Excel Export]
|
||
```
|
||
|
||
| Tab | What it shows | Key controls |
|
||
|---|---|---|
|
||
| **Overview** | KPI cards (closing receivable, reconciliation status, settlement counts, exceptions) and receivable by marketplace | **View details →** per marketplace; **All markets →** |
|
||
| **Dashboard** (home) | All closings with status and receivable | **Delete** removes the closing and every row/file it owns |
|
||
| **Upload & Mapping** | Drag-drop upload; per-file rows, date coverage, marketplace, status | **Header mapping** panel to classify unknown columns and save the rule |
|
||
| **Validation & Exceptions** | Every finding grouped by severity | — |
|
||
| **Finance Summary** | The Finance report: opening → revenue components → gross → fees → net revenue → closing, plus the ledger | Market switcher · **All Markets** sub-tab · Summary Excel |
|
||
| **AR Ledger** | Opening balance (**Adjust · Carry forward · Set to zero**), roll-forward statement, ledger movement, cross-check vs settlement method, **movement by date**, **daily FX table** | Daily/Weekly/Monthly + custom range · market switcher · All Markets |
|
||
| **Settlement Reconciliation** | Every settlement with orders, transfers, rows, period and status; the disbursement list | **Clearing-lag** control + re-process |
|
||
| **A/R Aging** | Aging matrix (Current / 1-30 / 31-60 / 61-90 / 91-Over) and chart | — |
|
||
| **Transaction Details** | Virtualized grid of every row with source file + row | Filters: settlement, type, marketplace, receivable, storage, search |
|
||
| **Reconciliation** | Sign-explained metrics (Revenue / Fees / Memo / Settlements), closing & variance, plus the **Reconciliation Control** | Finance amounts, tolerance, **Verify**, **Mark month complete** |
|
||
| **Journal Entry** | GL decomposition per period with account names | Journal Entry # |
|
||
| **Excel Export** | Generate/download Summary or Full workbook, with history | Two generate buttons |
|
||
|
||
### The All Markets sub-tab
|
||
|
||
Present on Finance Summary, AR Ledger and Reconciliation. Shows every marketplace in **its own
|
||
currency** with its FX rate, plus the **combined USD total**, and a **View details** button per row.
|
||
The selected market lives in the URL (`?market=Germany`, or `?market=__all__`), so links are shareable.
|
||
|
||
---
|
||
|
||
## 9. Data model — the source columns
|
||
|
||
Amazon's report has a 7-row preamble, **header on row 8**, data from row 9. Column counts vary by
|
||
marketplace (USA 30, Canada 29, UK 27, AU/IE 23, localized EU 28/24).
|
||
|
||
**The five columns that drive everything:**
|
||
|
||
| Col | Header | Role |
|
||
|---|---|---|
|
||
| A | `date/time` | Aging, date filters, payout clearing |
|
||
| B | `settlement id` | Grouping; the paid/receivable boundary (monotonic — higher = newer) |
|
||
| C | `type` | Order / Refund / Adjustment / fees / **Transfer** |
|
||
| I | `account type` | `Standard Orders` / `Invoiced Orders` (USA only; others use one `All Orders` stream) |
|
||
| **AD** | **`total`** | **The net amount summed for every figure** |
|
||
|
||
The other 25 columns (product sales, taxes, shipping, gift wrap, promos, selling fees, FBA fees,
|
||
storage, other) feed the **revenue/fee breakdown** in the Finance Summary and Journal Entry.
|
||
|
||
---
|
||
|
||
## 10. Multi-marketplace handling
|
||
|
||
| Aspect | How it works |
|
||
|---|---|
|
||
| **Languages** | Headers, transaction types and dates are localized in FR, DE, IT, ES, NL, PL, SV, TR. All normalized to canonical English |
|
||
| **Currencies** | USD, CAD, GBP, EUR, PLN, SEK, AUD, TRY — each with a default FX-to-USD rate, editable per closing |
|
||
| **Account streams** | Only the USA report has `account type`. Other marketplaces run a single `All Orders` stream |
|
||
| **Settlement owner** | A settlement belongs to the marketplace holding most of its rows. Payouts attach to their settlement's **owner**, not the file they appear in (Belgium's payouts sit inside the Germany report) |
|
||
| **Blank marketplace rows** | Inherit the file's **dominant** marketplace — never a filename guess (critical for combined files like "Belgium Germany") |
|
||
| **Cross-market rows** | Rows in a different marketplace than their settlement's owner are excluded and flagged |
|
||
| **Helper rows** | EU files carry a Finance-added translation row under the real header — detected and skipped |
|
||
|
||
---
|
||
|
||
## 11. Validation & exception categories
|
||
|
||
No amount is ever silently excluded.
|
||
|
||
| Category | Severity | Meaning |
|
||
|---|---|---|
|
||
| `missing_column` | error | A required field is absent — must be fixed |
|
||
| `unmapped_amounts` | **error** | An unmapped column contains money. **Caught Australia's renamed FBA-fee column (−2,427.43)** |
|
||
| `unmapped_column` | warning | An unrecognized header — classify it in Upload & Mapping |
|
||
| `wrong_month` | error/warning | File dates fall outside the reporting month |
|
||
| `overlapping_files` | warning | Two files of the same marketplace cover the same dates |
|
||
| `missing_dates` | warning | A gap in coverage within the month |
|
||
| `duplicate_rows` | warning | Identical row fingerprints — surfaced, never deleted |
|
||
| `undated_rows` | warning | Rows with no usable date (e.g. a manually appended total row) |
|
||
| `potential_storage_fee` | warning | Storage-like description booked under another type |
|
||
| `missing_storage_fees` | warning | A marketplace with no storage fees at all this month |
|
||
| `cross_market_settlement` | warning | Rows belonging to another marketplace's settlement |
|
||
| `in_transit_disbursement` | info | A payout not yet cleared — explains why a settlement stays receivable |
|
||
| `helper_header_row` | info | Translation rows skipped |
|
||
| `empty_file` | warning | Header only, no data rows |
|
||
|
||
---
|
||
|
||
## 12. Excel exports
|
||
|
||
| | Contents | Size / speed |
|
||
|---|---|---|
|
||
| **Summary Excel** | Finance Summary · AR Ledger · Reconciliation Control · Category Totals · Audit Trail | ~11 KB, seconds |
|
||
| **Full Excel** | Summary · Detail · COA · AR Ledger · Journal Entry · per-marketplace transaction tabs · Reconciliation · Exceptions · Processing Audit Trail — with **live formulas** (`SUMIFS`, `ROUND`, cross-sheet refs) | ~240 MB, a few minutes |
|
||
|
||
The Full workbook mirrors the manual file's structure so an auditor can trace any total back to the
|
||
transaction rows. The **Processing Audit Trail** sheet records generation time, inputs, filenames with
|
||
**SHA-256 hashes**, row counts, FX rates, reserves and the app version.
|
||
|
||
---
|
||
|
||
## 13. Testing
|
||
|
||
| Suite | Covers |
|
||
|---|---|
|
||
| `test_engine_unit.py` | Dates, column mapping, aging bands, settlement classification |
|
||
| `test_multimarket.py` | 11 localized date formats, 22 type translations, storage detection, header mapping, **13-market benchmark** |
|
||
| `test_per_market.py` | Journal resolution, payout owner attribution, per-market API |
|
||
| `test_reconciliation_jan2026.py` | **USA = 11,110,433 to the penny**, settlement split, reconciliation |
|
||
| `test_excel_export.py` / `test_summary_export.py` | Workbook structure, formulas, sheet splitting |
|
||
| `test_workbook_comparison.py` | Generated workbook vs the sample |
|
||
| `test_api.py` / `test_robustness.py` | Full API flow; corrupt/empty/bad-header files |
|
||
|
||
**64 fast + 12 integration tests.** Run integration with the sample files present:
|
||
|
||
```bash
|
||
AR_SAMPLE_DIR="/path/to/samples" python -m pytest tests -m integration
|
||
```
|
||
|
||
---
|
||
|
||
## 14. Known data issues & assumptions
|
||
|
||
These came out of reviewing the real January and February files:
|
||
|
||
1. **Australia renamed a column** (Feb): `fba fees` → `fulfilment by amazon fees`. Aliased; the
|
||
`unmapped_amounts` check caught the −2,427.43 before it reached a report.
|
||
2. **Canada has a manual footer row** (Feb): "Cost of advertising", −362,715.55, with no date, type
|
||
or settlement id. Excluded from the receivable, flagged as `undated_rows` — **needs a Finance decision**.
|
||
3. **New Amazon columns** (Feb): `Transaction Status`, `Transaction Release Date`, `low value goods`. All mapped.
|
||
4. **Duplicates are usually legitimate** — components sum to totals and the receivable reconciles
|
||
exactly with them included. Surfaced for review, never auto-removed.
|
||
5. **Liquidations are a memo line** — their amounts stay in the sales/fee buckets; re-bucketing would
|
||
change gross revenue.
|
||
6. **Settlement ids are assumed unique per Amazon account** — true across all real data.
|
||
7. **Month-boundary payouts**: a payout dated Jan 30 clears the bank in early February but lives in
|
||
the January file, so February never records receiving it. Explains USA/Canada roll-forward variances.
|
||
|
||
---
|
||
|
||
## 15. Where to look
|
||
|
||
| I want to… | Go to |
|
||
|---|---|
|
||
| Understand the receivable maths | [`accounts-receivable-logic.md`](accounts-receivable-logic.md) |
|
||
| See where each dashboard number comes from | `dashboard-guide.html` |
|
||
| Change the settlement cutoff | Settlement Reconciliation tab → clearing-lag |
|
||
| Map a new/renamed Amazon column | Upload & Mapping tab → Header mapping |
|
||
| Set the opening balance | AR Ledger tab → Adjust |
|
||
| Sign off the month | Reconciliation tab → Reconciliation Control → Verify → Mark month complete |
|
||
| Get the audit file | Excel Export tab → Generate full workbook |
|