cadrumo.domain.transactions.model_validation module

Validation helpers for strict transaction boundary models.

The transaction models keep their Pydantic validators on the boundary records, while this helper module owns the reusable parsing and invariant checks those validators call. It normalizes raw transactions, UTC timestamps, classifier provenance, confidence ranges, identifier tuples, lineage text, and non-negative tax amount fields without turning those rules into a separate public API.

See also

Transaction

Strict transaction record whose validators delegate to these helpers.

RawTransaction

Upstream row model accepted directly or coerced from JSON-compatible payloads.

BusinessClassification

Classification enum coupled to business_pct by this module.

TransactionValidationError

Typed domain error raised for validation failures.

validate_utc_aware()

UTC-awareness gate wrapped into the transaction error hierarchy.

require_aware_datetime(value)[source]

Return value if it is UTC-aware, as a transaction error if not.

Delegates to the canonical awareness check and only translates the exception, so the rule stays in one place.

Return type:

datetime

Parameters:

value (datetime)

validate_classified_by_shape(value)[source]

Return the classifier attribution if it names a recognised author.

Return type:

str

Parameters:

value (str)

validate_confidence_range(value)[source]

Return a classifier confidence if it is a share of one, or None.

Return type:

Decimal | None

Parameters:

value (Decimal | None)

classification_for_business_share(share)[source]

Return the classification a business share of this size implies.

The inverse of validate_business_pct_coupling(), and its other half: that one answers “may this classification carry a share”, this one answers “which classification does this share mean”. Kept beside it because the two describe one relationship, and a surface that knew only one of them had to invent the other.

A whole share is wholly business and a zero share wholly personal – neither is a mixture, and calling either MIXED would be a false statement about the row rather than a conservative one. Only a share strictly between is genuinely apportioned. An allocate path that hard-coded MIXED labelled a fully-business expense as mixed-use, which is the mistake this exists to stop repeating.

Parameters:

share (Decimal) – The business proportion, as a unit proportion in 0..1.

Return type:

BusinessClassification

Returns:

The classification the share implies.

Raises:

TransactionValidationError – When share is not a unit proportion. A proportion outside 0..1 names no classification at all, and answering MIXED for one would carry the bad value onward.

validate_business_pct_coupling(state, pct)[source]

Refuse a business share that disagrees with its classification.

A share-bearing classification must carry a share and any other must not, which is the coupling every write path checks. WHICH classifications bear one is takes_business_share(), read here rather than spelled out, so this check and the command that offers the flag cannot come to disagree about it.

Return type:

None

Parameters:
normalize_identifier_tuple(value)[source]

Return the identifiers stripped, refusing blanks and duplicates.

Return type:

tuple[str, ...]

Parameters:

value (tuple[str, ...])

trim_lineage_text(value)[source]

Return lineage prose stripped, or None when it carries nothing.

Return type:

str | None

Parameters:

value (str | None)

parse_required_aware_datetime(value, *, field_name)[source]

Return value as a UTC-aware datetime, naming the field on refusal.

Return type:

datetime

Parameters:
validate_non_negative_decimal(value, *, field_name)[source]

Return value if it is not negative, naming the field on refusal.

Return type:

Decimal | None

Parameters:
coerce_raw_transaction(raw)[source]

Return raw as a RawTransaction, accepting the model or its payload.

Return type:

RawTransaction

Parameters:

raw (object)