Status: Accepted, stated in the project's written engineering rules. This record covers how Cluster ERP represents money: Python Decimal, database Numeric, JSON strings, and ROUND_HALF_EVEN. The mechanisms, including currency precision from reference data, are explained on money and Decimal.
Context¶
An ERP computes prices, totals, costs, and exchange rates. The project's written engineering rules forbid float for money and list the representation for each layer. A test docstring calls it "an absolute project rule" (tests/test_analytics_contributions.py:121-123). The rules also require that currency symbols, decimals, and formats come from reference data, not from code. Rationale for choosing banker's rounding is not determined from code.
Decision¶
- Every monetary value is a
Decimalin Python andNumericin the database. - JSON carries money as a string.
- Rounding uses
ROUND_HALF_EVEN. - The currency and its number of decimal places come from reference data.
The flowchart shows the representation at each layer.
flowchart LR db["PostgreSQL Numeric column"] --> py["Python Decimal"] py --> calc["arithmetic and quantize with ROUND_HALF_EVEN"] calc --> ser["field_serializer returns str"] ser --> json["JSON string value"] json --> client["client keeps the string"]
In prose: a value leaves the database as Decimal, is computed and rounded as Decimal, and is turned into a string by the schema before it reaches JSON. ExchangeRateRead and ConvertResult show the serialiser pattern (src/cluster_erp/core/currencies.py:186-204).
Consequences¶
- No file under
src/cluster_erpdeclares aMapped[float]column (searched withgrep -rn "Mapped\[float\]"). ROUND_HALF_EVENappears in 32 files undersrc/cluster_erp, andROUND_HALF_UPappears in none (counted withgrep -rln).- The change history writes
Decimalvalues as strings in its JSON diff (src/cluster_erp/core/change_history.py:108-109). - A tenant-defined field of type
decimalis stored in JSONB as a string (src/cluster_erp/core/tenant_fields.py:875-877). assert_known_currencyrejects a currency code that is not live, active reference data (src/cluster_erp/core/currencies.py:30-40).- Money samples in this wiki are written as strings, for example
"amount": "1250.00".
Where the code enforces it¶
| Mechanism | What it checks | Source |
|---|---|---|
test_monetary_measures_are_not_float_backed |
Every monetary analytics measure sits on a Numeric column that is not a Float |
tests/test_analytics_contributions.py:115-134 |
tests/test_tenant_fields.py |
A decimal tenant field returns a string, never a float |
tests/test_tenant_fields.py:61-62 |
field_serializer on read schemas |
Decimal fields leave the API as strings |
src/cluster_erp/core/currencies.py:186-204 |
assert_known_currency |
Currency codes are live reference data | src/cluster_erp/core/currencies.py:30-40 |
Not determined from code: a general test that every Pydantic schema with a money field serialises it as a string. None was found in tests/.
Related pages¶
Verified against commit 92f8051 on 2026-11-13.