cadrumo.domain.categories.proportionality module

Proportionality and explainability primitives for category profiles.

Defines registry-projected vocabulary tokens and strict pydantic models that encode how a spending category is deducted on the autónomo filings, plus the citation chain back to the relevant authority that makes each rule explainable. Every ProportionalityRule carries at least one CategoryCitation; the consistency rules between kind-specific fields (fixed_pct, default_ratio, statutory_cap_*) are enforced by the model validator.

class CategoryCitationSource(*values)[source]

Bases: StrEnum

Allowed citation sources for explainable category profiles.

Variables:
  • MANUAL_RENTA – AEAT Manual práctico Renta.

  • MANUAL_IVA – AEAT Manual práctico IVA.

  • LEY_IRPF – Ley del Impuesto sobre la Renta de las Personas Físicas.

  • REGLAMENTO_IRPF – Reglamento del IRPF.

  • AEAT_HELP – AEAT online help / portal text.

MANUAL_RENTA
MANUAL_IVA
LEY_IRPF
REGLAMENTO_IRPF
AEAT_HELP
ANNUAL_EDITION_CITATION_SOURCES: frozenset[CategoryCitationSource]

Citation sources published as a dated ANNUAL EDITION, as opposed to a statute whose reference year is its enactment. The distinction is what makes the anti-mirror invariant precise rather than a year-sniffing heuristic: “Manual práctico Renta 2025” names the edition read, while “Ley 35/2006” names when the law was passed and says nothing about which filing year it covers.

STATUTORY_CITATION_SOURCES: frozenset[CategoryCitationSource]

Citation sources that name a PROVISION rather than a dated publication. Their windows are bounded by the provision’s own effective span in the registry legal catalogue, which makes a multi-year statutory citation a derived fact instead of an author’s confidence. Complete by construction against the enum: a new source must land in one partition or the other, and the gate that reads these sets reds if it lands in neither.

class CategoryCitation(**data)[source]

Bases: _ProportionalityStrictFrozenModel

Traceable citation backing one category or proportionality rule.

Variables:
  • source – Originating CategoryCitationSource.

  • reference – Human-readable document reference (title, edition, BOE number).

  • locator – Section, article, or page locator within the referenced document.

  • url – Canonical URL where the citation can be checked. Constrained to an official AEAT or BOE origin over https – see _validate_authoritative_url().

  • quote – Verbatim Spanish from the bundled corpus, stored INLINE and never as a translation key. Verifying a quotation requires the literal text at the citation site; indirecting it means the record no longer carries its own evidence, whatever the key resolves to. Empty when, and only when, grounding is not VERIFIED.

  • grounding – Which of the three states this citation’s evidence is in – read against the corpus, examined and refused, or unreachable because the cited document is not bundled.

  • grounding_reason – Why no quotation is carried. Required when, and only when, grounding is not VERIFIED.

  • legal_ref – The registry legal-catalogue id of the provision cited. Required on a STATUTORY source and forbidden on an annual-edition one, so that every citation is bounded on exactly one axis: an edition-dated citation by the edition it names, a statutory one by the provision’s own effective span. Leaving it optional would let an author widen a statutory window with nothing able to check it.

  • valid_from – First date this citation is asserted to support the rule.

  • valid_to – Last date, inclusive. Both bounds are required: the span is the claim, and a defaulted one would assert grounding nobody typed.

Parameters:

data (Any)

source: CategoryCitationSource
reference: str
locator: str
url: AnyHttpUrl
quote: str
grounding: CitationGrounding
grounding_reason: str
legal_ref: str | None
valid_from: date
valid_to: date
property window: ValidityWindow

Return the closed span this citation is asserted over.

Returns:

The ValidityWindow the two declared bounds describe.

parse_http_url(value)[source]

Parse a string into a statically typed AnyHttpUrl.

Parameters:

value (str) – Raw HTTP / HTTPS URL string.

Return type:

AnyHttpUrl

