ADR-0004: MONEY as a First-Class Type¶
Status: Accepted Date: 2026-06-22 Deciders: Paxman core team Supersedes: — Superseded by: —
Context and Problem Statement¶
Paxman normalizes data that frequently includes monetary values (invoices, quotations, procurement). Two options for representing money:
MONEYas a first-class field type — distinct fromSTRING,DECIMAL, etc. Carries amount, ISO-4217 currency code, and precision.MONEYas aDECIMALwith metadata — money is just a decimal with acurrencysemantic tag.
How should Paxman represent monetary values?
Decision Drivers¶
- First-class support for finance — Paxman's primary use cases are invoice/quotation/procurement normalization (PRD §6).
- Currency arithmetic — cross-currency operations require an explicit currency, not a tag.
- Precision — money must not lose precision through float conversion.
- FX handling — explicit
CurrencyPolicyfor cross-currency resolution. - Auditability — finance requires clear, unambiguous representation.
Considered Options¶
Option A — MONEY as a first-class type (chosen)¶
MONEY is one of the nine V1 field types. It is a structured type: Decimal amount + ISO-4217 currency code + optional precision. The Reconciler has a MONEY arithmetic module that enforces currency policy.
Pros:
- Strong type safety at the canonical contract level.
- Currency mismatches are caught at the contract level, not at runtime.
MONEYarithmetic is a Reconciler primitive, not a capability.- Audit trail is unambiguous.
Cons:
- More adapter work: each adapter (Pydantic, JSON Schema, OpenAPI) must handle
MONEYdistinctly. - Cross-adapter round-trips must preserve
MONEYsemantics.
Option B — MONEY as a DECIMAL with a currency semantic tag¶
Money is a DECIMAL field with semantic_tags = ["currency", "iso4217"]. The currency is a separate field in the same object.
Pros:
- Easier adapter work: existing types cover money.
- The semantic tag system already exists.
Cons:
- Currency is not structurally tied to the value, leading to mis-pairing.
- The Reconciler must do a lookup for every money-typed field to find its currency.
- FX and precision handling are scattered.
Decision Outcome¶
Chosen option: A (first-class). MONEY is a first-class field type in the canonical contract. The V1 set of field types is STRING, INTEGER, DECIMAL, BOOLEAN, DATE, ENUM, OBJECT, ARRAY, MONEY (see ARCHITECTURE.md §4.1).
The MONEY type is modeled as:
MoneyValue(
amount: Decimal, # exact precision
currency: str, # ISO-4217 code
precision: int | None, # decimal places; None = inferred
)
Adapters are responsible for mapping external representations to MoneyValue (e.g., JSON Schema type: "string", pattern: "^\\d+\\.\\d{2} [A-Z]{3}$").
The Reconciler has a money.py module that implements:
- Currency matching
- Cross-currency resolution via
CurrencyPolicy(STRICT_MATCH, ALLOW_FX, REJECT_WITHOUT_RATE) - Decimal arithmetic (no float)
Consequences¶
Positive¶
- Finance use cases are first-class.
- Cross-currency mismatches are caught at the contract level.
- Audit trail is unambiguous.
Negative¶
- Adapter work is more complex.
- Adapter tests must cover
MONEYround-trips.
Neutral¶
MONEYjoins the nine V1 types and inherits the same canonicalization, validation, and evidence rules.
Validation¶
- The contract adapter tests round-trip
MONEYfor every adapter. - The Reconciler tests cover
STRICT_MATCH,ALLOW_FX, andREJECT_WITHOUT_RATEpolicies. - The
MONEYarithmetic tests assert no precision loss for representative decimal operations.
References¶
- PRD.md §6 (use cases), §7.2
- ARCHITECTURE.md §4.1 (canonical contract types)
- PACKAGE_STRUCTURE.md §7 (reconciler/money.py)