aeat.adapters.inbound.financial.providers._base module

Shared provider contracts and ingest helpers.

Defines the FinancialProvider ABC together with the validation record ProviderValidation, the provider error hierarchy (FinancialProviderError, InvalidFinancialSourceError, UnsupportedFinancialSourceError, BankStatementParseError), and the parsing / provenance helpers concrete providers reuse to emit RawTransaction records with consistent RawProvenance.

Providers emit ParsedLedgerRow objects: the source sign is consumed at the adapter boundary into a TransactionDirection, while the stored raw amount is an absolute magnitude.

Provider corpus discipline

Every concrete FinancialProvider subclass must declare two class-level corpus attributes:

verification_source

A string literal describing how the corpus fixtures were obtained:

  • "real_bank_corpus_pdf" — sanitised PDFs from real bank statements held by the operator.

  • "synthetic_from_bank_published_text" — PDFs generated from sanitised text dumps published in third-party open-source corpora (e.g., portfolio-performance); structure matches real layouts but origin is reconstructed.

  • "no_corpus" — no corpus is currently held. The provider must also set provisional_pending_specimen = True.

provisional_pending_specimen

True when no real-corpus PDF has been parsed to confirm the provider produces correct output. Providers with no_corpus must set this True; providers with a confirmed corpus round-trip set it False.

The test suite in test_pdf_n26.py and the detection-invariant test enforce these attributes at collection time so a newly enrolled provider cannot silently ship without the declaration.

CorpusVerificationSource

Allowed values for FinancialProvider.verification_source.

alias of Literal[‘real_bank_corpus_pdf’, ‘synthetic_from_bank_published_text’, ‘no_corpus’]

exception FinancialProviderError(message=None, *, context=None, suggestion=None, translated_message=None)[source]

Bases: AeatError

Base error raised by financial-ingest providers.

Subclasses core.errors.AeatError so the application layer can catch every provider failure with one except clause.

Parameters:
  • message (str | None)

  • context (Mapping[str, object] | None)

  • suggestion (str | None)

  • translated_message (str | None)

Return type:

None

code: ClassVar[ErrorCode]
exception FinancialProviderConfigError(message=None, *, context=None, suggestion=None, translated_message=None)[source]

Bases: FinancialProviderError

Raised when a FinancialProvider subclass declaration is invalid.

Fired by __init_subclass__ when the concrete provider class is missing or carries an invalid verification_source or provisional_pending_specimen class variable.

Parameters:
  • message (str | None)

  • context (Mapping[str, object] | None)

  • suggestion (str | None)

  • translated_message (str | None)

Return type:

None

code: ClassVar[ErrorCode]
exception UnsupportedFinancialSourceError(message=None, *, context=None, suggestion=None, translated_message=None)[source]

Bases: FinancialProviderError

Raised when no provider can interpret a source document.

Parameters:
  • message (str | None)

  • context (Mapping[str, object] | None)

  • suggestion (str | None)

  • translated_message (str | None)

Return type:

None

code: ClassVar[ErrorCode]
exception InvalidFinancialSourceError(message=None, *, context=None, suggestion=None, translated_message=None)[source]

Bases: FinancialProviderError

Raised when a source document is unreadable or structurally invalid.

Parameters:
  • message (str | None)

  • context (Mapping[str, object] | None)

  • suggestion (str | None)

  • translated_message (str | None)

Return type:

None

code: ClassVar[ErrorCode]
exception FinancialValidationError(message=None, *, context=None, suggestion=None, translated_message=None)[source]

Bases: FinancialProviderError

Raised when a specific field (date, amount) fails domain validation.

Parameters:
  • message (str | None)

  • context (Mapping[str, object] | None)

  • suggestion (str | None)

  • translated_message (str | None)

Return type:

None

code: ClassVar[ErrorCode]
exception BankStatementParseError(message=None, *, missing=(), malformed=(), ambiguous=(), coverage=None)[source]

Bases: FinancialProviderError

Raised when a bank statement PDF cannot be fully parsed.

Carries structured attributes that allow callers to assert on the failure kind without parsing the message string — the same pattern as DeclaracionParseError and JustificanteParseError.

This error is appropriate for PDF-specific parse failures where the document was accepted by format detection but extraction produced incomplete or malformed results. Low-level structural failures (file unreadable, wrong bank marker) continue to raise InvalidFinancialSourceError.

Variables:
  • missing – Tuple of field or row identifiers that produced no match in the PDF text (e.g. a required header field absent).

  • malformed – Tuple of field identifiers whose captured value could not be coerced to the target type (date, amount, currency).

  • ambiguous – Tuple of field identifiers that matched more than one region in the PDF.

  • coverage – Fraction of expected transaction rows successfully extracted (Decimal). None when the error is not a coverage failure.

Parameters:
Return type:

None

code: ClassVar[ErrorCode]
class ProviderValidation(**data)[source]

Bases: BaseModel

Typed validation result returned before ingest.

Variables:
  • is_valid – Whether the source document can be ingested.

  • warnings – Human-readable warning strings; non-empty even when is_valid is True (e.g., a missing currency column).

  • detected_encoding – Provider-specific encoding marker, when applicable (CSV byte decoding, OFX parser tag, etc.).

  • detected_dialect – Compact provider-specific dialect / layout description used in operator diagnostics.

Parameters:
  • is_valid (bool)

  • warnings (tuple[str, ...])

  • detected_encoding (str | None)

  • detected_dialect (str | None)

is_valid: bool
warnings: tuple[str, ...]
detected_encoding: str | None
detected_dialect: str | None
class FinancialProvider[source]

Bases: ABC

Abstract base class for file-backed raw transaction providers.

