aeat.domain.usage_ratios._model module

Pydantic model and resolver for the operator’s per-category usage-ratio overrides.

Defines UsageRatioProfile — the strict, frozen pydantic v2 record that captures the operator’s persisted business / personal split coefficients — plus resolve_user_ratio(), the pure helper consumed by aeat.domain.deductibility to look up an override before falling back to the statutory domain.categories.ProportionalityRule.default_ratio. The eligibility set ELIGIBLE_USAGE_RATIO_CATEGORIES is derived once at import time from domain.categories.CATEGORY_PROFILES_2025.

ELIGIBLE_USAGE_RATIO_CATEGORIES: frozenset[SpendingCategory]

Categories for which a UsageRatioProfile may carry an override.

Derived from domain.categories.CATEGORY_PROFILES_2025 at import time: a category is eligible iff its domain.categories.ProportionalityRule.kind is domain.categories.ProportionalityKind.USAGE_RATIO_HOME_AREA or domain.categories.ProportionalityKind.USAGE_RATIO_PERSONAL.

class UsageRatioProfile(**data)[source]

Bases: BaseModel

The operator’s persisted per-category usage-ratio overrides.

Only categories listed in ELIGIBLE_USAGE_RATIO_CATEGORIES may be persisted; every other key is rejected by the cross-field validator. The bounds validator additionally rejects NaN / Infinity Decimal values and any ratio outside the inclusive [0, 1] range. Stored ratios are canonicalised by category-value sort order so two equal profiles serialise to identical bytes — a property relied on when the encrypted envelope is git-tracked.

The inner mapping is frozen after validation. Callers use with_ratio() / without_ratio() to derive new profiles.

Variables:

ratios – Frozen mapping from domain.categories.SpendingCategory to a Decimal in [0, 1].

Parameters:

ratios (Mapping[SpendingCategory, Decimal])

ratios: Mapping[SpendingCategory, Decimal]
with_ratio(category, ratio)[source]

Return a new UsageRatioProfile with one ratio set or replaced.

Parameters:
  • category (SpendingCategory) – The spending category whose ratio to set.

  • ratio (Decimal) – The replacement ratio in [0, 1].

Return type:

UsageRatioProfile

Returns:

A fresh frozen profile; the receiver is left unchanged.

without_ratio(category)[source]

Return a new UsageRatioProfile with one ratio removed.

A no-op when category has no current override.

Parameters:

category (SpendingCategory) – The spending category whose override to remove.

Return type:

UsageRatioProfile

Returns:

A fresh frozen profile; the receiver is left unchanged.

resolve_user_ratio(profile, category)[source]

Return the operator’s persisted ratio for category, or None if unset.

Pure helper consumed by aeat.domain.deductibility. When the return value is None the caller falls back to domain.categories.ProportionalityRule.default_ratio and records the resolution source in the transaction trace fields.

Parameters:
Return type:

Decimal | None

Returns:

The user-configured ratio, or None if the category has no override.

class UsageRatioReference(**data)[source]

Bases: BaseModel

Validated reference from one ledger transaction to a usage-ratio profile entry.

Parameters:
usage_ratio_id: str
category: SpendingCategory
ratio: Decimal
validate_usage_ratio_reference(profile, *, category_id, usage_ratio_id, business_pct=None)[source]

Validate a ledger transaction’s usage-ratio reference against profile.

Usage-ratio profiles are keyed by SpendingCategory; therefore a persisted ledger reference must be the concrete category value, not a CLI alias or a parallel identifier. When a row also carries business_pct, the percentage must match the referenced profile ratio so the stored transaction fact and its proportionality source cannot drift.

Return type:

UsageRatioReference

Returns:

The validated UsageRatioReference anchored to the profile.

Parameters: