diff --git a/src/getml_io/getml/scores.py b/src/getml_io/getml/scores.py new file mode 100644 index 0000000..20c8df1 --- /dev/null +++ b/src/getml_io/getml/scores.py @@ -0,0 +1,33 @@ +from collections.abc import Sequence +from datetime import datetime +from typing import Annotated, Literal + +from pydantic import BaseModel, Field + + +class _Score(BaseModel): + date_time: datetime + set_used: str + target: str + + +class ClassificationScore(_Score): + accuracy: float + auc: float + cross_entropy: float + type: Literal["classification"] = "classification" + + +class RegressionScore(_Score): + mae: float + rmse: float + rsquared: float + type: Literal["regression"] = "regression" + + +Score = Annotated[ + ClassificationScore | RegressionScore, + Field(discriminator="type"), +] + +Scores = Sequence[ClassificationScore] | Sequence[RegressionScore] diff --git a/src/getml_io/metadata/pipeline_information.py b/src/getml_io/metadata/pipeline_information.py index 58913ac..91b3b83 100644 --- a/src/getml_io/metadata/pipeline_information.py +++ b/src/getml_io/metadata/pipeline_information.py @@ -14,6 +14,7 @@ from getml_io.getml.features import Features from getml_io.getml.predictors import FeatureSelector, Predictor from getml_io.getml.preprocessors import Preprocessor +from getml_io.getml.scores import Scores from getml_io.metadata.data_model_information import DataModelInformation from getml_io.metadata.dataframe_information import DataFrameInformationByName from getml_io.metadata.placeholder_information import PlaceholderInformation @@ -44,7 +45,7 @@ class PipelineInformation(BaseModel): targets: Sequence[str] data_model: DataModelInformation features: Features - # scores # TODO @urfoex: #18 + scores: Scores # columns # TODO @urfoex: #50 # metadata # TODO @urfoex: #51 # tables # TODO @urfoex: #52 diff --git a/src/getml_io/serialize/exception.py b/src/getml_io/serialize/exception.py index 928e51e..b64904b 100644 --- a/src/getml_io/serialize/exception.py +++ b/src/getml_io/serialize/exception.py @@ -1,5 +1,7 @@ from pathlib import Path +from getml.pipeline.score import Score as GetMLScore + from getml_io.getml.roles import Role from getml_io.metadata.dataframe_information import ( ROLE_TO_COLUMN_STATISTICS_TYPE_MAPPING, @@ -91,3 +93,19 @@ def __init__( f"Supported are: {list(ROLE_TO_COLUMN_STATISTICS_TYPE_MAPPING.keys())}." ) super().__init__(message) + + +class WrongPipelineScoreTypeError(GetMLIOError): + """Exception raised when the type of a score does not match the expected type.""" + + def __init__( + self, + expected_type: type[GetMLScore], + received_type: type[GetMLScore], + ) -> None: + """Initialize the exception with a custom message.""" + message = ( + f"Expected score type {expected_type.__name__}, " + f"but received {received_type.__name__}." + ) + super().__init__(message) diff --git a/src/getml_io/serialize/pipeline.py b/src/getml_io/serialize/pipeline.py index b370e0e..87c4d1f 100644 --- a/src/getml_io/serialize/pipeline.py +++ b/src/getml_io/serialize/pipeline.py @@ -13,6 +13,10 @@ ) from getml.pipeline import Features as GetMLFeatures from getml.pipeline import Pipeline +from getml.pipeline import Scores as GetMLScores +from getml.pipeline.score import ClassificationScore as GetMLClassificationScore +from getml.pipeline.score import RegressionScore as GetMLRegressionScore +from getml.pipeline.score import Score as GetMLScore from numpy.typing import NDArray from getml_io.getml.feature_learning import ( @@ -43,6 +47,7 @@ Substring, TextFieldSplitter, ) +from getml_io.getml.scores import ClassificationScore, RegressionScore, Scores from getml_io.metadata.dataframe_information import DataFrameInformationByName from getml_io.metadata.pipeline_information import ( LossFunction, @@ -51,6 +56,7 @@ from getml_io.serialize.data_model import serialize_data_model from getml_io.serialize.dataframe_information import derive_instances_with_relative_path from getml_io.serialize.dataframe_or_view import serialize_dataframe_or_view +from getml_io.serialize.exception import WrongPipelineScoreTypeError from getml_io.serialize.pipeline_information import serialize_pipeline_information from getml_io.serialize.placeholder import serialize_placeholder from getml_io.utils.convert import ( @@ -123,7 +129,7 @@ def serialize_pipeline( targets=pipeline.targets, data_model=serialize_data_model(pipeline.data_model), features=serialize_features(pipeline.features), - # scores # TODO @urfoex: #18 + scores=serialize_scores(pipeline.scores), # columns # TODO @urfoex: #50 # metadata # TODO @urfoex: #51 # tables # TODO @urfoex: #52 @@ -331,3 +337,64 @@ def serialize_features(features: GetMLFeatures) -> Features: ) for feature in features } + + +def serialize_scores(scores: GetMLScores) -> Scores: + """Serialize getML Scores into a Scores object. + + Args: + scores: The getML Scores to serialize. + + Returns: + Scores: The serialized Scores information. + + """ + return ( + _serialize_classification_scores(list(scores)) + if scores.is_classification + else _serialize_regression_scores(list(scores)) + ) + + +def _serialize_classification_scores( + scores: list[GetMLScore], +) -> list[ClassificationScore]: + classification_scores: list[ClassificationScore] = [] + for score in scores: + if not isinstance(score, GetMLClassificationScore): + raise WrongPipelineScoreTypeError( + GetMLClassificationScore, + type(score), + ) + classification_scores.append( + ClassificationScore( + date_time=score.date_time, + set_used=score.set_used, + target=score.target, + accuracy=score.accuracy, + auc=score.auc, + cross_entropy=score.cross_entropy, + ), + ) + return classification_scores + + +def _serialize_regression_scores(scores: list[GetMLScore]) -> list[RegressionScore]: + regression_scores: list[RegressionScore] = [] + for score in scores: + if not isinstance(score, GetMLRegressionScore): + raise WrongPipelineScoreTypeError( + GetMLRegressionScore, + type(score), + ) + regression_scores.append( + RegressionScore( + date_time=score.date_time, + set_used=score.set_used, + target=score.target, + mae=score.mae, + rmse=score.rmse, + rsquared=score.rsquared, + ), + ) + return regression_scores diff --git a/tests/integration/assertions.py b/tests/integration/assertions.py index feadde7..3488862 100644 --- a/tests/integration/assertions.py +++ b/tests/integration/assertions.py @@ -2,6 +2,7 @@ from pathlib import Path from getml_io.getml.features import Features +from getml_io.getml.scores import Scores from getml_io.metadata.container_information import ContainerInformation from getml_io.metadata.dataframe_information import ( ColumnProfile, @@ -213,6 +214,10 @@ def assert_pipeline_information( pipeline_information.features, expected_pipeline_information.features, ) + assert_scores( + pipeline_information.scores, + expected_pipeline_information.scores, + ) def assert_features( @@ -229,3 +234,23 @@ def assert_features( assert feature.sql is not None assert feature.importance is not None assert feature.correlation is not None + + +def assert_scores( + scores: Scores, + expected_scores: Scores, +) -> None: + assert len(scores) == len(expected_scores) + for score, expected_score in zip(scores, expected_scores, strict=True): + assert score.type == expected_score.type + assert score.target == expected_score.target + assert score.date_time is not None + assert score.set_used == expected_score.set_used + if score.type == "classification": + assert score.accuracy is not None + assert score.auc is not None + assert score.cross_entropy is not None + elif score.type == "regression": + assert score.mae is not None + assert score.rmse is not None + assert score.rsquared is not None diff --git a/tests/integration/data/loans/expected.pipeline.json b/tests/integration/data/loans/expected.pipeline.json index 28ef7fb..d9b10f8 100644 --- a/tests/integration/data/loans/expected.pipeline.json +++ b/tests/integration/data/loans/expected.pipeline.json @@ -631,11 +631,11 @@ "feature_learners": [ { "aggregation": [ - "SUM", + "COUNT", "AVG", + "SUM", "MAX", - "MIN", - "COUNT" + "MIN" ], "allow_sets": true, "delta_t": 0.0, @@ -649,19 +649,19 @@ "num_threads": 0, "propositionalization": { "aggregation": [ - "SUM", - "AVG", "MEDIAN", - "TREND", - "COUNT MINUS COUNT DISTINCT", - "MODE", "STDDEV", - "MAX", - "MIN", + "COUNT", + "AVG", + "TREND", "COUNT DISTINCT", + "SUM", "FIRST", + "MODE", "LAST", - "COUNT" + "COUNT MINUS COUNT DISTINCT", + "MAX", + "MIN" ], "delta_t": 0.0, "loss_function": "CrossEntropyLoss", @@ -1062,5 +1062,25 @@ "correlation": 0.06660193285904141, "sql": "" } - } + }, + "scores": [ + { + "date_time": "2025-08-19T22:33:06", + "set_used": "train", + "target": "default", + "accuracy": 0.9825708061002179, + "auc": 0.9952295229522934, + "cross_entropy": 0.08200917844672241, + "type": "classification" + }, + { + "date_time": "2025-08-19T22:33:07", + "set_used": "test", + "target": "default", + "accuracy": 0.9551569506726457, + "auc": 0.8903818953323912, + "cross_entropy": 0.1751293856880503, + "type": "classification" + } + ] } \ No newline at end of file diff --git a/tests/integration/data/numerical/expected.pipeline.json b/tests/integration/data/numerical/expected.pipeline.json index cf3fe2b..e13289d 100644 --- a/tests/integration/data/numerical/expected.pipeline.json +++ b/tests/integration/data/numerical/expected.pipeline.json @@ -570,19 +570,19 @@ "num_threads": 0, "propositionalization": { "aggregation": [ - "SUM", - "AVG", "MEDIAN", - "TREND", - "COUNT MINUS COUNT DISTINCT", - "MODE", "STDDEV", - "MAX", - "MIN", + "COUNT", + "AVG", + "TREND", "COUNT DISTINCT", + "SUM", "FIRST", + "MODE", "LAST", - "COUNT" + "COUNT MINUS COUNT DISTINCT", + "MAX", + "MIN" ], "delta_t": 0.0, "loss_function": "SquareLoss", @@ -817,5 +817,25 @@ "correlation": -0.09593210355580734, "sql": "" } - } + }, + "scores": [ + { + "date_time": "2025-08-19T22:33:16", + "set_used": "train", + "target": "targets", + "mae": 0.16209001296605818, + "rmse": 0.25502448762753166, + "rsquared": 0.999961056217837, + "type": "regression" + }, + { + "date_time": "2025-08-19T22:33:18", + "set_used": "test", + "target": "targets", + "mae": 0.47556934465061534, + "rmse": 0.6929843656362203, + "rsquared": 0.9996960826789243, + "type": "regression" + } + ] } \ No newline at end of file diff --git a/tests/integration/data/robot/expected.pipeline.json b/tests/integration/data/robot/expected.pipeline.json index e2aa7df..84ad49d 100644 --- a/tests/integration/data/robot/expected.pipeline.json +++ b/tests/integration/data/robot/expected.pipeline.json @@ -7020,19 +7020,19 @@ "num_threads": 0, "propositionalization": { "aggregation": [ - "SUM", - "AVG", "MEDIAN", - "TREND", - "COUNT MINUS COUNT DISTINCT", - "MODE", "STDDEV", - "MAX", - "MIN", + "COUNT", + "AVG", + "TREND", "COUNT DISTINCT", + "SUM", "FIRST", + "MODE", "LAST", - "COUNT" + "COUNT MINUS COUNT DISTINCT", + "MAX", + "MIN" ], "delta_t": 0.0, "loss_function": "SquareLoss", @@ -8351,5 +8351,88 @@ "correlation": -0.0760457382486313, "sql": "" } - } + }, + "scores": [ + { + "date_time": "2025-08-19T22:33:26", + "set_used": "train", + "target": "f_x", + "mae": 0.010792690446641749, + "rmse": 0.014323699491113766, + "rsquared": 0.9944956319750042, + "type": "regression" + }, + { + "date_time": "2025-08-19T22:33:26", + "set_used": "train", + "target": "f_y", + "mae": 0.008576129904852969, + "rmse": 0.010966870213613632, + "rsquared": 0.9980571216378779, + "type": "regression" + }, + { + "date_time": "2025-08-19T22:33:26", + "set_used": "train", + "target": "f_z", + "mae": 0.007760050167507584, + "rmse": 0.009929547385971533, + "rsquared": 0.9941936929030325, + "type": "regression" + }, + { + "date_time": "2025-08-19T22:33:35", + "set_used": "test", + "target": "f_x", + "mae": 0.19983083674112964, + "rmse": 0.23230018960718285, + "rsquared": 0.017883551035554685, + "type": "regression" + }, + { + "date_time": "2025-08-19T22:33:35", + "set_used": "test", + "target": "f_y", + "mae": 0.17922503092447914, + "rmse": 0.23293257590565702, + "rsquared": 0.06071981485675976, + "type": "regression" + }, + { + "date_time": "2025-08-19T22:33:35", + "set_used": "test", + "target": "f_z", + "mae": 0.1051623312123616, + "rmse": 0.12464067763768448, + "rsquared": 0.10725879337436184, + "type": "regression" + }, + { + "date_time": "2025-08-19T22:33:36", + "set_used": "validation", + "target": "f_x", + "mae": 0.37266234512329094, + "rmse": 0.4610907233806685, + "rsquared": 0.15291051179666182, + "type": "regression" + }, + { + "date_time": "2025-08-19T22:33:36", + "set_used": "validation", + "target": "f_y", + "mae": 0.2962504494476319, + "rmse": 0.3366322436782126, + "rsquared": 0.00013277252581157456, + "type": "regression" + }, + { + "date_time": "2025-08-19T22:33:36", + "set_used": "validation", + "target": "f_z", + "mae": 0.2142715495045979, + "rmse": 0.24715488683475131, + "rsquared": 0.022980212283867575, + "type": "regression" + } + ] } \ No newline at end of file diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index d442c51..860982e 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -1,6 +1,7 @@ import copy import re from collections.abc import Mapping, Sequence +from datetime import datetime, timezone from pathlib import Path from typing import Any, Protocol @@ -18,7 +19,10 @@ from getml.feature_learning.loss_functions import CROSSENTROPYLOSS from getml.pipeline import Features as GetMLFeatures from getml.pipeline import Pipeline +from getml.pipeline import Scores as GetMLScores from getml.pipeline.feature import Feature as GetMLFeature +from getml.pipeline.score import ClassificationScore as GetMLClassificationScore +from getml.pipeline.score import RegressionScore as GetMLRegressionScore from numpy.typing import NDArray from getml_io.getml.feature_learning import FastProp @@ -29,6 +33,7 @@ from getml_io.getml.project_information import ProjectInformation from getml_io.getml.relationships import Relationship from getml_io.getml.roles import Role, Roles +from getml_io.getml.scores import ClassificationScore, Scores from getml_io.metadata.container_information import ContainerInformation from getml_io.metadata.data_model_information import DataModelInformation from getml_io.metadata.dataframe_information import ( @@ -405,6 +410,7 @@ def pipeline_information_empty( targets=[], data_model=data_model_information_empty, features={}, + scores=[], ) @@ -430,12 +436,49 @@ def mock_features(mocker: pytest_mock.MockerFixture) -> GetMLFeatures: return features +@pytest.fixture +def mock_scores_regression(mocker: pytest_mock.MockerFixture) -> GetMLScores: + scores = mocker.MagicMock(GetMLScores) + regression_score = mocker.MagicMock(GetMLRegressionScore) + regression_score.date_time = datetime(2023, 1, 1, 12, 0, 0, tzinfo=timezone.utc) + regression_score.set_used = "test_set" + regression_score.target = "test_target" + regression_score.mae = 0.1 + regression_score.rmse = 0.2 + regression_score.rsquared = 0.8 + scores.__iter__.return_value = iter([ # pyright: ignore [reportAny] + regression_score, + ]) + scores.is_classification = False + scores.is_regression = True + return scores + + +@pytest.fixture +def mock_scores_classification(mocker: pytest_mock.MockerFixture) -> GetMLScores: + scores = mocker.MagicMock(GetMLScores) + classification_score = mocker.MagicMock(GetMLClassificationScore) + classification_score.date_time = datetime(2023, 1, 1, 12, 0, 0, tzinfo=timezone.utc) + classification_score.set_used = "test_set" + classification_score.target = "test_target" + classification_score.accuracy = 0.1 + classification_score.auc = 0.2 + classification_score.cross_entropy = 0.8 + scores.__iter__.return_value = iter([ # pyright: ignore [reportAny] + classification_score, + ]) + scores.is_classification = True + scores.is_regression = False + return scores + + @pytest.fixture def mock_pipeline( mocker: pytest_mock.MockerFixture, ndarray: NDArray[np.float64], mock_dataframe: DataFrame, mock_features: GetMLFeatures, + mock_scores_regression: GetMLScores, ) -> Pipeline: pipeline = mocker.Mock() pipeline.id = "mock_pipeline_id" @@ -471,6 +514,7 @@ def pipeline_transform(_: DataFrame | View | Subset, *, df_name: str) -> DataFra pipeline.tags = ["test_tag"] pipeline.targets = ["test_target"] pipeline.features = mock_features + pipeline.scores = mock_scores_regression return pipeline @@ -623,6 +667,20 @@ def features() -> Features: } +@pytest.fixture +def scores() -> Scores: + return [ + ClassificationScore( + date_time=datetime(2023, 1, 1, 12, 0, 0, tzinfo=timezone.utc), + set_used="test_set", + target="test_target", + accuracy=0.9, + auc=0.95, + cross_entropy=0.05, + ), + ] + + @pytest.fixture def pipeline_information( # noqa: PLR0913 dataframe_information_test: DataFrameInformation, @@ -636,6 +694,7 @@ def pipeline_information( # noqa: PLR0913 predictions_path: Path, feature_sets_path: Path, features: Features, + scores: Scores, ) -> PipelineInformation: return PipelineInformation( id="pipeline_id", @@ -680,6 +739,7 @@ def pipeline_information( # noqa: PLR0913 targets=["test_target"], data_model=data_model_information, features=features, + scores=scores, ) diff --git a/tests/unit/metadata/test_pipeline_information.py b/tests/unit/metadata/test_pipeline_information.py index e7afdd0..9436e29 100644 --- a/tests/unit/metadata/test_pipeline_information.py +++ b/tests/unit/metadata/test_pipeline_information.py @@ -1,3 +1,4 @@ +from datetime import datetime, timezone from pathlib import Path import pytest @@ -58,6 +59,7 @@ def _get_expected_serialized_empty_pipeline_information() -> PipelineInformation "tags": [], "targets": [], "features": {}, + "scores": [], } @@ -344,4 +346,15 @@ def _get_expected_serialized_pipeline_information() -> PipelineInformationType: "target": "test_target", }, }, + "scores": [ + { + "accuracy": 0.9, + "auc": 0.95, + "cross_entropy": 0.05, + "date_time": datetime(2023, 1, 1, 12, 0, tzinfo=timezone.utc), + "set_used": "test_set", + "target": "test_target", + "type": "classification", + }, + ], } diff --git a/tests/unit/serialize/test_pipeline.py b/tests/unit/serialize/test_pipeline.py index a6ada09..ae0cb55 100644 --- a/tests/unit/serialize/test_pipeline.py +++ b/tests/unit/serialize/test_pipeline.py @@ -1,3 +1,4 @@ +from datetime import datetime, timezone from pathlib import Path import pytest @@ -7,6 +8,7 @@ from getml.data import Container, DataFrame from getml.pipeline import Features as GetMLFeatures from getml.pipeline import Pipeline +from getml.pipeline import Scores as GetMLScores from getml_io.getml.feature_learning import ( Fastboost, @@ -35,7 +37,10 @@ Substring, TextFieldSplitter, ) +from getml_io.getml.scores import ClassificationScore, RegressionScore from getml_io.metadata.dataframe_information import DataFrameInformationByName +from getml_io.metadata.pipeline_information import LossFunction +from getml_io.serialize.exception import WrongPipelineScoreTypeError from getml_io.serialize.pipeline import ( serialize_feature_learner, serialize_feature_sets, @@ -44,6 +49,7 @@ serialize_predictions, serialize_predictor, serialize_preprocessor, + serialize_scores, ) from tests.unit.conftest import MockDuckDBExecuteFactory @@ -104,6 +110,40 @@ def test_serialize_pipeline( # noqa: PLR0913 pipeline_path / "predictions", ) + assert len(pipeline_information.feature_learners) == 1 + assert pipeline_information.feature_learners[0].type == "fast_prop" + + assert len(pipeline_information.feature_selectors) == 1 + assert pipeline_information.feature_selectors[0].type == "linear_regression" + + assert pipeline_information.include_categorical is False + assert pipeline_information.is_classification is False + assert pipeline_information.is_regression is True + + assert pipeline_information.loss_function == LossFunction.CROSS_ENTROPY_LOSS + assert len(pipeline_information.peripheral) == 1 + assert pipeline_information.peripheral[0].name == "placeholder_peripheral" + + assert len(pipeline_information.predictors) == 1 + assert pipeline_information.predictors[0].type == "linear_regression" + + assert len(pipeline_information.preprocessors) == 1 + assert pipeline_information.preprocessors[0].type == "category_trimmer" + + assert pipeline_information.share_selected_features == 0.0 + assert pipeline_information.tags == ["test_tag"] + assert pipeline_information.targets == ["test_target"] + + assert pipeline_information.data_model is not None + assert pipeline_information.data_model.population.name == "placeholder_population" + assert len(pipeline_information.data_model.peripheral) == 0 + + assert len(pipeline_information.features) == 1 + assert pipeline_information.features["test_feature"].name == "test_feature" + + assert len(pipeline_information.scores) == 1 + assert pipeline_information.scores[0] is not None + @pytest.mark.unit def test_serialize_pipeline_with_empty_outputs( @@ -331,3 +371,69 @@ def test_serialize_features(mock_features: GetMLFeatures) -> None: feature = features["test_feature"] assert feature.name == "test_feature" assert feature.index == 0 + + +@pytest.mark.unit +def test_serialize_scores_regression(mock_scores_regression: GetMLScores) -> None: + # Given + # When + scores = serialize_scores(mock_scores_regression) + + # Then + assert len(scores) == 1 + score = scores[0] + assert isinstance(score, RegressionScore) + assert score.type == "regression" + assert score.date_time == datetime(2023, 1, 1, 12, 0, 0, tzinfo=timezone.utc) + assert score.set_used == "test_set" + assert score.target == "test_target" + assert score.mae == 0.1 # noqa: PLR2004 + assert score.rmse == 0.2 # noqa: PLR2004 + assert score.rsquared == 0.8 # noqa: PLR2004 + + +@pytest.mark.unit +def test_serialize_scores_regression_wrong_type( + mock_scores_classification: GetMLScores, + mock_scores_regression: GetMLScores, +) -> None: + # Given + mock_scores_regression.__iter__ = mock_scores_classification.__iter__ + + # When / Then + with pytest.raises(WrongPipelineScoreTypeError): + _ = serialize_scores(mock_scores_regression) + + +@pytest.mark.unit +def test_serialize_scores_classification( + mock_scores_classification: GetMLScores, +) -> None: + # Given + # When + scores = serialize_scores(mock_scores_classification) + + # Then + assert len(scores) == 1 + score = scores[0] + assert isinstance(score, ClassificationScore) + assert score.type == "classification" + assert score.date_time == datetime(2023, 1, 1, 12, 0, 0, tzinfo=timezone.utc) + assert score.set_used == "test_set" + assert score.target == "test_target" + assert score.accuracy == 0.1 # noqa: PLR2004 + assert score.auc == 0.2 # noqa: PLR2004 + assert score.cross_entropy == 0.8 # noqa: PLR2004 + + +@pytest.mark.unit +def test_serialize_scores_classification_wrong_type( + mock_scores_classification: GetMLScores, + mock_scores_regression: GetMLScores, +) -> None: + # Given + mock_scores_classification.__iter__ = mock_scores_regression.__iter__ + + # When / Then + with pytest.raises(WrongPipelineScoreTypeError): + _ = serialize_scores(mock_scores_classification) diff --git a/tests/unit/serialize/test_pipeline_information.py b/tests/unit/serialize/test_pipeline_information.py index 1e40273..685cfce 100644 --- a/tests/unit/serialize/test_pipeline_information.py +++ b/tests/unit/serialize/test_pipeline_information.py @@ -76,6 +76,7 @@ def _get_expected_pipeline_information() -> PipelineInformationType: "tags": [], "targets": [], "features": {}, + "scores": [], } diff --git a/tests/unit/types.py b/tests/unit/types.py index d2f9001..9d21f1e 100644 --- a/tests/unit/types.py +++ b/tests/unit/types.py @@ -1,6 +1,7 @@ from __future__ import annotations from collections.abc import Mapping, Sequence +from datetime import datetime from pathlib import Path from getml_io.getml.relationships import Relationship @@ -41,6 +42,8 @@ PredictorType = Mapping[str, float | str] FeatureType = Mapping[str, str | float | int] FeaturesType = Mapping[str, FeatureType] +ScoreType = Mapping[str, str | float | datetime] +ScoresType = Sequence[ScoreType] PipelineInformationType = Mapping[ str, str @@ -53,5 +56,6 @@ | LossFunction | float | Sequence[str] - | FeaturesType, + | FeaturesType + | ScoresType, ]