Concrete subclasses must declare name, supported_extensions, source_format, verification_source, and provisional_pending_specimen and implement ingest() plus validate_source(). The shared _build_provenance() helper centralises RawProvenance construction so every emitted RawTransaction carries consistent provenance metadata.

See the module docstring for the corpus discipline contract that verification_source and provisional_pending_specimen jointly enforce.

Variables:
  • name – Stable provider identifier embedded in synthetic transaction ids and provenance records.

  • supported_extensions – Lowercase file extensions (including the leading dot) the provider accepts.

  • source_format – Source-format enum used for provenance.

  • verification_source – Corpus provenance declaration; one of "real_bank_corpus_pdf", "synthetic_from_bank_published_text", or "no_corpus".

  • provisional_pending_specimenTrue when no confirmed corpus round-trip has been performed for this provider. Providers with verification_source = "no_corpus" must set this True.

name: ClassVar[str]
supported_extensions: ClassVar[frozenset[str]]
source_format: ClassVar[SourceFormat]
verification_source: ClassVar[Literal['real_bank_corpus_pdf', 'synthetic_from_bank_published_text', 'no_corpus']]
provisional_pending_specimen: ClassVar[bool]
can_handle(path)[source]

Return whether the provider is a plausible match for path.

Parameters:

path (Path) – Candidate source document.

Return type:

bool

Returns:

True if path exists and its extension is in supported_extensions. Content sniffing is left to validate_source() and detect_provider().

abstractmethod ingest(path)[source]

Yield parsed ledger rows from path.

Implementations must produce one ParsedLedgerRow per source row (via build_raw_transaction()), each carrying a magnitude domain.transactions.RawTransaction plus the domain.transactions.TransactionDirection derived from the source sign at the parse boundary, with provenance built via _build_provenance().

Parameters:

path (Path) – Source document to ingest.

Return type:

Iterator[ParsedLedgerRow]

Returns:

An iterator that yields one parsed ledger row per source row.

Raises:

InvalidFinancialSourceError – When the document cannot be parsed or a row is malformed.

abstractmethod validate_source(path)[source]

Validate path before ingesting it.

Parameters:

path (Path) – Candidate source document.

Return type:

ProviderValidation

Returns:

A ProviderValidation describing whether the document is ingestable and surfacing any warnings.

describe_dialect(dialect)[source]

Return a compact human-readable dialect description.

Return type:

str

Parameters:

dialect (type[Dialect])

normalize_header(value)[source]

Normalize a column header for alias matching.

Return type:

str

Parameters:

value (str)

coerce_cell_text(value)[source]

Coerce a source value to a stripped string for raw-field storage.

Return type:

str

Parameters:

value (object)

parse_date_value(value, *, day_first=True, label='date')[source]

Parse a bank-statement date or date-time into a date.

Return type:

date

Parameters:
parse_amount_value(value, *, decimal_separator=None)[source]

Parse bank-export numeric text into Decimal without float coercion.

Float coercion is forbidden because bank exports carry exact cents; intermediate floats would silently round (e.g., 1234.561234.5599999...). The parser preserves the sign convention of the source — bracketed (123.45) and trailing-minus 123.45- both decode to a negative decimal.Decimal.

Parameters:
  • value (object) – Raw cell value; accepted as Decimal, int, float (re-parsed via str), or text.

  • decimal_separator (Optional[Literal[',', '.']]) – Optional explicit decimal separator. When omitted, the parser infers it from the rightmost occurrence of , or . in the text.

Return type:

Decimal

Returns:

A Decimal preserving the printed precision and sign.

Raises:

FinancialValidationError – When the value is empty or cannot be parsed.

synthesize_transaction_id(*, provider_name, source_sha256, source_row_index)[source]

Build a deterministic synthetic transaction identifier.

Return type:

str

Parameters:
  • provider_name (str)

  • source_sha256 (str)

  • source_row_index (int)

class ParsedLedgerRow(**data)[source]

Bases: BaseModel

One parsed source row: a magnitude RawTransaction + its flow.

The provider observes the bank export’s sign (or native debit/credit signal) once, at the parse boundary, to choose a TransactionDirection; it then stores the absolute magnitude on raw and discards the sign. Downstream the import action carries direction straight onto the Transaction, never re-deriving flow from a sign that no longer exists.

Variables:
  • raw – The verbatim per-row RawTransaction carrying the non-negative magnitude amount.

  • direction – The authoritative flow direction derived from the source sign at the parse boundary.

Parameters:
raw: RawTransaction
direction: TransactionDirection
direction_from_signed_amount(signed_amount)[source]

Map a source-signed amount to the authoritative TransactionDirection.

A negative source amount is an OUTGOING flow (money out); a positive source amount is INCOMING (money in). The sign is consumed exactly once here, at the adapter boundary, and discarded; the stored amount is the absolute magnitude. A zero amount carries no flow and is rejected by build_raw_transaction() before this is called.

Return type:

TransactionDirection

Parameters:

signed_amount (Decimal)

build_raw_transaction(*, provider, path, source_sha256, source_row_index, provider_transaction_id, booked_date, value_date, amount, currency, counterparty, description, raw_fields)[source]

Create one ParsedLedgerRow from a source-signed amount.

amount is the source-signed value as the parser read it. Its sign is consumed here to choose the TransactionDirection, then the stored RawTransaction carries the absolute magnitude (flow lives in direction, never in the sign). A zero-amount row carries no flow and is refused at the parse boundary, consistent with the manual ledger path.

Raises:

InvalidFinancialSourceError – When amount is zero.

Return type:

ParsedLedgerRow

Parameters:
default_currency()[source]

Return the configured project-default financial currency.

Return type:

str