.. _changelog_v0: V0 Changelog ============ 0.3.0 (2026-09-27) ------------------ Added ~~~~~ * **One licence check for every KaxaNuk library.** Attribution validates its licence through ``kaxanuk-license-client`` 1.0, the package the Backtest Engine, Attribution Analysis and Portfolio Construction share, instead of a validator of its own. It is a new dependency, ``kaxanuk-license-client~=1.0``, which takes the client's fixes without a new attribution release, and it comes from KaxaNuk's private package index, the one the ``pip install`` command of the welcome email names, which serves it to any valid licence. ``services.license_validator`` stays as a thin wrapper with the names attribution and the KaxaNuk Research Lab use: ``validate_license()``, ``_state`` (now the client's own dict, changed in place and never replaced), the licence errors as attributes of the module, and a new ``reset_validation_state()``, which forgets that the process validated so the next call checks again. It also holds ``PRODUCT``, what attribution tells the client about itself: the product ``attribution-analysis``, the key in ``KNAA_API_KEY_KAXANUK``, another server in ``KNAA_LICENSE_SERVER_URL``, the unified licence server by default, and the logger it has always used, ``kaxanuk.attribution_analysis.services.license_validator``. The Research Lab, which puts each user's key in ``KNAA_API_KEY_KAXANUK``, clears ``_state["validated"]`` before validating and catches the errors by name, keeps working as it is. * **``LicenseConfigurationError``**, raised when ``KNAA_LICENSE_SERVER_URL`` or ``KAXANUK_LICENSE_SERVER_URL`` is not an http or https URL a request can be sent to, before anything is sent. The licence errors, ``LicenseError`` and its four subclasses, now inherit from ``AttributionAnalysisError`` and from the client's matching error at once, so both ``except AttributionAnalysisError`` and ``except kaxanuk.license_client.LicenseError`` catch them. * **``KNAA_LICENSE_SERVER_URL``** points attribution, and only attribution, at another licence server. ``KAXANUK_LICENSE_SERVER_URL`` still works, for every KaxaNuk library, and is read after it. Changed ~~~~~~~ * **The licence is checked on the unified KaxaNuk licence server, for this product.** Without a server variable, attribution validates on the licence server that serves every KaxaNuk library instead of on a server of its own, and the request names the product, ``attribution-analysis``, and attribution's version: a licence that does not include Attribution Analysis is refused with a message naming the products it does include. * **The analysis keeps running when the licence server cannot be reached, for 3 days.** A successful check is saved, and inside 24 hours a run does not contact the server at all, as before. When the server cannot be reached, a check saved less than 3 days ago stands in, with a warning each run saying how much of the grace is left. "Cannot be reached" is now anything that is not a verdict on the licence: no connection, a timeout, a redirect, a 400, 408, 422, 425, 429 or 5xx, or a 200 that cannot be read. In 0.2 the grace was 7 days but covered only connection errors and timeouts, and any answer other than a 200, an outage on our side included, rejected the licence outright. The grace runs from the last *successful* check, never past the expiry the server reported, and ignores a check stamped more than an hour in the future, so neither repeated offline runs nor a rolled-back clock can stretch it. * **A rejection is final.** A 401, 402, 403 or 404, or a 200 whose ``ok`` is not ``true``, raises ``LicenseValidationError`` at once and deletes the saved check, so going offline cannot bring a revoked licence back. In 0.2 a rejection left the saved check in place, where it went on standing in offline for up to 7 days after the last success, and a 200 whose ``ok`` was ``"yes"`` or ``1`` was accepted. * **Where the saved checks live.** One file per licence key, named after the key's SHA-256 digest, in the folder ``%LOCALAPPDATA%\KaxaNuk\licence-cache\attribution-analysis`` on Windows and ``$XDG_CACHE_HOME/kaxanuk/licence-cache/attribution-analysis`` elsewhere (``~/.cache`` without ``XDG_CACHE_HOME``); ``KAXANUK_LICENSE_CACHE_DIR`` moves them. Each holds the key's digest, never the key, is signed, is tied to the device and to Attribution Analysis, and is written atomically, so a process that validates several users' keys, like the Research Lab's, never mixes them. ``~/.kaxanuk_attribution_cache``, the single cache of 0.2 and earlier, is neither read nor removed: * *the first run after upgrading needs to reach the licence server**, and the old file can be deleted by hand. * **``.kaxanuk_license`` is read as a ``.env`` file.** The line ``KNAA_API_KEY_KAXANUK=`` is attribution's, and a file holding a single key without a name serves every product; in 0.2 the first line counted, whatever product it named. A ``KNAA_API_KEY_KAXANUK`` holding only spaces now counts as unset instead of being sent as an empty key. * ``validate_license()`` returns the client's ``ValidationResult``: keyword-only, with ``ok``, ``message`` and the licence's ``expires_at``. Code that built attribution's two-field ``ValidationResult`` by position has to name its fields. Every licence message and log line is ASCII and never carries the licence key. * **Development.** ``kaxanuk-license-client`` is the only package taken from KaxaNuk's private index, and it is taken from nowhere else (``[[tool.pdm.source]]`` with ``include_packages`` and ``exclude_packages``): installing the project with PDM needs ``KAXANUK_INDEX_KEY`` set to a licence key. * **Read the Docs no longer installs the package.** Installing it would need the licence client, which only the private index serves: autodoc imports the package from ``src/``, ``kaxanuk.license_client`` is mocked, ``docs/source/conf.py`` reads the version from the source instead of importing it, and the ``docs`` dependency group now also lists the package's runtime dependencies. Fixed ~~~~~ * The offline-grace warning reached nobody on a Spanish Windows console. Its em dash cannot be encoded in cp850, and the bare ``StreamHandler`` in ``services.configuration_logger`` turned that into a ``UnicodeEncodeError`` inside ``logging``: the user saw ``--- Logging error ---`` and the warning itself was lost. The console is now moved to UTF-8 when it is not on it already, a console that refuses to be reconfigured gets the record with the offending characters replaced rather than no record at all, and the optional log file is written as UTF-8 instead of in the machine's codepage. 0.2.0 (2026-07-12) ------------------ Added ~~~~~ * Input handlers now slices the file right away with the actual start date. Fixed ~~~~~ * Formating in the [attribution_analysis_parameters.xlsx](templates/attribution_analysis/Config/attribution_analysis_parameters.xlsx) configuration 0.1.0 (2026-07-03) ------------------ Added ~~~~~ * Brinson-Fachler attribution, decomposing active return into allocation, selection and interaction effects per asset and date. * KN Five-Factor Model (KN5FM) attribution, decomposing portfolio return into per-factor contributions plus an idiosyncratic residual. * Excel-based configuration workbook (``attribution_analysis_parameters.xlsx``) and CLI (``init``, ``run``, ``update``, ``autorun``). * CSV and Parquet market data input handlers, and CSV/Excel portfolio and benchmark weight handlers. * Interactive Dash dashboard for cumulative factor contributions and summary statistics. * License key validation against the KaxaNuk license server, with local caching and an offline grace period. * Added a data-integrity check to PortfolioWeights that rejects weight series without ~daily (240–260/year) observation density, catching backtest-granularity files passed in by mistake. * Auto date alignment across all input tables (asset returns, portfolio/benchmark weights, benchmark returns, factor returns) to their common date range before running any calculation. * User-defined column name mapping for market-data inputs, so CSV/Parquet files don't need to match a fixed schema. * Performance statistics module (Sharpe ratio, annualized return/volatility, skewness, kurtosis, max drawdown, historic and Gaussian VaR/CVaR) surfaced in the dashboard's summary panel. * Synthetic factor-exposure generator for prototyping the factor model pipeline without real factor data. * PyArrow-based columnar architecture used throughout for high-performance, vectorized computation. * Pandas-based reference implementations of both attribution methodologies, provided alongside the PyArrow implementations for validation and comparison. * Excel-workbook schema-version check that prompts ``update excel`` when the configuration template is outdated.