# Change log > Migrating from v0.5 or earlier? See the [migration guide](migration-ref) > for before/after snippets covering every breaking change in v0.6–v0.7. ## v0.8.2 _Released on 05-06-2026_ ### Added - **Database-backed `ScenarioResult` persistence** — `SqlScenarioRepository` now writes results to the database via the same dual-path strategy as `DatabaseDataManager`. :::{dropdown} {octicon}`light-bulb` Details :color: light Results whose subclass implements the new `algomancy_scenario.persistence.SqlResultLayout` protocol are written as real, externally queryable SQL tables (one per sub-table, named `result______`); everything else falls back to a JSON blob in `algomancy_scenario_runs.result_blob` via the new abstract `to_json` / `from_json` contract on `BaseScenarioResult`. Typed rehydration on restart uses the algorithm's `result_class` attribute (defaults to the bundled `ScenarioResult`). `SqlScenarioRepository.delete` cleans up any per-scenario result tables alongside the run/KPI rows. ::: ### Changed - Actualized the quickstart documentation page. - `StatefulDataManager` marked deprecated; `DatabaseDataManager` should be used for persistent deployments; `StatelessDataManager` is the default for in-memory use. - **`BaseScenarioResult` now requires `to_json` / `from_json`** — abstract on the base, concrete on the bundled `ScenarioResult`. **Breaking** for custom subclasses. :::{dropdown} {octicon}`light-bulb` Details :color: light Add `to_json(self) -> str` and a `from_json(cls, json_string) -> BaseScenarioResult` classmethod to existing `BaseScenarioResult` subclasses, or implement `algomancy_scenario.persistence.SqlResultLayout` to land DataFrames in real SQL tables instead of a JSON blob. The contract mirrors `BaseDataSource.to_json` / `from_json` exactly. ::: - **`BaseAlgorithm.result_class`** — new class attribute (defaults to `ScenarioResult`) used by the repository to rehydrate persisted results into their original type; override on algorithms whose `run()` returns a custom subclass. ### Fixed - Fixed a bug where playwright tests would fail to initialize due to an erroneous version lookup. - Added documentation for `DatabaseDataManager`. ## v0.8.1 _Released on 05-06-2026_ ### Added - **Data Parameters** — per-scenario knobs declared by `BaseDataSource` subclasses. :::{dropdown} {octicon}`light-bulb` Details :color: light A concrete `BaseDataSource` subclass can override the new `initialize_data_parameters()` method to return a typed `BaseParameterSet` describing data-side knobs (date range, region filter, category whitelist, ...). The framework collects user-supplied values per scenario, persists them alongside the algorithm parameters, and pushes them onto the algorithm via `BaseAlgorithm.set_data_params` before `run()`. Algorithms read `self.data_params` and decide whether and how to act on them — nothing is applied automatically. Surfaces: - `BaseDataSource.initialize_data_parameters()` — default returns `EmptyParameters()`, so existing subclasses keep working unchanged. - `BaseAlgorithm.data_params` property + `set_data_params(...)` method. - `Scenario` accepts a `data_params` kwarg and pushes it onto the algorithm before each `run()`. - `ScenarioFactory.get_associated_parameters(algo_name, dataset_key)` returns the `(algo_params, data_params)` tuple, resolving the data half via the selected data source. - `ScenarioManager.create_scenario(..., data_params=...)` and `ScenarioManager.get_data_parameters(dataset_key)`. - GUI: a second parameter card renders next to the algorithm card in the scenario-creation modal, populated as soon as the user picks a dataset. - API: `GET /api/v1/sessions/{sid}/data/{dataset_key}/parameters` returns the descriptor; `POST /scenarios` accepts a new `data_params` field. - Persistence: new nullable `data_parameter_values TEXT` column on `algomancy_scenarios`, added idempotently via `ALTER TABLE` on startup so existing databases keep loading. See {ref}`Data Parameters ` for the data-source declaration pattern and {ref}`Algorithms and Parameters ` for the algorithm-side read pattern. ::: - **`SqlTableLayout` protocol** for database persistence for custom data sources. :::{dropdown} {octicon}`light-bulb` Details :color: light `DatabaseDataManager` now works for any ``BaseDataSource`` subclass, not just the bundled ``DataSource``. Two persistence paths are dispatched at write time: - Subclasses that implement the new ``algomancy_data.database.SqlTableLayout`` protocol (``to_sql_tables() -> dict[str, DataFrame]`` and ``from_sql_tables(tables)``) get per-sub-table SQL storage — DataFrames land in real ``ds__{session}__{dataset}__{sub}`` tables and stay externally queryable. - Subclasses that don't implement it fall back to JSON-blob persistence via the abstract ``to_json``/``from_json`` API already required by ``BaseDataSource``. The catalogue grows a nullable ``payload`` column to hold the blob. The bundled ``DataSource`` satisfies ``SqlTableLayout`` trivially via its ``tables`` dict, so existing deployments see no behavioural change. Pre-existing ``algomancy_datasets`` tables that are missing the ``payload`` column now raise a clear error on startup directing the user at the manual-rebuild path. See {ref}`Database persistence of custom data sources ` for the protocol and a worked example. ::: - Added infrastructure to delete sessions. New ``SessionManager.delete_session(id)`` is used by a GUI interface element and the ``DELETE /api/v1/sessions/{session_id}`` API endpoint. ### Breaking - Session identity is now a UUID; new property ``display_name`` is the mutable label. :::{dropdown} {octicon}`light-bulb` Details :color: light Previously a session's identifier was its user-facing string (``"main"``, ``"alice_experiment"``), which made renaming impossible and made URLs leak human-readable names. The new set-up features: - Each session has a stable UUID ``id`` and a separate mutable ``display_name``. - API routes use UUIDs: ``/api/v1/sessions/{session_uuid}/...``. The list endpoint returns ``{sessions: [{id, display_name}, ...], default: id}``. - New ``PATCH /api/v1/sessions/{id}`` renames a session (display_name only). - ``CreateSessionRequest.name`` is now ``display_name``; ``CopySessionRequest.new_name`` is now ``new_display_name``. - Filesystem backend writes a small ``meta.json`` into each session directory carrying ``{id, display_name}``. Directories without ``meta.json`` are migrated transparently on first scan. - DB backend changes ``algomancy_sessions`` from ``(name PK)`` to ``(id PK, display_name)``. - ``SessionManager`` API renames: ``sessions_names`` → ``session_ids``, ``start_session_name`` → ``start_session_id``, new ``list_sessions()``, ``get_display_name()``, ``rename_session()``, ``resolve_id_by_display_name()``. - **Migration:** run ``python scripts/migrate_m14_session_ids.py [--data-path DIR] [--database-url URL]`` once per deployment. The filesystem migration is also performed automatically on first scan, so upgrading without running the script will still work — the script just makes the rewrite eager and visible. For API clients, the resolver still accepts the old ``display_name`` in URLs (e.g. ``/sessions/main/...``) as a soft alias, so existing integrations keep working while you migrate them to UUIDs. ::: - Sessions are now always used :::{dropdown} {octicon}`light-bulb` Details :color: light The `CoreConfig.use_sessions` attribute was **removed** (and the same flag on `ApiConfiguration`). `SessionManager` is now always constructed; single-tenant deployments simply get a single auto-created `"main"` session. The `/health` endpoint no longer reports `use_sessions`. - **Migration:** delete `use_sessions=...` from your `CoreConfig` / `ApiConfiguration` / `AppConfig` arguments. If you were using `use_sessions=False` to hide the session picker from the GUI admin page, set `FeatureConfig(show_session_picker=False)` instead — the runtime keeps a session under the hood either way. - The GUI server attribute `app.server.use_sessions` is gone. Code that branched on it should use `app.server.session_manager` unconditionally; code that gated picker visibility should use `app.server.show_session_picker`. ::: ## v0.7.0 ### Added - **`algomancy-api` package** — new FastAPI HTTP service exposing the full `ScenarioManager` / `SessionManager` surface over HTTP, so remote frontends can drive an Algomancy backend over the network. :::{dropdown} {octicon}`light-bulb` Details :color: light The HTTP layer is deliberately thin — every route maps to a single manager method and responses reuse the existing `to_dict()` payloads. There is no parallel domain model; clients work with the same `Scenario`, `DataSource`, and `KPI` concepts the GUI does. Launch with `algomancy-api --config-callback myapp:make_config` (default port `8051`) or `algomancy-api --example` for the bundled wiring. `ApiLauncher.build(cfg)` returns a standard `FastAPI` instance for use behind a production uvicorn/gunicorn process manager. - **Sessions router** — `GET/POST /sessions`, `POST /sessions/{sid}/copy` with session-name validation and conflict handling. - **Algorithm + KPI discovery router** — `GET /sessions/{sid}/algorithms`, `GET /sessions/{sid}/algorithms/{name}/parameters` (parameter descriptors derived from `BaseParameterSet`), and `GET /sessions/{sid}/kpis`. - **Scenarios router** — full CRUD plus `POST /sessions/{sid}/scenarios/{id}/run`, `GET /sessions/{sid}/scenarios/{id}/status` for polling, and `GET /sessions/{sid}/processing` for the currently running scenario. - **Data router** — list, fetch, delete, derive, `POST /sessions/{sid}/data/from-json` to add a dataset from a `DataSource.to_json()` payload, and `POST /sessions/{sid}/etl` to run ETL over a multipart upload. - **Meta** — `GET /health` liveness probe; OpenAPI schema at `/openapi.json` and Swagger UI at `/docs`. ::: - **Error mapping** — single exception → HTTP-status map (`ValueError` → 400, `ParameterError` → 400, `AssertionError` → 409, anything else → 500); response shape is always `{"detail": ""}`. - **End-to-end smoke test** (`tests/test_smoke_live.py`) drives the full session → scenario → run → poll → fetch flow against a live server. - **`algomancy-scenario` surface** — `KPIFactory`, `ScenarioFactory`, and `ScenarioManager` now expose algorithm/KPI template names for API discovery. - **Documentation** — new "Frontends" fundamentals page and HTTP API reference (`docs/source/reference/api.md`); refreshed `algomancy-api` README with quick-start and endpoint inventory. ### Changed - `SessionManager` was lifted out of `algomancy-gui` into `algomancy-scenario` so it can be shared by all three frontends without pulling in Dash. ### Fixed - Bugfix to `BaseKPI` surfaced while wiring up the algorithms router. ## 0.6.0 _Released on 2026-05-29_ The 0.6.0 release is the first instalment of the algomancy-data ETL/schema overhaul (milestones M1–M8). The chain of changes is large but additive where possible; consult the [migration guide](migration-ref) for the breaking-change summary. ### Added - **Schema API modernization (M1)** — new `Column` dataclass gives schemas a declarative field model; `Schema` gains `columns()`, `required_columns()`, `optional_columns()`, and `primary_key()` classmethods. :::{dropdown} {octicon}`light-bulb` Details :color: light `Column` fields: `name`, `dtype`, `optional`, `primary_key`, `default`, `nullable`, `unique`, `description`. `Column` is exported from the public `algomancy_data` API. Example schemas migrated to the Column style; legacy `_DATATYPES` dicts auto-convert with a `DeprecationWarning`. ::: - **Structured validation framework (M2)** — `ValidationMessage` gains location fields; `run_validation` returns a rich `ValidationResult` dataclass; new built-in validators added. :::{dropdown} {octicon}`light-bulb` Details :color: light - `ValidationMessage` gains optional `table`, `column`, `row`, and `code` fields. `__str__` surfaces the location inline; `__eq__` / `to_dict()` added for inspection and serialisation. - `ValidationSequence.run_validation` now returns a `ValidationResult` dataclass (`is_valid`, `messages`, `halt_on`, `counts_by_severity`, plus `as_dataframe()`, `messages_by_severity()`, `messages_at_least()`, and `__bool__` / `__iter__` / `__len__` helpers). - `ValidationSequence.halt_on` (default `CRITICAL`) lets projects promote `ERROR` or `WARNING` to a pipeline-halting severity without subclassing. - New built-in validators: `RequiredColumnsValidator`, `OptionalColumnGuard` (validator/transformer hybrid that injects missing optional columns with their declared default and dtype), `PrimaryKeyValidator` (joint-key uniqueness + non-null), `UniqueValueValidator`, and `MissingValueValidator`. ::: - **Predictable ETL termination (M3)** — `ETLPipeline.run()` returns an `ETLResult` dataclass; data-quality failures surface cleanly rather than as exceptions. :::{dropdown} {octicon}`light-bulb` Details :color: light `ETLResult` carries `status`, `datasource`, `validation_result`, `raised`, plus `is_success` / `is_failure` / `messages` helpers. Data-quality failures arrive as `ETLResult(status='failed')`; programmer errors (KeyError/AttributeError/TypeError) still propagate so real defects aren't masked. `DataManager.etl_data` / `ScenarioManager.etl_data` return the result; auto-create only fires on success. `DataTypeConverter` records `ConversionIssue` objects instead of silently producing `NaN`s; these surface as `CONVERSION_FAILED` messages. `StatefulDataManager.startup()` records per-item failures in `self.startup_errors` and rolls back failing keys. ::: - **Reduce ETL boilerplate (M4)** — extractor registry and `SimpleETLFactory` let users build a full pipeline without subclassing `ETLFactory`. :::{dropdown} {octicon}`light-bulb` Details :color: light New extractor registry (`register_extractor`, `get_extractor_class`, `registered_keys`) keyed on `(FileExtension, SchemaType)`. The four built-in extractors (CSV single, JSON single, XLSX single, XLSX multi) populate it at import time. `ETLFactory` now ships with default implementations of `create_extraction_sequence`, `create_validation_sequence`, `create_transformation_sequence`, and `create_loader` — no longer `@abstractmethod`. New `SimpleETLFactory(schemas, transformers=None, loader=None, logger=None)` is a concrete factory requiring no subclassing. `DataManager.prepare_files` dispatches by the schema's declared `_EXTENSION`, falling back to the path suffix. ::: - **Flexibility & extension points (M5)** — `register_extractor` is now public; new `ForeignKeyValidator` and `DataFrameExtractor`; docs on extending the registry. :::{dropdown} {octicon}`light-bulb` Details :color: light `Schema.extension()` accepts any `StrEnum`-derived value or plain string, so user-defined `FileExtension` subclasses route through the registry without changes. `ForeignKeyValidator` checks every (non-null) value in `left_table[left_col]` exists in `right_table[right_col]`; supports composite keys; emits structured messages with `FK_VIOLATION`, `TABLE_NOT_FOUND`, and `COLUMN_NOT_FOUND` codes. `DataFrameExtractor` wraps a pre-built pandas `DataFrame` via the same `Extractor` contract. New docs page `fundamentals/extending.md` covers subclassing `FileExtension`, registering an extractor, and extending `DataTypeConverter`. ::: - **Docs, tests, migration (M6)** — `algomancy-data` coverage at 87%; `docs/source/fundamentals/ETL.md` rewritten end-to-end; single migration guide added. :::{dropdown} {octicon}`light-bulb` Details :color: light `pytest-cov` added; `algomancy-data` coverage reports 87%. `docs/source/fundamentals/ETL.md` opens with a `SimpleETLFactory` quickstart, then covers Column-based schemas, the extractor registry, new validators, structured validation messages, `ETLResult`-based termination, and a full worked example. New `docs/source/migration.md` covers every breaking change in v0.6/0.7/0.8 with before/after snippets. Bundled example refreshed: `ExampleETLFactory` inherits from `SimpleETLFactory`, demonstrates `OptionalColumnGuard` and `ForeignKeyValidator`. ::: - **Relational cascade cleanup (M7)** — declarative cascade-drop mechanism via `CascadeDropTransformer` and `CascadeSnapshot`; drops surface as structured `ValidationMessage`s. :::{dropdown} {octicon}`light-bulb` Details :color: light Declare foreign keys via `Column(foreign_key=("parent_table", "parent_col"))`, opt parents into cascade with `parent_requires_child=True`, and optionally `track_partial_loss=True`. `CascadeDropTransformer` drops orphans and parents-with-missing-children to a fixpoint; `CascadeSnapshot` enables partial-loss detection. Drop messages carry codes `CASCADE_ORPHAN_DROP`, `CASCADE_REQUIRED_CHILD_DROP`, and `CASCADE_PARTIAL_LOSS_DROP` at `Severity.ERROR`. `ForeignKeyValidator.from_schemas([...])` auto-derives validators from the same declarations. Fully opt-in: pipelines without the transformer are unaffected. ::: - **Quickstart modernization (M8)** — `algomancy-quickstart` templates updated to emit Column-based schemas, `SimpleETLFactory`-derived ETL factories, and new validation defaults out of the box. - **Quickstart Module** (`algomancy-quickstart`) — interactive setup wizard that guides new users through creating a full Algomancy application in five steps. :::{dropdown} {octicon}`light-bulb` Details :color: light The `QuickstartWizard` covers: (1) creating folder structure and a basic `main.py`; (2) generating implementation templates for schemas, algorithms, KPIs, ETL factories, and custom pages; (3) scanning data files and generating ETL pipelines with schema inference; (4) installing default assets (CSS, images) from GitHub or a bundled fallback; and (5) configuring custom styling with colors and themes. Features interactive prompts, intelligent file detection (CSV, XLSX, JSON), automatic datatype inference with column mapping, and Jinja2-based code generation. ::: - Added character-safe and existing scenario name checks (via `InputChecker`), resulting in a disabled create button when invalid. - Added `list_tags` function to the scenario registry, accessible via `ScenarioManager`. - `BaseAlgorithm` now has access to the application's central logger through `self._logger`. - Added pages section to the tutorial. ### Changed - **Cleanup of dependencies between `algomancy-content` and `algomancy-gui`.** :::{dropdown} {octicon}`light-bulb` Details :color: light - **[Breaking]** `BasePage` and subclasses moved to `algomancy_gui`; update imports accordingly. - `LibraryManager` moved to `algomancy-gui` from `algomancy-content`. - `algomancy-gui` internals reorganized. ::: - **Refinement of `Schema` class.** :::{dropdown} {octicon}`light-bulb` Details :color: light - **[Breaking]** `_defined_datatypes` moved to `_DATATYPES` as a class attribute. - `Schema`s no longer need to be instantiated before being passed to the configuration — passing types works; passing instances still works. - **[Breaking]** Schema classproperties (`datatypes`, `datatype_groups`, `sub_names`, `file_name`, `extension`) are now explicit `@classmethod`s — call sites must use `()`. The `classproperty` shim was removed; `get_subschema()` now returns a dynamic class rather than an instance, eliminating subschema instance state. ::: - Made `dataset_name_invalid` generic (`name_invalid`) for both datasets and scenarios. - Moved `name_invalid` callback to `inputchecker.py` to avoid duplicate code. - Euro (€) is now the default quantity for money. - Split `AppConfiguration` into separate configuration containers for better organization. _(backwards compatible)_ - Implemented the Singleton pattern on `Logger` for global access. - `BASE_DATA_BOUND` renamed to `BASEDATASOURCE` for legibility. - `fill_empty()` in `transformer.py`: replaced deprecated `fillna(method=...)` with `DataFrame.ffill(axis=1)`. - `DataManagementImportModal` now reads `ETLResult` and shows validation message counts on failure (back-compat catch on `ValidationError` preserved). ### Fixed ## 0.4.4 _Released on 23-2-2026_ ### Added - Documentation updates: reference is now complete with some todos ## 0.4.3 _Released on 18-2-2026_ ### Added - Added `BaseDataSource._post_derive()` method as a hook to allow subclasses to perform post-derivation processing. - Added `_post_derive()` PyTest to pre-merge testing protocol. ## 0.4.2 _Released on 17-2-2026_ ### Added - Added `test_datasource.py` to the PyTest pre-merge protocol. ### Fixed - Fixed bug where `BaseDataSource.derive()` would not update `ds_type` and `id` appropriately. - Fixed bug where `DataSource.from_json()` would not preserve datatypes. ## 0.4.0 _Released on 12-02-2026_ ### Changed - **[Breaking]** Removed `InputFileConfigs`, moved responsibility to `Schema` class - Updated documentation - Updated tutorial code (data, root files, assets, data handling, algorithms, KPIs) ### Migration from InputFileConfiguration to Schema-only pattern Prior to this release, file configurations were specified separately using `SingleInputFileConfiguration` and `MultiInputFileConfiguration`. These have been removed, and the `Schema` class now contains all necessary file metadata. :::{dropdown} {octicon}`move-to-end` Migration example :color: secondary #### For single-file schemas (CSV, JSON, XLSX with one sheet): **Old version** ```python from algomancy_data import Schema, FileExtension, DataType, SingleInputFileConfiguration class WarehouseLayoutSchema(Schema): ID = "slotid" X = "x" Y = "y" ZONE = "zone" @property def datatypes(self) -> Dict[str, DataType]: return { WarehouseLayoutSchema.ID: DataType.STRING, WarehouseLayoutSchema.ZONE: DataType.STRING, WarehouseLayoutSchema.X: DataType.FLOAT, WarehouseLayoutSchema.Y: DataType.FLOAT, } # File configuration was separate warehouse_config = SingleInputFileConfiguration( extension=FileExtension.CSV, file_name="warehouse_layout", file_schema=WarehouseLayoutSchema(), ) ``` **New version** ```python from algomancy_data import Schema, FileExtension, DataType from algomancy_data.schema import SchemaType class WarehouseLayoutSchema(Schema): # File metadata now lives in the Schema class _FILENAME = "warehouse_layout" _EXTENSION = FileExtension.CSV _SCHEMA_TYPE = SchemaType.SINGLE ID = "slotid" X = "x" Y = "y" ZONE = "zone" def _defined_datatypes(self) -> Dict[str, DataType]: return { WarehouseLayoutSchema.ID: DataType.STRING, WarehouseLayoutSchema.ZONE: DataType.STRING, WarehouseLayoutSchema.X: DataType.FLOAT, WarehouseLayoutSchema.Y: DataType.FLOAT, } # No separate configuration needed - just instantiate the schema warehouse_schema = WarehouseLayoutSchema() ``` **Key changes for single-file schemas:** - Add `_FILENAME`, `_EXTENSION`, and `_SCHEMA_TYPE = SchemaType.SINGLE` as class attributes - Rename `datatypes` property to `_defined_datatypes()` method - Remove the separate `SingleInputFileConfiguration` wrapper #### For multi-sheet XLSX files: **Old version** ```python from algomancy_data import Schema, FileExtension, DataType, MultiInputFileConfiguration class StedenSchema(Schema): COUNTRY = "Country" CITY = "City" @property def datatypes(self) -> Dict[str, DataType]: return { StedenSchema.COUNTRY: DataType.STRING, StedenSchema.CITY: DataType.STRING, } class KlantenSchema(Schema): ID = "ID" Name = "Naam" @property def datatypes(self) -> Dict[str, DataType]: return { KlantenSchema.ID: DataType.INTEGER, KlantenSchema.Name: DataType.STRING, } # Separate configuration with multiple schemas multisheet_config = MultiInputFileConfiguration( extension=FileExtension.XLSX, file_name="multisheet", file_schemas={ "Steden": StedenSchema(), "Klanten": KlantenSchema(), }, ) ``` **New version** ```python from algomancy_data import Schema, FileExtension, DataType from algomancy_data.schema import SchemaType class LocationSchema(Schema): # File metadata in the schema _FILENAME = "multisheet" _EXTENSION = FileExtension.XLSX _SCHEMA_TYPE = SchemaType.MULTI # All column names from all sheets in one schema # steden sheet COUNTRY = "Country" CITY = "City" # klanten sheet ID = "ID" Name = "Naam" def _defined_datatypes(self) -> Dict[str, Dict[str, DataType]]: # Return a dict of sheet names to column datatypes return { "Steden": { LocationSchema.COUNTRY: DataType.STRING, LocationSchema.CITY: DataType.STRING, }, "Klanten": { LocationSchema.ID: DataType.INTEGER, LocationSchema.Name: DataType.STRING, }, } # No separate configuration needed location_schema = LocationSchema() ``` **Key changes for multi-sheet schemas:** - Combine all sheet schemas into a single `Schema` class - Add `_FILENAME`, `_EXTENSION`, and `_SCHEMA_TYPE = SchemaType.MULTI` as class attributes - Rename `datatypes` property to `_defined_datatypes()` method - Return `Dict[str, Dict[str, DataType]]` (sheet name → column datatypes) instead of `Dict[str, DataType]` - Remove the separate `MultiInputFileConfiguration` wrapper and individual sheet schemas ::: ## 0.3.21 _12-02-2026_ ### Added - Added charactersafe & existing dataset name checks (in InputChecker Class), resulting in a disabled import button ### Changed - Custom pages should now subclass the appropriate base classes (moved from `Protocol` to `AbstractBaseClass`). Functional implementation should remain unchanged. ### Fixed - Fixed a bug where the overview page failed to use the `OverviewPage` content from the registry appropriately. - Fixed risk of App breaking down when user tries to import/derive a new dataset with weird names (e.g. with .) or an already existing dataset name. ## 0.3.20 ### Fixed - `StandardDataPage` now work again when sessions are enabled ## 0.3.17 ### Fixed - `StandardHomePage` and `StandardDataPage` now work again when sessions are disabled ## 0.3.16 ### Changed - `GuiLauncher` was moved to `algomancy-gui` - Updated `numpy` dependency to `2.4.1` due to yanked version `2.4.0` ## 0.3.13 ### Added - Added `use_sessions` atribute to `AppConfiguration` to allow disabling sessions. ## 0.3.5 - 0.3.12 Revised distribution model; the package is now hosted at `pypi.org/project/algomancy` and installable with the terminal command: ``` uv add algomancy ``` ## 0.3.4 _Released at 07-01-2026_ ### Added - Added `copy()` to `BaseParameterSet` and corresponding tests. - Introduced locking to `BaseParameterSet` to prevent concurrent mutation. - Added `get_parameters` responsibility across factories/facades and session manager. - Work-in-progress Command Line Interface (CLI). ### Changed - **[Breaking]** Renamed `BaseAlgorithmParameters` to `BaseParameterSet` and moved to `algomancy-utils` in preparation for data parameters - Migration to UV workspaces for the monorepo structure. - Units/measurements refactor in `algomancy-utils`: moved formatting to `BaseMeasurement`; removed `Unit.name`. - Various import path updates across packages after workspace migration. - **[Breaking] algomancy-data (ETL): Unified ETL pipeline concepts.** - Introduced `ExtractionSequence` and `TransformationSequence` as the orchestration primitives for extract and transform steps. - `ETLPipeline` now accepts `extraction_sequence` and `transformation_sequence` instead of a dict of `extractors` and a list of `transformers`. - `ETLFactory` abstract methods have been renamed: - `create_extractors(...)` → `create_extraction_sequence(...)` - `create_transformers()` → `create_transformation_sequence()` - Validation continues to use `ValidationSequence` unchanged. - See updated examples in `example/data_handling/factories.py` and implementation in: - `packages/algomancy-data/src/algomancy_data/etl.py` - `packages/algomancy-data/src/algomancy_data/extractor.py` - `packages/algomancy-data/src/algomancy_data/transformer.py` #### Migration notes (ETL) - Replace factory method implementations and usages: - Implement `create_extraction_sequence(files)` to build and return an `ExtractionSequence` (use `sequence.add_extractor(...)`). - Implement `create_transformation_sequence()` to build and return a `TransformationSequence` (use `sequence.add_transformer(...)`). - When constructing `ETLPipeline`, pass the sequences instead of individual collections. ### Fixed - GUI: fixed slider issue on showcase page; ensured overview loads immediately; reduced layout snapping on data page; hidden dummy card; hid initial loader flash via CSS; cleaned up legacy callbacks; verified callback field necessity using `get_parameters`. - Tests: updated and fixed several tests (including utils unit tests; temporary intentionally failing test removed/fixed). ### CI/CD - Multiple pipeline updates for CI and CD, including result publication tweaks and cache adjustments. ### Misc - Minor documentation updates and code cleanups. ## 0.3.3 _Released at 12-12-2025_ ### Summary - **Breaking:** Content pages moved to a protocol/class-based system. Instead of passing content functions, the frontend now receives Page classes. This aligns page composition with the new `BaseAlgorithm`/`BaseKPI` patterns. - Parameters - Added Multi-select parameter support via `MultiEnumParameter`. - Added time-related parameters `TimeParameter` and `IntervalParameter` with frontend input components. - Added a configuration option that shows/hides the _Upload_ tab for parameter selection. Default is hidden. - Added a _ standard_ data page that uses the `tables` attribute of the data container to render simple DashTables. ### Migration to Page classes (breaking) Prior to this release, pages were typically supplied to the app as plain content functions. As of v0.3.2, pages are defined as classes that satisfy the page protocol and are passed to the frontend by type (class) rather than by function reference. The content classes need to follow the appropriate `Protocol`s. That is, implement the required fuctions with signature. These protocols are enforced by validation of the AppConfiguration. An outline of the expected functions is included below. - HomePage: - `create_content() -> html.Div` - `register_callbacks() -> None` - DataPage: - `create_content(data: BASEDATASOURCE) -> html.Div` - `register_callbacks() -> None` - ScenarioPage: - `create_content(scenario: Scenario) -> html.Div` - `register_callbacks() -> None` - ComparePage: - `create_side_by_side_content(scenario: Scenario, side: str) -> html.Div` - `create_compare_section(left: Scenario, right: Scenario) -> html.Div:` - `create_details_section(left: Scenario, right: Scenario) -> html.Div` - `register_callbacks() -> None` - OverviewPage: - `create_content(List[Scenario]) -> html.Div` - `register_callbacks() -> None` Below is a conceptual before/after to illustrate the change. **Old version (functions passed to the frontend)** ```python # example (conceptual) from algomancy_gui.configuration.appconfig import AppConfig class ExampleDataPage: def create_content(self, data) -> html.Div: ... def register_callbacks(self): ... config = AppConfig( home_page_content='standard', data_page_content=ExampleDataPage.create_content, # Callable[..., Div] or str data_page_callbacks=ExampleDataPage.register_callbacks # Callable[..., None] or str ) ``` **New version (Page classes passed to the frontend)** ```python # example (conceptual) from algomancy_gui.configuration.appconfig import AppConfig class ExampleDataPage: def create_content(self, data) -> html.Div: ... def register_callbacks(self): ... config = AppConfig( home_page_content='standard' # Protocol[HomePage] or str data_page = ExampleDataPage, # Protocol[DataPage] or str ) ``` If you maintain custom pages, convert them into classes that implement the expected page protocol (constructor + render/handlers). For a minimal reference implementation, see `algomancy_content.pages.placeholderscenariopage.PlaceholderScenarioPage`. ### New Typed Parameters (multi-select and time-related) Two new parameter families were added in `algomancy_scenario.basealgorithmparameters` and are fully supported in the GUI: - `MultiEnumParameter`: choose multiple options from a predefined list. - `TimeParameter` and `IntervalParameter`: capture a specific time (`HH:MM[:SS]`) and a time interval (start/end) respectively. #### Examples ```python from algomancy_utils.baseparameterset import ( MultiEnumParameter, TimeParameter, IntervalParameter, ) # Multi-select example select_products = MultiEnumParameter( name="products", choices=["A", "B", "C"], value=["A", "C"], # user may select multiple required=True, ) # Time of day example (24h) cutoff_time = TimeParameter( name="cutoff_time", value="14:30", required=False, ) # Interval example (start/end times) processing_window = IntervalParameter( name="processing_window", start="08:00", end="17:30", ) ``` In the frontend, these parameters render as: - Multi-select: a list with checkboxes or tags allowing multiple selections. - Time/Interval: time pickers; intervals present paired inputs for start and end values. ## 0.3.2 _Released at 16-12-2025_ ### Summary - Added Sessions to the App - Sessions can be managed in the Admin page - An empty session can be created by clicking the "New Session" button in the Admin page - An copy of a session can be created by clicking the "Copy Session" button in the Compare page - This copies only the datasets and not the scenarios - **Breaking**: When retrieving the scenario manager `get_app().server.scenario_manager` no longer works - Scenario manager is now available through the session manager and the active session ## 0.3.1 _Released at 03-12-2025_ ### Summary - **Breaking:** Revised `AlgorithmTemplate` pattern to `BaseAlgorithm` workflow, analog to `BaseDataSource` - **Breaking:** Also revised `KPITemplate` pattern to `BaseKPI` workflow - Scenario engine - Export and typing cleanups in `scenario` to support `BaseAlgorithm`/`BaseKPI` patterns. - Measurement/unit utilities updated in `scenario.unit`. - Added threshold KPIs; `ImprovementDirection.AT_LEAST`, `.AT_MOST`, and argument `threshold` - Components and pages - Updated Compare and Scenario page callbacks and KPI Card to align with the new KPI base class and thresholds. - Minor adjustments to Standard Home and Overview pages. - Examples - Replaced example KPI template with a `BaseKPI` implementation (`DelayKPI`). ### Migration to BaseAlgorithm To align the development patterns, the template pattern has been substituted for a basemodel pattern, similarly to the creation of custom `DataSource`s. Below is an example of the before and after. **Old version** ```python def batching_algorithm( data: DataSource, parameters: BatchingAlgorithmParameters, set_progress: Callable[[float], None], ) -> ScenarioResult: sleep(parameters.batch_size) set_progress(100) return ScenarioResult(data_id=data.id) batching_algorithm_template = AlgorithmTemplate( name="Batching", param_type=BatchingAlgorithmParameters, # Parameter type used to be passed main_method_template=batching_algorithm, # as well as the main method handle. ) ``` **New version** ```python class BatchingAlgorithm(BaseAlgorithm): """ From v0.3.1, create your own algorithm by deriving BaseAlgorithm """ def __init__(self, params: BatchingAlgorithmParameters): super().__init__("Batching", params) @staticmethod def initialize_parameters() -> BatchingAlgorithmParameters: """ Minimal bit of boilerplate, necessary for internal handling """ return BatchingAlgorithmParameters() def run(self, data: DataSource) -> ScenarioResult: """ Derived Algorithms now have to implement their own run() method """ sleep(self.params.batch_size) self.set_progress(100) return ScenarioResult(data_id=data.id) ``` ### Migration to BaseKPI Similarly, the KPI creation has also been moved to the basemodel pattern. **Old version** ```python default = QUANTITIES["default"] default_unit = BaseMeasurement(default["unit"], min_digits=1, max_digits=3, decimals=1) def create_error_template(): def error_rate_calculation(result: ScenarioResult) -> float: return 0.1 * (1 + 0.5 * random.random()) # placeholder return KpiTemplate( name="Error Rate", better_when=ImprovementDirection.LOWER, callback=error_rate_calculation, measurement_base=default_unit, ) ``` **New version** ```python default = QUANTITIES["default"] default_unit = BaseMeasurement(default["unit"], min_digits=1, max_digits=3, decimals=1) class ErrorKPI(BaseKPI): def __init__(self): """ Basic configurations are now made by passing them to the base object """ super().__init__( name = "Error Rate", better_when = ImprovementDirection.LOWER, base_measurement = default_unit, ) def compute(self, result: ScenarioResult) -> float: """ The user defines the compute function, as before""" return 0.1 * (1 + 0.5 * random.random()) # placeholder ``` ### Threshold KPIs A threshold KPI is initialized with a `threshold` value and appropriate `ImprovementDirection`, in addition to the usual arguments. It is considered to be a 'success' if the value of the kpi exceeds (or does not exceed, in the case of `AT_MOST`) the threshold value. The `.pretty()` function will format a threshold as either a checkmark or a cross, depending on the value relative to the threshold. An example is included below ```python # noinspection PyUnresolvedReferences class DelayKPI(BaseKPI): def __init__(self): super().__init__( name="Average Delay", better_when=ImprovementDirection.AT_MOST, base_measurement=BaseMeasurement(QUANTITIES["time"]["s"], min_digits=1, max_digits=3, decimals=1), threshold=1200, ) ``` ## 0.2.15 _Released at 28-11-2025_ ### Summary - `DataSource` and `ScenarioResult` are now derived from the abstract `BaseDataSource` and `BaseScenarioResult`, which are used for internal typing and should be extended for custom containers rather than DataSource and ScenarioResult. This change **is** backwards-compatible, and should not break existing projects ## 0.2.14 _Released at 21-11-2025_ ### Summary - Styling and UX improvements across default components and pages. - Restructured CSS and refined component defaults; added/updated animated spinners and modal behavior. - Data engine: improvements to extractors. - Logging and reliability fixes. ### Details - UI/UX - Restyled default components and performed multiple styling updates. - Restructured CSS and applied small CSS tweaks to polish visuals. - Disabled page background when modals are open for better focus. - Wrapped Data page in a spinner and added further updates to animated spinners with better customizability. - Data Engine - Multiple extractor-related improvements and refactors. - Examples - Updated tests and example implementation for alignment with UI and engine changes. ### Bug fixes - Fixed a scale assertion bug in the measurement formatting logic. - Replaced erroneous `log_exception` usage with the correct `log_traceback` in logging. ### Multi extractor update > **This is a breaking change** > `InputFileConfiguration` is now an abstract class; its uses should be replaced by `SingelInputFileConfiguration`, which is a drop-in replacement. Its counterpart, the `MultiInputFileConfiguration` is used by the MultiExtractor. Its use should be clear from the example below. ```python class StedenSchema(Schema): COUNTRY = "Country" CITY = "City" @property def datatypes(self) -> Dict[str, DataType]: return { StedenSchema.COUNTRY: DataType.STRING, StedenSchema.CITY: DataType.STRING, } class KlantenSchema(Schema): ID = 'ID' Name = "Naam" @property def datatypes(self) -> Dict[str, DataType]: return { KlantenSchema.ID: DataType.INTEGER, KlantenSchema.Name: DataType.STRING, } multisheet_config = MultiInputFileConfiguration( extension=FileExtension.XLSX, file_name="multisheet", file_schemas={ "Steden": StedenSchema(), "Klanten": KlantenSchema(), } ) ``` ```python class ExampleETLFactory(de.ETLFactory): def __init__(self, configs, logger=None): super().__init__(configs, logger) def create_extractors( self, files: Dict[str, F], # name to path format ) -> Dict[str, de.Extractor]: ... multisheet_extractor = de.XLSXMultiExtractor( # -- this is new file=cast(de.XLSXFile, files[multisheet]), schemas=self.get_schemas(multisheet), logger=self.logger, ), ``` **Note**: the resulting DataFrames show up with keys `multisheet.Steden` and `multisheet.Klanten` in the ETL internal dictionary. ## 0.2.13 _Released at 06-11-2025_ ### Summary - Autocreate now supports algorithms with parameters. - Delete button is now disabled while a scenario is queued or running. - CQM-loader is now a configurable option ## 0.2.12 _Released at 06-11-2025_ ### Summary - Bug fixes ### Bug fixes - Fixed an issue where `unit.py` would cause crashes in python pre-3.14. ## 0.2.11 _Released at 05-11-2025_ ### Summary - Several minor bug fixes and documentation updates. - implemented `__eq__` for `DataSource`, checking the internal uuid. ### Bug fixes - Removed redundant message from internal logging - `Scenario.cancel()` and `Scenario.refresh()` now act properly when no logger is passed - Secret key is now set on `BasicAuth` - data path is no longer validated for configurations without persistent state. ## 0.2.10 _Released at 05-11-2025_ ### Summary - Introduced a unified KPI measurement framework with `BaseMeasurement`, replacing `UOM`. This allows for automatic unit conversion and consistent representation in the UI. - Renamed the remaining "performance" page references to "compare" across modules for consistency. - Improved logging: stack traces are now routed to the logger; startup log severity adjusted. - Documentation: README updates and minor cleanup. - Tests: Added pytest module `tests/test_unit_measurement_examples.py` covering Measurement examples from `unit.py`. - New feature: automatic creation of scenarios is now supported. - New feature: Added `refresh` functionality to the `Scenario` component. - **[Breaking]** New feature: `AppConfiguration` class now manages and validates the launch configuration ### AppConfiguration >**This is a breaking change.** Added `AppConfiguration` class to manage and validate the launch configuration. Conceptually, this class is a wrapper that provides a consistent interface for the configuration fields and their validation. This class is now used to manage the launch sequence. In particular, DashLauncher.build(...) now takes an `AppConfiguration` object as an argument, instead of a dictionary. Your main method must be migrated to use the new class. An example is shown below: ```python # main method: preferred version from algomancy_gui.gui_launcher import GuiLauncher from algomancy_gui.configuration.appconfig import AppConfig def main(): app_cfg = AppConfig( data_path="data", has_persistent_state=True, # ... ) app = GuiLauncher.build(app_cfg) GuiLauncher.run(app=app, host=app_cfg.host, port=app_cfg.port) ``` For migration, the `AppConfiguration.from_dict(...)` method can be used to create an `AppConfiguration` object from a dictionary. Note that this is not advised, as this will not allow for IDE support. ```python # main method: migration alternative from algomancy_gui.gui_launcher import GuiLauncher from algomancy_gui.configuration.appconfig import AppConfig def main(): configuration = { "data_path": "data", "has_persistent_state": True, # ... } app_cfg = AppConfig.from_dict(configuration) app = GuiLauncher.build(app_cfg) GuiLauncher.run(app=app, host=app_cfg.host, port=app_cfg.port) ``` ### Autocreate Added automatic creation of scenarios. This will cause any creation of a `DataSource` (or derived) to spawn a `Scenario` with the same name (suffixed with `[auto]`). The algorithm template must be specified in the configuration dictionary. To configure, add the below to the configuration. ```python # framework configuration app_cfg = AppConfiguration( # ..., autocreate= True, # set to True for autocreate mode default_algo= "As is", # select the name of an algorithm template to use for autocreation # ..., ) ``` - Added `refresh` functionality to the `Scenario` component. This will cause the `Scenario` to reset its status and discard the `ScenarioResult`. To refresh a scenario, the `Scenario.refresh()` method is called from the Scenario management screen. The process scenario button is now context-aware. At a later time, this button will also support a cancel operation. ### Interface changes - **[Breaking]** Replaced `UOM` with `BaseMeasurement` in KPI-related APIs and templates. Update custom KPI code to construct and return `Measurement`/`BaseMeasurement` instead of the old types. - **[Breaking]** Removed obsolete `KpiType` enum. ### Measurement framework - New `BaseUnit`, `Quantity`, `BaseMeasurement`, and `Measurement` types in `algomancy\\scenarioengine\\unit.py` provide consistent formatting, auto-scaling, and unit chaining. - KPI templates should be migrated to `BaseMeasurement`/`Measurement`. See `algomancy\\scenarioengine\\keyperformanceindicator.py` for how KPIs surface measurements. - Extensive examples are available in `algomancy\\scenarioengine\\unit.py\\example_usage()`. - KPI Template creation should now follow the following pattern: ```python import random from src.algomancy import ImprovementDirection, KpiTemplate, ScenarioResult from scenario import QUANTITIES, BaseMeasurement def throughput_calculation(result: ScenarioResult) -> float: return 100 * (1 + 0.5 * random.random()) # placeholder mass = QUANTITIES["mass"] mass_kg = BaseMeasurement( mass["kg"], # the default unit is kg; the associated quantity is mass min_digits=1, # the minimum number of nonzero digits before the decimal point max_digits=3, # the maximum number of nonzero digits before the decimal point decimals=2, # the number of decimal places to display smallest_unit="g", # the smallest unit to display - overrides min_digits largest_unit="ton", # the largest unit to display - overrides max_digits ) template = KpiTemplate( name="Throughput", # type=KpiType.NUMERIC, # KpiType has become redundant, formatting is now handled by Measurement better_when=ImprovementDirection.HIGHER, callback=throughput_calculation, measurement_base=mass_kg, # Pass the measurement to use as a basis for the kpi value ) ``` ### Compare page naming cleanup All remaining references to the `performance` page were renamed to `compare` for consistency (imports, component IDs, modules). If you import internal modules, update your imports accordingly. > Note: css classes are also affected, so you may need to update your style.css file. ### Logging - Exceptions now include full stack traces in the central logger. - The startup message severity has been adjusted for better signal in production logs. ### Docs - README refreshed to reflect the new measurement framework and naming. ## 0.2.9 _Released at 29-10-2025_ ### Summary **New features** - Added internal `ContentRegistry` class, which now manages and distributes the content functions. **Bug fixes** - Fixed issue where `url` callbacks would cause conflicts. - Fixed a bug where the `url` callbacks had multiple listeners, which sometimes caused synchronization issues. ### ContentRegistry The `ContentRegistry` class is now used to manage and distribute the content functions. These responsibilities were previously handled by the `Launcher` class, which has been refactored to only manage the launch sequence. This is a purely internal change, and should not affect the user. ## 0.2.8 _Released at 29-10-2025_ ### Summary **New features** - Added a Waitress WSGI wrapper for production servers **Interface changes** - **[Breaking]** Added additional CLI arguments `threads` and `connections` to the startup sequence - Simplified AlgorithmParameter syntax ### Waitress WSGI wrapper The Waitress WSGI wrapper is now used to run the application if `debug` is set to `False`. This should relieve issues experienced with the Flask development server, such as the lack of thread safety that could be observed when accessing the app from multiple sources simultaneously. The wrapper is configured through the CLI arguments; in particular, `threads` and `connections` have been added. They control the number of threads and the maximum number of simultaneous connections, respectively. `threads` defaults to 8, and `connections` defaults to 100. > **This is a breaking change.** `threads` and `connections` are now required arguments of `Launcher.run(...)` ### AlgorithmParameter syntax The `__getitem__` method of the AlgorithmParameters class has been implemented. Instead of accessing a parameter in `key` as `algorithm_parameters._parameters[key].value`, it can now be accessed as `algorithm_parameters[key]`. Note that this is optional, legacy syntax will still work. ## 0.2.7 _Released at 27-10-2025_ ### Summary **New features** - Opened up compare page styling through style.css - The order of the main sections (side-by-side, compare, KPI cards, and details) are now configurable through the configuration dictionary **Interface changes** - **[Breaking]** The side-by-side section of the compare page now passes `"left"` and `"right"` to the content function. **Bug fixes** - MultiExtractor no longer uses the (previously renamed) `extraction_message` and `extraction_success_message` functions ### Compare page configuration The order of the main sections (side-by-side, compare, KPI cards, and details) are now configurable through the configuration dictionary. To configure, specify the list of component keys in the order you want them to appear in the compare page, and add it to the configuration dictionary with key `performance_ordered_list_components`. The expected keys are `side-by-side`, `kpis`, `compare`, and `details`. An example is shown below: ```python # framework configuration configuration = { # ... "compare_ordered_list_components": [ 'side-by-side', 'kpis', 'compare', 'details'] # ... } ``` ### Side-by-side section The side-by-side section of the compare page now passes `"left"` and `"right"` to the content function, which allows the scenario specific section to contain their own responsive elements, such as dropdown menus. The content function signature has changed to include the side argument. > **This is a breaking change.** Content functions that expect only one argument will need to be updated. Alternatively, `**kwargs` can be added to the function signature to allow for `side` to be passed and be robust for future expansion. See here for more details. An example is shown below: **OLD** ```python @staticmethod def create_side_view(s: Scenario) -> html.Div: """ User defined function to create the side view of the compare page.""" return html.Div(...) ``` **NEW** ```python @staticmethod def create_side_view(s: Scenario, side: str) -> html.Div: """ User defined function to create the side view of the compare page.""" return html.Div(...) ``` **ALTERNATIVE** ```python @staticmethod def create_side_view(s: Scenario, **kwargs) -> html.Div: """ User defined function to create the side view of the compare page.""" return html.Div(...) ```