from __future__ import annotations from dataclasses import dataclass from datetime import date, datetime from decimal import Decimal from pathlib import Path @dataclass(frozen=True) class NormalizedTransaction: source_file: Path sheet_name: str source_row: int transaction_at: datetime income: Decimal expense: Decimal balance: Decimal | None own_account: str | None own_name: str | None counterparty_account: str | None counterparty_name: str | None counterparty_bank: str | None summary: str | None purpose: str | None reference: str | None currency: str | None @dataclass(frozen=True) class StatementBatch: source_file: Path sheet_name: str bank_name: str template_id: str header_row: int own_account: str | None own_name: str | None period_start: date | None period_end: date | None transactions: tuple[NormalizedTransaction, ...] warnings: tuple[str, ...] template_version: int = 1 @dataclass(frozen=True) class SheetResult: """Per-worksheet parse outcome with the four diagnostic evidence items. ``outcome`` is the deterministic parse verdict: ``parsed`` (transactions normalized), ``exception`` (template/header/balance problems) or ``ignored`` (empty or single-cell sheets with no bank content). The human decision about whether a parsed sheet may participate in calculations is a separate lifecycle stage (``review_status``), persisted in ``sheet_reviews``; it is never decided here. """ sheet_name: str outcome: str # parsed | exception | ignored message: str | None = None scanned_rows: int | None = None candidate_headers: tuple[str, ...] = () batch: StatementBatch | None = None