eb-contracts 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- eb_contracts/__init__.py +24 -0
- eb_contracts/api/__init__.py +27 -0
- eb_contracts/api/migrate_forecast.py +122 -0
- eb_contracts/api/validate.py +81 -0
- eb_contracts/contracts/__init__.py +9 -0
- eb_contracts/contracts/_internal/__init__.py +8 -0
- eb_contracts/contracts/_internal/runtime.py +48 -0
- eb_contracts/contracts/_internal/typing.py +37 -0
- eb_contracts/contracts/context/__init__.py +14 -0
- eb_contracts/contracts/context/v1/__init__.py +8 -0
- eb_contracts/contracts/context/v1/run_context.py +202 -0
- eb_contracts/contracts/costs/__init__.py +14 -0
- eb_contracts/contracts/costs/v1/__init__.py +8 -0
- eb_contracts/contracts/costs/v1/cost_asymmetry_spec.py +143 -0
- eb_contracts/contracts/demand_panel/__init__.py +24 -0
- eb_contracts/contracts/demand_panel/v1/__init__.py +9 -0
- eb_contracts/contracts/demand_panel/v1/panel_demand.py +239 -0
- eb_contracts/contracts/forecast_panel/__init__.py +24 -0
- eb_contracts/contracts/forecast_panel/v1/__init__.py +9 -0
- eb_contracts/contracts/forecast_panel/v1/forecast_panel.py +195 -0
- eb_contracts/contracts/readiness/v1/__init__.py +9 -0
- eb_contracts/contracts/readiness/v1/panel_fpc_result.py +316 -0
- eb_contracts/contracts/results/__init__.py +14 -0
- eb_contracts/contracts/results/v1/__init__.py +9 -0
- eb_contracts/contracts/results/v1/panel_point_result.py +117 -0
- eb_contracts/definitions/__init__.py +123 -0
- eb_contracts/definitions/conventions.py +129 -0
- eb_contracts/definitions/glossary.py +103 -0
- eb_contracts/definitions/semantics.py +134 -0
- eb_contracts/definitions/units.py +136 -0
- eb_contracts/validation/errors.py +29 -0
- eb_contracts-0.2.0.dist-info/METADATA +134 -0
- eb_contracts-0.2.0.dist-info/RECORD +35 -0
- eb_contracts-0.2.0.dist-info/WHEEL +4 -0
- eb_contracts-0.2.0.dist-info/licenses/LICENSE +28 -0
eb_contracts/__init__.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Public API for EB contracts.
|
|
3
|
+
|
|
4
|
+
This package provides contract artifacts and validation entrypoints for
|
|
5
|
+
forecasting and panel-based evaluation.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from eb_contracts.api.validate import (
|
|
11
|
+
panel_point_v1,
|
|
12
|
+
panel_quantile_v1,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
######################################
|
|
16
|
+
# Public API
|
|
17
|
+
######################################
|
|
18
|
+
from eb_contracts.contracts._internal.runtime import set_validation_mode
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"panel_point_v1",
|
|
22
|
+
"panel_quantile_v1",
|
|
23
|
+
"set_validation_mode",
|
|
24
|
+
]
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Public migration API.
|
|
3
|
+
|
|
4
|
+
This package provides explicit helpers for adapting external data into EB
|
|
5
|
+
contract artifacts.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
######################################
|
|
11
|
+
# Public API
|
|
12
|
+
######################################
|
|
13
|
+
from eb_contracts.api.migrate_forecast import (
|
|
14
|
+
PanelPointColumns,
|
|
15
|
+
PanelQuantileColumns,
|
|
16
|
+
to_panel_point_result_v1,
|
|
17
|
+
to_panel_point_v1,
|
|
18
|
+
to_panel_quantile_v1,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"PanelPointColumns",
|
|
23
|
+
"PanelQuantileColumns",
|
|
24
|
+
"to_panel_point_result_v1",
|
|
25
|
+
"to_panel_point_v1",
|
|
26
|
+
"to_panel_quantile_v1",
|
|
27
|
+
]
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Forecast migration helpers.
|
|
3
|
+
|
|
4
|
+
This module contains explicit utilities for adapting "in the wild" forecast frames
|
|
5
|
+
into EB contract artifacts.
|
|
6
|
+
|
|
7
|
+
Migration is intentionally explicit:
|
|
8
|
+
- You provide column mappings.
|
|
9
|
+
- The output is a validated contract artifact (unless validation mode is off).
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
from typing import Final
|
|
16
|
+
|
|
17
|
+
import pandas as pd
|
|
18
|
+
|
|
19
|
+
from eb_contracts.contracts.forecast_panel.v1.forecast_panel import (
|
|
20
|
+
PanelPointForecastV1,
|
|
21
|
+
PanelQuantileForecastV1,
|
|
22
|
+
)
|
|
23
|
+
from eb_contracts.contracts.results.v1.panel_point_result import PanelPointResultV1
|
|
24
|
+
|
|
25
|
+
######################################
|
|
26
|
+
# Mapping specs
|
|
27
|
+
######################################
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass(frozen=True, slots=True)
|
|
31
|
+
class PanelPointColumns:
|
|
32
|
+
"""Column mapping for point forecasts."""
|
|
33
|
+
|
|
34
|
+
entity_id: str
|
|
35
|
+
interval_start: str
|
|
36
|
+
y_true: str
|
|
37
|
+
y_pred: str
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass(frozen=True, slots=True)
|
|
41
|
+
class PanelQuantileColumns:
|
|
42
|
+
"""Column mapping for quantile forecasts."""
|
|
43
|
+
|
|
44
|
+
entity_id: str
|
|
45
|
+
interval_start: str
|
|
46
|
+
y_true: str
|
|
47
|
+
q: str
|
|
48
|
+
y_pred_q: str
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
######################################
|
|
52
|
+
# Public API
|
|
53
|
+
######################################
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
_RESULT_COLUMNS: Final[set[str]] = {
|
|
57
|
+
PanelPointResultV1.Y_TRUE_COL,
|
|
58
|
+
PanelPointResultV1.Y_PRED_COL,
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def to_panel_point_v1(frame: pd.DataFrame, *, columns: PanelPointColumns) -> PanelPointForecastV1:
|
|
63
|
+
"""Adapt a frame into the PanelPointForecastV1 contract."""
|
|
64
|
+
out = frame.rename(
|
|
65
|
+
columns={
|
|
66
|
+
columns.entity_id: PanelPointForecastV1.ENTITY_COL,
|
|
67
|
+
columns.interval_start: PanelPointForecastV1.INTERVAL_START_COL,
|
|
68
|
+
columns.y_true: PanelPointForecastV1.Y_TRUE_COL,
|
|
69
|
+
columns.y_pred: PanelPointForecastV1.Y_PRED_COL,
|
|
70
|
+
}
|
|
71
|
+
)
|
|
72
|
+
keep = [
|
|
73
|
+
PanelPointForecastV1.ENTITY_COL,
|
|
74
|
+
PanelPointForecastV1.INTERVAL_START_COL,
|
|
75
|
+
PanelPointForecastV1.Y_TRUE_COL,
|
|
76
|
+
PanelPointForecastV1.Y_PRED_COL,
|
|
77
|
+
]
|
|
78
|
+
return PanelPointForecastV1.from_frame(out.loc[:, keep])
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def to_panel_quantile_v1(
|
|
82
|
+
frame: pd.DataFrame, *, columns: PanelQuantileColumns
|
|
83
|
+
) -> PanelQuantileForecastV1:
|
|
84
|
+
"""Adapt a frame into the PanelQuantileForecastV1 contract."""
|
|
85
|
+
out = frame.rename(
|
|
86
|
+
columns={
|
|
87
|
+
columns.entity_id: PanelQuantileForecastV1.ENTITY_COL,
|
|
88
|
+
columns.interval_start: PanelQuantileForecastV1.INTERVAL_START_COL,
|
|
89
|
+
columns.y_true: PanelQuantileForecastV1.Y_TRUE_COL,
|
|
90
|
+
columns.q: PanelQuantileForecastV1.Q_COL,
|
|
91
|
+
columns.y_pred_q: PanelQuantileForecastV1.Y_PRED_Q_COL,
|
|
92
|
+
}
|
|
93
|
+
)
|
|
94
|
+
keep = [
|
|
95
|
+
PanelQuantileForecastV1.ENTITY_COL,
|
|
96
|
+
PanelQuantileForecastV1.INTERVAL_START_COL,
|
|
97
|
+
PanelQuantileForecastV1.Y_TRUE_COL,
|
|
98
|
+
PanelQuantileForecastV1.Q_COL,
|
|
99
|
+
PanelQuantileForecastV1.Y_PRED_Q_COL,
|
|
100
|
+
]
|
|
101
|
+
return PanelQuantileForecastV1.from_frame(out.loc[:, keep])
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def to_panel_point_result_v1(
|
|
105
|
+
frame: pd.DataFrame, *, columns: PanelPointColumns
|
|
106
|
+
) -> PanelPointResultV1:
|
|
107
|
+
"""Adapt a frame into the PanelPointResultV1 contract."""
|
|
108
|
+
out = frame.rename(
|
|
109
|
+
columns={
|
|
110
|
+
columns.entity_id: PanelPointResultV1.ENTITY_COL,
|
|
111
|
+
columns.interval_start: PanelPointResultV1.INTERVAL_START_COL,
|
|
112
|
+
columns.y_true: PanelPointResultV1.Y_TRUE_COL,
|
|
113
|
+
columns.y_pred: PanelPointResultV1.Y_PRED_COL,
|
|
114
|
+
}
|
|
115
|
+
)
|
|
116
|
+
keep = [
|
|
117
|
+
PanelPointResultV1.ENTITY_COL,
|
|
118
|
+
PanelPointResultV1.INTERVAL_START_COL,
|
|
119
|
+
PanelPointResultV1.Y_TRUE_COL,
|
|
120
|
+
PanelPointResultV1.Y_PRED_COL,
|
|
121
|
+
]
|
|
122
|
+
return PanelPointResultV1.from_frame(out.loc[:, keep])
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Public validation entrypoints for contract artifacts.
|
|
3
|
+
|
|
4
|
+
This module defines stable, versioned entrypoints for validating and constructing
|
|
5
|
+
contract-wrapped data artifacts. Consumers should prefer these functions over
|
|
6
|
+
importing versioned contract modules directly.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import pandas as pd
|
|
12
|
+
|
|
13
|
+
from eb_contracts.contracts.costs.v1.cost_asymmetry_spec import CostAsymmetrySpecV1
|
|
14
|
+
from eb_contracts.contracts.demand_panel.v1.panel_demand import (
|
|
15
|
+
PanelDemandV1,
|
|
16
|
+
validate_panel_demand_v1,
|
|
17
|
+
)
|
|
18
|
+
from eb_contracts.contracts.forecast_panel.v1.forecast_panel import (
|
|
19
|
+
PanelPointForecastV1,
|
|
20
|
+
PanelQuantileForecastV1,
|
|
21
|
+
)
|
|
22
|
+
from eb_contracts.contracts.results.v1.panel_point_result import PanelPointResultV1
|
|
23
|
+
|
|
24
|
+
######################################
|
|
25
|
+
# Public API
|
|
26
|
+
######################################
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def panel_demand_v1(panel: PanelDemandV1) -> None:
|
|
30
|
+
"""Validate a V1 demand panel artifact."""
|
|
31
|
+
validate_panel_demand_v1(panel)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def panel_point_forecast_v1(frame: pd.DataFrame) -> PanelPointForecastV1:
|
|
35
|
+
"""
|
|
36
|
+
Validate and construct a V1 panel point forecast artifact.
|
|
37
|
+
|
|
38
|
+
This is the canonical, explicit entrypoint for point forecast panels.
|
|
39
|
+
"""
|
|
40
|
+
return PanelPointForecastV1.from_frame(frame)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def panel_quantile_forecast_v1(frame: pd.DataFrame) -> PanelQuantileForecastV1:
|
|
44
|
+
"""
|
|
45
|
+
Validate and construct a V1 panel quantile forecast artifact.
|
|
46
|
+
|
|
47
|
+
This is the canonical, explicit entrypoint for quantile forecast panels.
|
|
48
|
+
"""
|
|
49
|
+
return PanelQuantileForecastV1.from_frame(frame)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# Backwards-compatible aliases (keep these stable for downstream users).
|
|
53
|
+
def panel_point_v1(frame: pd.DataFrame) -> PanelPointForecastV1:
|
|
54
|
+
"""Alias for `panel_point_forecast_v1` (kept for backwards compatibility)."""
|
|
55
|
+
return panel_point_forecast_v1(frame)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def panel_quantile_v1(frame: pd.DataFrame) -> PanelQuantileForecastV1:
|
|
59
|
+
"""Alias for `panel_quantile_forecast_v1` (kept for backwards compatibility)."""
|
|
60
|
+
return panel_quantile_forecast_v1(frame)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def cost_asymmetry_v1(frame: pd.DataFrame) -> CostAsymmetrySpecV1:
|
|
64
|
+
"""Validate and construct a V1 cost-asymmetry specification artifact."""
|
|
65
|
+
return CostAsymmetrySpecV1.from_frame(frame)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def panel_point_result_v1(frame: pd.DataFrame) -> PanelPointResultV1:
|
|
69
|
+
"""Validate and construct a V1 panel point result artifact."""
|
|
70
|
+
return PanelPointResultV1.from_frame(frame)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
__all__ = [
|
|
74
|
+
"cost_asymmetry_v1",
|
|
75
|
+
"panel_demand_v1",
|
|
76
|
+
"panel_point_forecast_v1",
|
|
77
|
+
"panel_point_result_v1",
|
|
78
|
+
"panel_point_v1",
|
|
79
|
+
"panel_quantile_forecast_v1",
|
|
80
|
+
"panel_quantile_v1",
|
|
81
|
+
]
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Contract artifacts and internal contract helpers.
|
|
3
|
+
|
|
4
|
+
This package contains versioned contract artifacts (e.g., demand panels, forecasts,
|
|
5
|
+
results, readiness diagnostics) and internal utilities used by those artifacts.
|
|
6
|
+
|
|
7
|
+
Do not treat this package as a stable public API surface. Prefer `eb_contracts.api`
|
|
8
|
+
for user-facing entrypoints.
|
|
9
|
+
"""
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Runtime configuration for contract validation.
|
|
3
|
+
|
|
4
|
+
This module provides a minimal runtime surface used by contract validators to
|
|
5
|
+
determine validation behavior (e.g., strict, warn, off).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from collections.abc import Iterator
|
|
11
|
+
from contextlib import contextmanager
|
|
12
|
+
import contextvars
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
from typing import Literal
|
|
15
|
+
|
|
16
|
+
ValidationMode = Literal["strict", "warn", "off"]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(frozen=True, slots=True)
|
|
20
|
+
class RuntimeConfig:
|
|
21
|
+
"""Runtime configuration controlling validation behavior."""
|
|
22
|
+
|
|
23
|
+
validation: ValidationMode = "strict"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
_runtime_var: contextvars.ContextVar[RuntimeConfig | None] = contextvars.ContextVar(
|
|
27
|
+
"eb_contracts_runtime",
|
|
28
|
+
default=None,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def get_runtime() -> RuntimeConfig:
|
|
33
|
+
"""Return the active runtime configuration."""
|
|
34
|
+
cfg = _runtime_var.get()
|
|
35
|
+
if cfg is None:
|
|
36
|
+
cfg = RuntimeConfig()
|
|
37
|
+
_runtime_var.set(cfg)
|
|
38
|
+
return cfg
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@contextmanager
|
|
42
|
+
def set_validation_mode(mode: ValidationMode) -> Iterator[None]:
|
|
43
|
+
"""Temporarily set validation behavior within a context."""
|
|
44
|
+
token = _runtime_var.set(RuntimeConfig(validation=mode))
|
|
45
|
+
try:
|
|
46
|
+
yield
|
|
47
|
+
finally:
|
|
48
|
+
_runtime_var.reset(token)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Shared typing definitions for EB contracts.
|
|
3
|
+
|
|
4
|
+
This module centralizes type aliases and lightweight protocol definitions
|
|
5
|
+
used across contract artifacts, validators, and public entrypoints.
|
|
6
|
+
|
|
7
|
+
It contains no runtime logic.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import Literal, TypeAlias
|
|
13
|
+
|
|
14
|
+
import pandas as pd
|
|
15
|
+
|
|
16
|
+
######################################
|
|
17
|
+
# Core data structures
|
|
18
|
+
######################################
|
|
19
|
+
|
|
20
|
+
# Canonical DataFrame type used throughout contract artifacts.
|
|
21
|
+
Frame: TypeAlias = pd.DataFrame
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
######################################
|
|
25
|
+
# Validation behavior
|
|
26
|
+
######################################
|
|
27
|
+
|
|
28
|
+
ValidationMode: TypeAlias = Literal["strict", "warn", "off"]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
######################################
|
|
32
|
+
# Contract markers
|
|
33
|
+
######################################
|
|
34
|
+
|
|
35
|
+
# Marker alias for validated contract artifacts.
|
|
36
|
+
# Contract classes typically expose a `.frame` attribute of type Frame.
|
|
37
|
+
ContractArtifact: TypeAlias = object
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Public context contract API.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
######################################
|
|
8
|
+
# Public API
|
|
9
|
+
######################################
|
|
10
|
+
from eb_contracts.contracts.context.v1.run_context import RunContextV1
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"RunContextV1",
|
|
14
|
+
]
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Context contract artifacts (version 1).
|
|
3
|
+
|
|
4
|
+
This package contains version 1 context-related contract artifacts, including
|
|
5
|
+
validated run-level metadata used for provenance and governance.
|
|
6
|
+
|
|
7
|
+
Artifacts in this package are versioned and may evolve in future releases.
|
|
8
|
+
"""
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Context contract models (V1).
|
|
3
|
+
|
|
4
|
+
This module defines V1 context artifacts for attaching non-tabular metadata to
|
|
5
|
+
forecasts, costs, and results.
|
|
6
|
+
|
|
7
|
+
V1 includes:
|
|
8
|
+
- RunContextV1: run-level metadata describing provenance and semantics
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
from datetime import datetime
|
|
15
|
+
from typing import ClassVar, Final
|
|
16
|
+
|
|
17
|
+
from eb_contracts.contracts._internal.runtime import get_runtime
|
|
18
|
+
from eb_contracts.validation.errors import ContractViolation, ContractViolationError
|
|
19
|
+
|
|
20
|
+
######################################
|
|
21
|
+
# Contract artifacts
|
|
22
|
+
######################################
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True, slots=True)
|
|
26
|
+
class RunContextV1:
|
|
27
|
+
"""Validated run-level context metadata."""
|
|
28
|
+
|
|
29
|
+
run_id: str
|
|
30
|
+
issued_at: datetime
|
|
31
|
+
|
|
32
|
+
model_id: str | None = None
|
|
33
|
+
dataset_id: str | None = None
|
|
34
|
+
|
|
35
|
+
horizon: int | None = None
|
|
36
|
+
interval_minutes: int | None = None
|
|
37
|
+
tz: str | None = None
|
|
38
|
+
|
|
39
|
+
tags: dict[str, str] | None = None
|
|
40
|
+
|
|
41
|
+
CONTRACT_NAME: ClassVar[Final[str]] = "RunContextV1"
|
|
42
|
+
|
|
43
|
+
@classmethod
|
|
44
|
+
def from_values(
|
|
45
|
+
cls,
|
|
46
|
+
*,
|
|
47
|
+
run_id: str,
|
|
48
|
+
issued_at: datetime,
|
|
49
|
+
model_id: str | None = None,
|
|
50
|
+
dataset_id: str | None = None,
|
|
51
|
+
horizon: int | None = None,
|
|
52
|
+
interval_minutes: int | None = None,
|
|
53
|
+
tz: str | None = None,
|
|
54
|
+
tags: dict[str, str] | None = None,
|
|
55
|
+
) -> RunContextV1:
|
|
56
|
+
violations = validate_run_context_v1(
|
|
57
|
+
run_id=run_id,
|
|
58
|
+
issued_at=issued_at,
|
|
59
|
+
model_id=model_id,
|
|
60
|
+
dataset_id=dataset_id,
|
|
61
|
+
horizon=horizon,
|
|
62
|
+
interval_minutes=interval_minutes,
|
|
63
|
+
tz=tz,
|
|
64
|
+
tags=tags,
|
|
65
|
+
)
|
|
66
|
+
_raise_or_warn(cls.CONTRACT_NAME, violations)
|
|
67
|
+
return cls(
|
|
68
|
+
run_id=run_id,
|
|
69
|
+
issued_at=issued_at,
|
|
70
|
+
model_id=model_id,
|
|
71
|
+
dataset_id=dataset_id,
|
|
72
|
+
horizon=horizon,
|
|
73
|
+
interval_minutes=interval_minutes,
|
|
74
|
+
tz=tz,
|
|
75
|
+
tags=tags,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
######################################
|
|
80
|
+
# Validators
|
|
81
|
+
######################################
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def validate_run_context_v1(
|
|
85
|
+
*,
|
|
86
|
+
run_id: str,
|
|
87
|
+
issued_at: datetime,
|
|
88
|
+
model_id: str | None,
|
|
89
|
+
dataset_id: str | None,
|
|
90
|
+
horizon: int | None,
|
|
91
|
+
interval_minutes: int | None,
|
|
92
|
+
tz: str | None,
|
|
93
|
+
tags: dict[str, str] | None,
|
|
94
|
+
) -> list[ContractViolation]:
|
|
95
|
+
"""Validate values against the RunContextV1 contract."""
|
|
96
|
+
violations: list[ContractViolation] = []
|
|
97
|
+
|
|
98
|
+
if not run_id or not isinstance(run_id, str):
|
|
99
|
+
violations.append(
|
|
100
|
+
ContractViolation(
|
|
101
|
+
code="invalid_run_id",
|
|
102
|
+
message="run_id must be a non-empty string.",
|
|
103
|
+
)
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
if not isinstance(issued_at, datetime):
|
|
107
|
+
violations.append(
|
|
108
|
+
ContractViolation(
|
|
109
|
+
code="invalid_issued_at",
|
|
110
|
+
message="issued_at must be a datetime.",
|
|
111
|
+
)
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
if horizon is not None and horizon <= 0:
|
|
115
|
+
violations.append(
|
|
116
|
+
ContractViolation(
|
|
117
|
+
code="invalid_horizon",
|
|
118
|
+
message="horizon must be a positive integer when provided.",
|
|
119
|
+
)
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
if interval_minutes is not None and interval_minutes <= 0:
|
|
123
|
+
violations.append(
|
|
124
|
+
ContractViolation(
|
|
125
|
+
code="invalid_interval_minutes",
|
|
126
|
+
message="interval_minutes must be a positive integer when provided.",
|
|
127
|
+
)
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
if tags is not None:
|
|
131
|
+
if not isinstance(tags, dict):
|
|
132
|
+
violations.append(
|
|
133
|
+
ContractViolation(
|
|
134
|
+
code="invalid_tags",
|
|
135
|
+
message="tags must be a dict[str, str] when provided.",
|
|
136
|
+
)
|
|
137
|
+
)
|
|
138
|
+
else:
|
|
139
|
+
for k, v in tags.items():
|
|
140
|
+
if not isinstance(k, str) or not k:
|
|
141
|
+
violations.append(
|
|
142
|
+
ContractViolation(
|
|
143
|
+
code="invalid_tag_key",
|
|
144
|
+
message="All tag keys must be non-empty strings.",
|
|
145
|
+
)
|
|
146
|
+
)
|
|
147
|
+
break
|
|
148
|
+
if not isinstance(v, str):
|
|
149
|
+
violations.append(
|
|
150
|
+
ContractViolation(
|
|
151
|
+
code="invalid_tag_value",
|
|
152
|
+
message="All tag values must be strings.",
|
|
153
|
+
)
|
|
154
|
+
)
|
|
155
|
+
break
|
|
156
|
+
|
|
157
|
+
if model_id is not None and not isinstance(model_id, str):
|
|
158
|
+
violations.append(
|
|
159
|
+
ContractViolation(
|
|
160
|
+
code="invalid_model_id",
|
|
161
|
+
message="model_id must be a string when provided.",
|
|
162
|
+
)
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
if dataset_id is not None and not isinstance(dataset_id, str):
|
|
166
|
+
violations.append(
|
|
167
|
+
ContractViolation(
|
|
168
|
+
code="invalid_dataset_id",
|
|
169
|
+
message="dataset_id must be a string when provided.",
|
|
170
|
+
)
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
if tz is not None and (not isinstance(tz, str) or not tz):
|
|
174
|
+
violations.append(
|
|
175
|
+
ContractViolation(
|
|
176
|
+
code="invalid_tz",
|
|
177
|
+
message="tz must be a non-empty string when provided.",
|
|
178
|
+
)
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
return violations
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
######################################
|
|
185
|
+
# Internal helpers
|
|
186
|
+
######################################
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _raise_or_warn(contract: str, violations: list[ContractViolation]) -> None:
|
|
190
|
+
"""Apply runtime validation behavior."""
|
|
191
|
+
if not violations:
|
|
192
|
+
return
|
|
193
|
+
|
|
194
|
+
mode = get_runtime().validation
|
|
195
|
+
if mode == "off":
|
|
196
|
+
return
|
|
197
|
+
|
|
198
|
+
if mode == "warn":
|
|
199
|
+
print(f"[eb-contracts] WARN: {contract}: " + "; ".join(v.message for v in violations))
|
|
200
|
+
return
|
|
201
|
+
|
|
202
|
+
raise ContractViolationError(contract=contract, violations=violations)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Public cost contract API.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
######################################
|
|
8
|
+
# Public API
|
|
9
|
+
######################################
|
|
10
|
+
from eb_contracts.contracts.costs.v1.cost_asymmetry_spec import CostAsymmetrySpecV1
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"CostAsymmetrySpecV1",
|
|
14
|
+
]
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Cost contract artifacts (version 1).
|
|
3
|
+
|
|
4
|
+
This package contains version 1 cost-related contract artifacts, including
|
|
5
|
+
validated cost-asymmetry specifications used for evaluation and optimization.
|
|
6
|
+
|
|
7
|
+
Artifacts in this package are versioned and may evolve in future releases.
|
|
8
|
+
"""
|