cadrumo.domain.contribuyente.guarderia_mensual module¶
The one grammar for a descendant’s month-level guardería spend.
Art. 81.2 LIRPF draws two month boundaries an annual total cannot express, so
GuarderiaMonthSpend entries are month-granular.
That makes the map a structured value on surfaces that carry only flat strings:
the --descendiente flag, the renta_family.descendiente.{n}.* fact index,
and one wizard page. This module is the single grammar all three share.
One grammar rather than three because the alternative was measured on this same descendant surface and shipped a real defect: a translated key token parsed without error and was silently dropped, leaving a claiming default in place. A per-door grammar drifts the same way – the CLI would accept a shape the wizard refuses, and the operator would meet different rules depending on which door they came through.
Accepted form:
MM:AMOUNT[;MM:AMOUNT...]
MM-MM:AMOUNT (an inclusive month range, all at that amount)
; separates entries because the --descendiente flag already splits its
own keys on ,; a comma here would end the flag’s value mid-map. : binds
a month to its amount and cannot occur inside an integer euro figure, so the
split is unambiguous.
The range form is not sugar for its own sake. The dominant real shape is a
constant monthly fee across an enrolment span, so 9-12:210 is what a
taxpayer reads off their centre’s certificate; spelling four identical entries
is where a transcription slip enters.
Ranges are INPUT only. serialise_guarderia_mensual() always emits the
expanded, month-sorted MM:AMOUNT form, so the stored fact has exactly one
representation and a save-then-reload round-trip is byte-stable regardless of
which form the operator typed.
Every malformed input REFUSES rather than dropping the offending entry. Dropping would under-grant, which is the safe direction on amount, but silently – and this map’s whole purpose is to carry months a taxpayer can evidence from a certificate they already hold, so a month that vanished between typing and storage is precisely what nobody would catch.
- ENTRY_SEPARATOR¶
the
--descendienteflag owns that.- Type:
Separator between entries. Not
,
- GUARDERIA_MENSUAL_ACCEPTED_FORM¶
The accepted form, quoted verbatim in every refusal so the operator is told what to write rather than only what was wrong.
- parse_guarderia_mensual(raw, *, field)[source]¶
Parse the monthly-spend grammar into canonical, month-sorted entries.
- Parameters:
raw (
str) – The operator-supplied map. A blank string yields an empty tuple, which means “no monthly breakdown declared” – distinct from a declared map that happens to sum to zero.field (
str) – The path or key naming this value in a refusal, so the operator is told which descendant and which door the bad value came through.
- Return type:
- Returns:
The declared months as canonical entries, ascending by month, with every range expanded.
- Raises:
ProfileAnswerTypeError – If any entry is malformed, names a month outside 1-12, inverts a range, repeats a month, or carries an amount that is not a non-negative integer.
- parse_month_spec(spec, *, entry, field, accepted_form="MM:AMOUNT or MM-MM:AMOUNT, entries separated by ';' (for example 9-12:210;1:180)")[source]¶
Read a bare month or an inclusive range into the months it names.
Shared with
meses_trabajo, which carries the Art. 81.1 month set through the same doors. accepted_form is the caller’s own grammar, quoted in refusals so an operator is told the form of the surface they used rather than this module’s.
- is_plain_whole_number(text)[source]¶
Whether text is exactly ASCII digits, so
intcannot fail on it.str.isdigitis NOT that test and the difference is a live defect rather than a nicety. It answers True for superscripts and for non-ASCII digit scripts, andintaccepts only the second group – so"9:²", a shape an operator reaches by pasting a footnote marker out of a formatted certificate, passed anisdigitgate and then raised a bareValueErrorfromint. That error is untranslated, names neither the field nor the accepted form, and escapes the wizard’s answer validator entirely, because the validator catches only the typed refusal this module raises. The operator met a crash where a verdict was due.isdigitalso letsint’s own tolerances through unexamined: it would be the wrong gate for"1_0", whichintreads as ten.Kept as one named predicate rather than repeated inline, because the same mistake was made independently at three sites in this feature and the reasoning is what stops it being made a fourth time.
- serialise_guarderia_mensual(entries)[source]¶
Render entries in the one canonical form, ascending by month.
Ranges are never emitted. The stored fact therefore has exactly one representation for a given map, which is what lets the fact index, the wizard’s resume projection, and the CLI payload round-trip byte-for-byte whichever form the operator originally typed.
- Return type:
- Parameters:
entries (Iterable[GuarderiaMonthSpend])