Skip to content

infrastructure.providers.chase.mappers.account_mapper

src.infrastructure.providers.chase.mappers.account_mapper

Chase account mapper.

Converts parsed QFX account data to ProviderAccountData.

Reference
  • OFX ACCTTYPE specification

Classes

ChaseAccountMapper

Mapper for converting parsed QFX account data to ProviderAccountData.

Thread-safe: No mutable state, can be shared across requests.

Example

mapper = ChaseAccountMapper() parsed = parser.parse(file_bytes) account_data = mapper.map_account(parsed.value)

Source code in src/infrastructure/providers/chase/mappers/account_mapper.py
class ChaseAccountMapper:
    """Mapper for converting parsed QFX account data to ProviderAccountData.

    Thread-safe: No mutable state, can be shared across requests.

    Example:
        >>> mapper = ChaseAccountMapper()
        >>> parsed = parser.parse(file_bytes)
        >>> account_data = mapper.map_account(parsed.value)
    """

    def map_account(self, parsed: ParsedAccount) -> ProviderAccountData:
        """Map parsed QFX account to ProviderAccountData.

        Args:
            parsed: ParsedAccount from QFX parser.

        Returns:
            ProviderAccountData with account information.
        """
        # Map account type
        account_type = self._map_account_type(parsed.account_type)

        # Create masked account number
        masked = self._mask_account_number(parsed.account_id)

        # Generate account name
        type_label = parsed.account_type.title()
        name = f"Chase {type_label} {masked}"

        # Get balance info (default to 0 if not present)
        balance = parsed.balance.ledger_balance if parsed.balance else Decimal("0")
        available = parsed.balance.available_balance if parsed.balance else None

        return ProviderAccountData(
            provider_account_id=parsed.account_id,
            account_number_masked=masked,
            name=name,
            account_type=account_type,
            balance=balance,
            currency=parsed.currency,
            available_balance=available,
            is_active=True,  # File imports are assumed active
            raw_data={
                "bank_id": parsed.bank_id,
                "account_type_raw": parsed.account_type,
                "transaction_count": len(parsed.transactions),
            },
        )

    def _map_account_type(self, raw_type: str) -> str:
        """Map Chase account type to Dashtam account type.

        Args:
            raw_type: Account type from QFX file.

        Returns:
            Mapped AccountType value.
        """
        normalized = raw_type.upper().strip()
        mapped = CHASE_ACCOUNT_TYPE_MAP.get(normalized)

        if mapped is None:
            logger.warning(
                "chase_unknown_account_type",
                raw_type=raw_type,
                defaulting_to="other",
            )
            return AccountType.OTHER.value

        return mapped

    def _mask_account_number(self, account_id: str) -> str:
        """Mask account number for display.

        Args:
            account_id: Full account number.

        Returns:
            Masked account number like "****1234".
        """
        if len(account_id) >= 4:
            return f"****{account_id[-4:]}"
        return f"****{account_id}"
Functions
map_account
map_account(parsed: ParsedAccount) -> ProviderAccountData

Map parsed QFX account to ProviderAccountData.

Parameters:

Name Type Description Default
parsed ParsedAccount

ParsedAccount from QFX parser.

required

Returns:

Type Description
ProviderAccountData

ProviderAccountData with account information.

Source code in src/infrastructure/providers/chase/mappers/account_mapper.py
def map_account(self, parsed: ParsedAccount) -> ProviderAccountData:
    """Map parsed QFX account to ProviderAccountData.

    Args:
        parsed: ParsedAccount from QFX parser.

    Returns:
        ProviderAccountData with account information.
    """
    # Map account type
    account_type = self._map_account_type(parsed.account_type)

    # Create masked account number
    masked = self._mask_account_number(parsed.account_id)

    # Generate account name
    type_label = parsed.account_type.title()
    name = f"Chase {type_label} {masked}"

    # Get balance info (default to 0 if not present)
    balance = parsed.balance.ledger_balance if parsed.balance else Decimal("0")
    available = parsed.balance.available_balance if parsed.balance else None

    return ProviderAccountData(
        provider_account_id=parsed.account_id,
        account_number_masked=masked,
        name=name,
        account_type=account_type,
        balance=balance,
        currency=parsed.currency,
        available_balance=available,
        is_active=True,  # File imports are assumed active
        raw_data={
            "bank_id": parsed.bank_id,
            "account_type_raw": parsed.account_type,
            "transaction_count": len(parsed.transactions),
        },
    )