Returns:

A validated pydantic.AnyHttpUrl.

class ProportionalityKind(token, *, is_full_deductible, is_usage_ratio, is_statutory_cap, requires_fixed_pct, is_non_deductible, requires_exclusive_use, _registry_validated=False)[source]

Bases: object

Opaque proportionality token projected from the facts registry.

The token and its evaluator-role metadata are governed category facts. This object retains only the projected token plus mechanics-facing role flags; callers cannot mint a token without a validated registry projection.

Parameters:
  • token (str)

  • is_full_deductible (bool)

  • is_usage_ratio (bool)

  • is_statutory_cap (bool)

  • requires_fixed_pct (bool)

  • is_non_deductible (bool)

  • requires_exclusive_use (bool)

  • _registry_validated (bool)

Return type:

Self

classmethod from_registry(token, **roles)[source]

Construct the typed value from its canonical registry token.

Return type:

Self

Parameters:
property value: str

Return the canonical proportionality token text.

property name: str

Return the canonical proportionality token name.

property is_full_deductible: bool

Return whether this role allows full deduction.

property is_usage_ratio: bool

Return whether this role uses a declared usage ratio.

property is_statutory_cap: bool

Return whether this role applies a statutory cap.

property requires_fixed_pct: bool

Return whether this role requires a fixed percentage.

property is_non_deductible: bool

Return whether this role disallows deduction.

property requires_exclusive_use: bool

Return whether this role requires exclusive use.

class StatutoryCapPeriod(token, *, is_per_person, _registry_validated=False)[source]

Bases: object

Opaque statutory-cap period token projected from the facts registry.

Parameters:
  • token (str)

  • is_per_person (bool)

  • _registry_validated (bool)

Return type:

Self

classmethod from_registry(token, *, is_per_person)[source]

Construct the typed value from its canonical registry token.

Return type:

Self

Parameters:
property value: str

Return the canonical statutory-cap period token text.

property name: str

Return the canonical statutory-cap period token name.

property is_per_person: bool

Return whether the cap is evaluated per person.

class StatutoryCapVariant(**data)[source]

Bases: _ProportionalityStrictFrozenModel

One legally distinct cap inside a statutory-cap rule, selected by a condition.

The condition is what makes a variant a variant; the UNIT of the amount is incidental and the law uses both. RIRPF art. 9.A.3.a distinguishes con and sin pernocta and states DAILY amounts; LIRPF art. 30.2.5.a distinguishes a person with discapacidad from one without and states ANNUAL per-person amounts. Modelling only the daily unit is why the second provision could ship with one of its two limbs missing.

Variables:
  • id – Stable identifier the calculation context selects on.

  • label – Translation key for the human-readable label.

  • statutory_cap_eur_per_day – A daily amount, for a rule capped per day.

  • statutory_cap_eur – An annual amount, for a rule capped per period. Exactly one of the two is set: a variant carrying both would leave the unit to the call site, and a variant carrying neither caps nothing.

Parameters:

data (Any)

id: str
label: tr
statutory_cap_eur_per_day: Decimal | None
statutory_cap_eur: Decimal | None
property is_per_day: bool

Return whether this variant’s amount is a daily one.

Returns:

True when the variant carries statutory_cap_eur_per_day.

class StatutoryCapAmount(**data)[source]

Bases: _ProportionalityStrictFrozenModel

One year-bounded value of a statutory cap whose amount the law varies.

Some statutory caps are fixed by the tax law and hold until it is amended – the 500 euro seguro de enfermedad limit of LIRPF art. 30.2.5.a is one. Others are fixed BY REFERENCE to a figure that moves every year: LIRPF art. 30.2.1 caps the deductible mutualidad alternativa premium at the cuota maxima por contingencias comunes established “en cada ejercicio economico” in the RETA.

