Source code for kaxanuk.attribution_analysis.portfolio_data_pipeline.portfolio_service

"""
Portfolio Service for loading portfolio and benchmark weight files.

Orchestrates the loading of portfolio weight files through a sequence
of input handlers (CSV, Excel), returning a raw PyArrow table.
"""

import datetime
import logging
from typing import Literal

import pyarrow as pa

from kaxanuk.attribution_analysis.exceptions import MissingPortfolioError
from kaxanuk.attribution_analysis.input_handlers.portfolio_input_handler_interface import (
    PortfolioInputHandlerInterface,
)
from kaxanuk.attribution_analysis.portfolio_data_pipeline import portfolio_transformer

logger = logging.getLogger(__name__)


[docs] class PortfolioService: """ Loads portfolio/benchmark weight files using available input handlers. """
[docs] @staticmethod def load_portfolio( portfolio_handlers: list[PortfolioInputHandlerInterface], file_name: str, start_date: datetime.date | Literal["auto"] | None = None, end_date: datetime.date | Literal["auto"] | None = None, ) -> pa.Table: """ Load a portfolio/benchmark weights file using the first handler able to read it. Parameters ---------- portfolio_handlers : list[PortfolioInputHandlerInterface] Input handlers to try, in order, until one succeeds. file_name : str File name (without extension) of the portfolio/benchmark weights file to load. start_date : datetime.date or {"auto"} or None, optional Configured start of the analysis period, typically `Configuration.start_date`. When given (and not ``"auto"``), the loaded table's dates must include at least one date on or after `start_date`. Checked immediately after this (cheap) file loads, so a misconfigured date fails fast instead of after the heavier market data/factor files are loaded. end_date : datetime.date or {"auto"} or None, optional Configured end of the analysis period, checked the same way as `start_date`. Returns ------- pa.Table The raw table returned by the successful handler. Raises ------ MissingPortfolioError If every handler in `portfolio_handlers` fails to load `file_name`. DateRangeError If `start_date`/`end_date` don't overlap any date in the loaded table. """ errors: list[str] = [] for handler in portfolio_handlers: try: table = handler.read_portfolio(file_name=file_name) except Exception as exc: # noqa: BLE001 - intentional: try each handler, aggregate failures errors.append(f"{type(handler).__name__}: {exc!s}") continue portfolio_transformer.validate_portfolio_date_range( table=table, start_date=start_date, end_date=end_date, source_name=f"portfolio '{file_name}'", ) logger.info( "Loaded portfolio '%s' via %s (%d tickers)", file_name, type(handler).__name__, table.num_rows, ) return table raise MissingPortfolioError( f"Could not load portfolio '{file_name}':\n" + "\n".join(errors) )