"""Currency normalization value models.Defines :class:`MonetaryAmount`, :class:`NormalizedAmount`, and:class:`CurrencyNormalizationStatus` for the EUR conversion results produced by:class:`CurrencyNormalizationService`."""from__future__importannotationsfromdatetimeimportdatefromdecimalimportDecimalfromenumimportStrEnumfrompydanticimportBaseModel,Field,field_validatorfrom...core.errors.hierarchyimportpydantic_validation_boundaryfrom...core.modelsimportSTRICT_FROZEN_CONFIGfrom...core.parsing.codesimportIsoCurrencyCode,normalise_iso_4217_currency
[docs]classCurrencyNormalizationStatus(StrEnum):"""Status of currency normalization."""NATIVE_EUR="native_eur"NORMALIZED="normalized"MISSING_RATE="missing_rate"UNSUPPORTED_CURRENCY="unsupported_currency"
[docs]classMonetaryAmount(BaseModel):"""A monetary amount with its currency."""model_config=STRICT_FROZEN_CONFIGamount:Decimalcurrency:IsoCurrencyCode@field_validator("currency",mode="before")@classmethod@pydantic_validation_boundarydef_normalise_currency(cls,v:object)->object:"""Normalise through the canonical ISO-4217 owner. Runs ``mode="before"`` because the field's own length constraint would otherwise fire on a padded token (``" eur "``) before the normaliser ever runs, and because :meth:`CurrencyNormalizationService.normalize` compares ``currency`` against :data:`~core.external_constants.DEFAULT_CURRENCY` by raw equality: a lowercase or padded native-EUR amount must already be canonical ``"EUR"`` by the time that comparison runs, or it silently misclassifies as a foreign currency with a missing rate instead of the native-EUR identity conversion. """returnnormalise_iso_4217_currency(v)
[docs]classNormalizedAmount(BaseModel):"""A EUR-normalized monetary amount with provenance."""model_config=STRICT_FROZEN_CONFIGoriginal:MonetaryAmounteur_amount:Decimalstatus:CurrencyNormalizationStatusrate:Decimal|None=Nonerate_source:str|None=Nonerate_date:date|None=None
[docs]classFxConversionStamp(BaseModel):"""The euro-conversion stamp a foreign-currency record carries. Three fields rather than the ``(rate, date)`` pair this replaced, because a stored euro figure that cannot say WHO quoted the rate is a number with no authority behind it. The rate and the date say what was applied; *source* says which rate authority stated it, which is what makes the conversion auditable years later against the same published series. """model_config=STRICT_FROZEN_CONFIGrate:Decimal=Field(gt=Decimal("0"))rate_date:datesource:str=Field(min_length=1)