A single ProportionalityRule.statutory_cap_eur cannot express the second kind. Encoding one year’s figure – or worse, a round number close to none of them – silently applies it to every filing year, and the error is invisible because the value looks like a law-fixed constant. The registry shipped exactly that: a flat 15000 that matched no ejercicio at all.

Variables:
  • value – The cap amount in euros for this span.

  • valid_from – First date the amount applies.

  • valid_to – Last date, inclusive. Both bounds required and closed, for the reasons ValidityWindow documents.

Parameters:

data (Any)

value: Decimal
valid_from: date
valid_to: date
property window: ValidityWindow

Return the closed span this amount applies over.

Returns:

The ValidityWindow the bounds describe.

class ProportionalityRule(**data)[source]

Bases: _ProportionalityStrictFrozenModel

Deductibility and proportionality rule for one spending category.

Variables:
  • kind – One of ProportionalityKind.

  • fixed_pct – Required when the projected kind declares a fixed-percent evaluator role; otherwise must be None.

  • default_ratio – Optional default usage ratio; only valid for usage-ratio kinds.

  • statutory_multiplier – Optional statutory factor applied on top of the operator-chosen usage ratio. Only valid for usage-ratio kinds. The canonical example is the LIRPF Art. 30.2 rule 5 (Ley 6/2017, BOE-A-2017-12544) 0.30 multiplier applied to suministros (utility) costs of the habitual vivienda when the operator deducts under estimacion directa: effective_deductible_pct = operator_chosen_ratio * statutory_multiplier. When None no statutory factor is applied (equivalent to Decimal("1")); the operator’s chosen ratio is the effective deductible percentage.

  • statutory_cap_eur_per_day – Daily statutory cap; only valid for a projected statutory-cap kind.

  • statutory_cap_eur – Generic statutory cap amount; only valid for a projected statutory-cap kind and must be paired with statutory_cap_period.

  • statutory_cap_period – StatutoryCapPeriod that the generic cap applies over; required when statutory_cap_eur is set.

  • statutory_cap_variants – Daily statutory caps selected by a legally relevant condition.

  • statutory_cap_schedule – Dated amounts for a cap the law re-fixes each ejercicio. Mutually exclusive with the fixed statutory_cap_eur: a cap is either law-fixed or year-referenced, never both.

  • citations – At least one CategoryCitation proving the rule.

  • notes – Translation key for the notes describing the rule.

Parameters:

data (Any)

kind: ProportionalityKind
fixed_pct: UnitProportion | None
default_ratio: UnitProportion | None
statutory_multiplier: UnitProportion | None
statutory_cap_eur_per_day: Decimal | None
statutory_cap_eur: Decimal | None
statutory_cap_period: StatutoryCapPeriod | None
statutory_cap_variants: tuple[StatutoryCapVariant, ...]
statutory_cap_schedule: tuple[StatutoryCapAmount, ...]
citations: tuple[CategoryCitation, ...]
notes: tr
cap_amount_for_year(year)[source]

Return the statutory cap amount in force for year.

Return type:

Decimal | None

Returns:

The scheduled amount covering year when the cap is year-referenced, the flat statutory_cap_eur when it is law-fixed, and None when this rule carries neither.

Parameters:

year (int)

effective_usage_ratio(rule, chosen_ratio)[source]

Return the legally-effective deductible percentage for chosen_ratio.

Applies the rule’s statutory_multiplier on top of the operator- chosen usage ratio. Only meaningful for usage-ratio kinds; raises when called on a non-usage-ratio rule (the caller is responsible for routing rules to the right evaluator).

Parameters:
  • rule (ProportionalityRule) – A ProportionalityRule of a usage-ratio kind.

  • chosen_ratio (Decimal) – The operator’s stored usage ratio (typically derived from censo office_m2 / total_m2 for HOME_AREA kinds, or a personal-use proportion for PERSONAL kinds).

Return type:

Decimal

Returns:

chosen_ratio * (rule.statutory_multiplier or Decimal("1")).

Raises:

CategoryValidationError – When rule.kind is not a usage-ratio kind.