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
UsageRatioProfilemay carry an override.Derived from
domain.categories.CATEGORY_PROFILES_2025at import time: a category is eligible iff itsdomain.categories.ProportionalityRule.kindisdomain.categories.ProportionalityKind.USAGE_RATIO_HOME_AREAordomain.categories.ProportionalityKind.USAGE_RATIO_PERSONAL.
- class UsageRatioProfile(**data)[source]¶
Bases:
BaseModelThe operator’s persisted per-category usage-ratio overrides.
Only categories listed in
ELIGIBLE_USAGE_RATIO_CATEGORIESmay be persisted; every other key is rejected by the cross-field validator. The bounds validator additionally rejectsNaN/InfinityDecimal 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.SpendingCategoryto aDecimalin[0, 1].- Parameters:
ratios (Mapping[SpendingCategory, Decimal])
- ratios: Mapping[SpendingCategory, Decimal]¶
- with_ratio(category, ratio)[source]¶
Return a new
UsageRatioProfilewith 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:
- Returns:
A fresh frozen profile; the receiver is left unchanged.
- without_ratio(category)[source]¶
Return a new
UsageRatioProfilewith one ratio removed.A no-op when
categoryhas no current override.- Parameters:
category (
SpendingCategory) – The spending category whose override to remove.- Return type:
- Returns:
A fresh frozen profile; the receiver is left unchanged.
- resolve_user_ratio(profile, category)[source]¶
Return the operator’s persisted ratio for
category, orNoneif unset.Pure helper consumed by
aeat.domain.deductibility. When the return value isNonethe caller falls back todomain.categories.ProportionalityRule.default_ratioand records the resolution source in the transaction trace fields.- Parameters:
profile (
UsageRatioProfile) – The operator’s currently persistedUsageRatioProfile.category (
SpendingCategory) – The spending category whose ratio to resolve.
- Return type:
- Returns:
The user-configured ratio, or
Noneif the category has no override.
- class UsageRatioReference(**data)[source]¶
Bases:
BaseModelValidated reference from one ledger transaction to a usage-ratio profile entry.
- Parameters:
usage_ratio_id (str)
category (SpendingCategory)
ratio (Decimal)
- 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 carriesbusiness_pct, the percentage must match the referenced profile ratio so the stored transaction fact and its proportionality source cannot drift.- Return type:
- Returns:
The validated
UsageRatioReferenceanchored to the profile.- Parameters:
profile (UsageRatioProfile)
category_id (str | None)
usage_ratio_id (str)
business_pct (Decimal | None)