pam-python 0.2.11__tar.gz → 0.2.13__tar.gz
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.
- {pam_python-0.2.11/pam_python.egg-info → pam_python-0.2.13}/PKG-INFO +1 -1
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/__init__.py +1 -1
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/cli.py +1 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/reports/__init__.py +2 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/reports/area.py +10 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/reports/bar.py +10 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/reports/definition.py +16 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/reports/donut.py +6 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/reports/line.py +10 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/reports/metric.py +10 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/reports/pie.py +6 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/reports/polar_area.py +6 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/reports/radial_bar.py +10 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/reports/store.py +33 -8
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/reports/table.py +10 -0
- pam_python-0.2.13/pam/reports/temporal.py +55 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/templates/init/AGENT.md +9 -4
- pam_python-0.2.13/pam/templates/init/REPORTS-th.md +744 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/templates/init/REPORTS.md +140 -16
- {pam_python-0.2.11 → pam_python-0.2.13/pam_python.egg-info}/PKG-INFO +1 -1
- {pam_python-0.2.11 → pam_python-0.2.13}/pam_python.egg-info/SOURCES.txt +3 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/tests/test_cli_templates.py +31 -0
- pam_python-0.2.13/tests/test_temporal_mode.py +116 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/LICENSE.txt +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/README.md +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/api.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/datetime_utils.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/interface_task_manager.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/logger.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/models/__init__.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/models/data_input_v2.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/models/request_command.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/models/sqlite_download.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/reports/axis.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/reports/box_plot.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/reports/bubble.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/reports/candlestick.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/reports/collection.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/reports/errors.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/reports/fields.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/reports/heatmap.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/reports/manager.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/reports/radar.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/reports/range_area.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/reports/range_bar.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/reports/range_transform.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/reports/record.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/reports/report.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/reports/scatter.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/reports/schema.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/reports/transport.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/reports/treemap.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/request_file_format.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/result_batch_uploader.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/server.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/service.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/sqlite.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/task_manager.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/temp_file_utils.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/templates/buildcmd/pamb +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/templates/buildcmd/pamb-base.sh +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/templates/init/dockerignore.tmpl +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/templates/init/gitignore.tmpl +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/templates/init/main.tmpl +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/templates/init/pylintrc.tmpl +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/templates/init/run_unit_test.bat +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/templates/init/run_unit_test.ps1 +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/templates/init/run_unit_test.sh +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/templates/project/pip/Dockerfile +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/templates/project/pip/requirements.txt +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/templates/project/uv/Dockerfile +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/templates/project/uv/pyproject.toml +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/templates/project/uv/python-version +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/templates/service/common/service.yaml +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/templates/service/pandas/functions.tmpl +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/templates/service/pandas/service.test.tmpl +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/templates/service/pandas/service_class.tmpl +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/templates/service/polars/functions.tmpl +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/templates/service/polars/service.test.tmpl +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/templates/service/polars/service_class.tmpl +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/tester_task.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam/utils.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam_python.egg-info/dependency_links.txt +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam_python.egg-info/entry_points.txt +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam_python.egg-info/requires.txt +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pam_python.egg-info/top_level.txt +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/pyproject.toml +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/setup.cfg +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/tests/test_api.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/tests/test_data_error_v2.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/tests/test_data_input_v2.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/tests/test_data_timeout_v2.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/tests/test_datetime_utils.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/tests/test_range_transform.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/tests/test_report_store.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/tests/test_request_data_v2.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/tests/test_result_batch_uploader.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/tests/test_service_dataframe_output.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/tests/test_sqlite_download_v2.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/tests/test_temp_file_utils.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/tests/test_typed_area_report.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/tests/test_typed_bar_report.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/tests/test_typed_box_plot_report.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/tests/test_typed_bubble_report.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/tests/test_typed_candlestick_report.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/tests/test_typed_donut_report.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/tests/test_typed_heatmap_report.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/tests/test_typed_line_report.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/tests/test_typed_metric_report.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/tests/test_typed_pie_report.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/tests/test_typed_polar_area_report.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/tests/test_typed_radar_report.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/tests/test_typed_radial_bar_report.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/tests/test_typed_range_area_report.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/tests/test_typed_range_bar_report.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/tests/test_typed_scatter_report.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/tests/test_typed_table_report.py +0 -0
- {pam_python-0.2.11 → pam_python-0.2.13}/tests/test_typed_treemap_report.py +0 -0
|
@@ -21,6 +21,7 @@ COMMON_PROJECT_TEMPLATES = {
|
|
|
21
21
|
"buildcmd/pamb-base.sh": "pamb-base.sh",
|
|
22
22
|
"init/pylintrc.tmpl": ".pylintrc",
|
|
23
23
|
"init/REPORTS.md": "REPORTS.md",
|
|
24
|
+
"init/REPORTS-th.md": "REPORTS-th.md",
|
|
24
25
|
"init/AGENT.md": "AGENT.md",
|
|
25
26
|
"init/gitignore.tmpl": ".gitignore",
|
|
26
27
|
"init/dockerignore.tmpl": ".dockerignore",
|
|
@@ -31,6 +31,7 @@ from pam.reports.range_bar import RangeBarChartReport
|
|
|
31
31
|
from pam.reports.scatter import ScatterChartReport
|
|
32
32
|
from pam.reports.store import ReportStore
|
|
33
33
|
from pam.reports.table import TableReport
|
|
34
|
+
from pam.reports.temporal import TemporalMode
|
|
34
35
|
from pam.reports.treemap import TreemapReport
|
|
35
36
|
|
|
36
37
|
__all__ = [
|
|
@@ -68,6 +69,7 @@ __all__ = [
|
|
|
68
69
|
"ScatterChartReport",
|
|
69
70
|
"Sum",
|
|
70
71
|
"TableReport",
|
|
72
|
+
"TemporalMode",
|
|
71
73
|
"TimeRangeTransform",
|
|
72
74
|
"TreemapReport",
|
|
73
75
|
]
|
|
@@ -8,6 +8,7 @@ from pam.reports.definition import ReportDefinition
|
|
|
8
8
|
from pam.reports.errors import ReportStoreValidationError
|
|
9
9
|
from pam.reports.fields import Dimension, Measure
|
|
10
10
|
from pam.reports.range_transform import TimeRangeTransform, normalize_range_transform
|
|
11
|
+
from pam.reports.temporal import TemporalMode, normalize_temporal_mode
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
AREA_RENDERER = "area"
|
|
@@ -50,6 +51,7 @@ class AreaChartReport(TypedCollectionReport):
|
|
|
50
51
|
series: Optional[Dimension] = None,
|
|
51
52
|
stacked: bool = False,
|
|
52
53
|
range_transform: Optional[TimeRangeTransform] = None,
|
|
54
|
+
temporal_mode: Optional[TemporalMode] = None,
|
|
53
55
|
):
|
|
54
56
|
if not isinstance(stacked, bool):
|
|
55
57
|
raise ReportStoreValidationError("Area stacked must be boolean")
|
|
@@ -61,6 +63,13 @@ class AreaChartReport(TypedCollectionReport):
|
|
|
61
63
|
normalized_range_transform = normalize_range_transform(
|
|
62
64
|
range_transform, granularity, normalized_values, "Area Chart"
|
|
63
65
|
)
|
|
66
|
+
normalized_temporal_mode = normalize_temporal_mode(
|
|
67
|
+
temporal_mode,
|
|
68
|
+
granularity,
|
|
69
|
+
normalized_values,
|
|
70
|
+
"Area Chart",
|
|
71
|
+
range_transform,
|
|
72
|
+
)
|
|
64
73
|
definition = ReportDefinition.create(
|
|
65
74
|
report_type="area",
|
|
66
75
|
contract_version=AREA_CONTRACT_VERSION,
|
|
@@ -72,6 +81,7 @@ class AreaChartReport(TypedCollectionReport):
|
|
|
72
81
|
},
|
|
73
82
|
options={"stacked": stacked},
|
|
74
83
|
range_transform=normalized_range_transform,
|
|
84
|
+
temporal_mode=normalized_temporal_mode,
|
|
75
85
|
)
|
|
76
86
|
report = store._declare_typed_report(
|
|
77
87
|
key=key,
|
|
@@ -8,6 +8,7 @@ from pam.reports.definition import ReportDefinition
|
|
|
8
8
|
from pam.reports.errors import ReportStoreValidationError
|
|
9
9
|
from pam.reports.fields import Dimension, Measure
|
|
10
10
|
from pam.reports.range_transform import TimeRangeTransform, normalize_range_transform
|
|
11
|
+
from pam.reports.temporal import TemporalMode, normalize_temporal_mode
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
BAR_RENDERER = "bar"
|
|
@@ -56,6 +57,7 @@ class BarChartReport(TypedCollectionReport):
|
|
|
56
57
|
orientation: str = "vertical",
|
|
57
58
|
stacked: bool = False,
|
|
58
59
|
range_transform: Optional[TimeRangeTransform] = None,
|
|
60
|
+
temporal_mode: Optional[TemporalMode] = None,
|
|
59
61
|
):
|
|
60
62
|
if not isinstance(category, Dimension):
|
|
61
63
|
raise ReportStoreValidationError(
|
|
@@ -110,6 +112,13 @@ class BarChartReport(TypedCollectionReport):
|
|
|
110
112
|
normalized_range_transform = normalize_range_transform(
|
|
111
113
|
range_transform, granularity, normalized_values, "Bar Chart"
|
|
112
114
|
)
|
|
115
|
+
normalized_temporal_mode = normalize_temporal_mode(
|
|
116
|
+
temporal_mode,
|
|
117
|
+
granularity,
|
|
118
|
+
normalized_values,
|
|
119
|
+
"Bar Chart",
|
|
120
|
+
range_transform,
|
|
121
|
+
)
|
|
113
122
|
definition = ReportDefinition.create(
|
|
114
123
|
report_type="bar",
|
|
115
124
|
contract_version=BAR_CONTRACT_VERSION,
|
|
@@ -121,6 +130,7 @@ class BarChartReport(TypedCollectionReport):
|
|
|
121
130
|
},
|
|
122
131
|
options={"orientation": orientation, "stacked": stacked},
|
|
123
132
|
range_transform=normalized_range_transform,
|
|
133
|
+
temporal_mode=normalized_temporal_mode,
|
|
124
134
|
)
|
|
125
135
|
report = store._declare_typed_report(
|
|
126
136
|
key=key,
|
|
@@ -18,6 +18,7 @@ class ReportDefinition:
|
|
|
18
18
|
binding: Dict[str, Any]
|
|
19
19
|
options: Dict[str, Any] = field(default_factory=dict)
|
|
20
20
|
range_transform: Optional[Dict[str, Any]] = None
|
|
21
|
+
temporal_mode: Optional[str] = None
|
|
21
22
|
|
|
22
23
|
def __post_init__(self) -> None:
|
|
23
24
|
if not isinstance(self.report_type, str) or not self.report_type:
|
|
@@ -41,6 +42,17 @@ class ReportDefinition:
|
|
|
41
42
|
raise ReportStoreValidationError(
|
|
42
43
|
"range_transform must be a dictionary or None"
|
|
43
44
|
)
|
|
45
|
+
if self.temporal_mode is not None and self.temporal_mode not in {
|
|
46
|
+
"AGGREGATE",
|
|
47
|
+
"SNAPSHOT",
|
|
48
|
+
}:
|
|
49
|
+
raise ReportStoreValidationError(
|
|
50
|
+
"temporal_mode must be AGGREGATE, SNAPSHOT, or None"
|
|
51
|
+
)
|
|
52
|
+
if self.temporal_mode is not None and self.range_transform is not None:
|
|
53
|
+
raise ReportStoreValidationError(
|
|
54
|
+
"temporal_mode and range_transform cannot be declared together"
|
|
55
|
+
)
|
|
44
56
|
|
|
45
57
|
# Validate serializability while the declaration is still close to its caller.
|
|
46
58
|
canonical_json(self.to_dict())
|
|
@@ -54,6 +66,7 @@ class ReportDefinition:
|
|
|
54
66
|
binding: Dict[str, Any],
|
|
55
67
|
options: Dict[str, Any] = None,
|
|
56
68
|
range_transform: Dict[str, Any] = None,
|
|
69
|
+
temporal_mode: str = None,
|
|
57
70
|
):
|
|
58
71
|
return cls(
|
|
59
72
|
report_type=report_type,
|
|
@@ -64,6 +77,7 @@ class ReportDefinition:
|
|
|
64
77
|
range_transform=(
|
|
65
78
|
dict(range_transform) if range_transform is not None else None
|
|
66
79
|
),
|
|
80
|
+
temporal_mode=temporal_mode,
|
|
67
81
|
)
|
|
68
82
|
|
|
69
83
|
def to_dict(self) -> Dict[str, Any]:
|
|
@@ -76,6 +90,8 @@ class ReportDefinition:
|
|
|
76
90
|
}
|
|
77
91
|
if self.range_transform is not None:
|
|
78
92
|
definition["range_transform"] = self.range_transform
|
|
93
|
+
if self.temporal_mode is not None:
|
|
94
|
+
definition["temporal_mode"] = self.temporal_mode
|
|
79
95
|
return definition
|
|
80
96
|
|
|
81
97
|
def to_json(self) -> str:
|
|
@@ -8,6 +8,7 @@ from pam.reports.definition import ReportDefinition
|
|
|
8
8
|
from pam.reports.errors import ReportStoreValidationError
|
|
9
9
|
from pam.reports.fields import Dimension, Measure
|
|
10
10
|
from pam.reports.range_transform import TimeRangeTransform, normalize_range_transform
|
|
11
|
+
from pam.reports.temporal import TemporalMode, normalize_temporal_mode
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
DONUT_RENDERER = "donut"
|
|
@@ -51,6 +52,7 @@ class DonutChartReport(TypedCollectionReport):
|
|
|
51
52
|
category: Dimension,
|
|
52
53
|
value: Measure,
|
|
53
54
|
range_transform: Optional[TimeRangeTransform] = None,
|
|
55
|
+
temporal_mode: Optional[TemporalMode] = None,
|
|
54
56
|
):
|
|
55
57
|
if not isinstance(category, Dimension):
|
|
56
58
|
raise ReportStoreValidationError(
|
|
@@ -80,12 +82,16 @@ class DonutChartReport(TypedCollectionReport):
|
|
|
80
82
|
normalized_range_transform = normalize_range_transform(
|
|
81
83
|
range_transform, granularity, (value,), "Donut"
|
|
82
84
|
)
|
|
85
|
+
normalized_temporal_mode = normalize_temporal_mode(
|
|
86
|
+
temporal_mode, granularity, (value,), "Donut", range_transform
|
|
87
|
+
)
|
|
83
88
|
definition = ReportDefinition.create(
|
|
84
89
|
report_type="donut",
|
|
85
90
|
contract_version=DONUT_CONTRACT_VERSION,
|
|
86
91
|
fields=(category, value),
|
|
87
92
|
binding={"category": category.name, "value": value.name},
|
|
88
93
|
range_transform=normalized_range_transform,
|
|
94
|
+
temporal_mode=normalized_temporal_mode,
|
|
89
95
|
)
|
|
90
96
|
report = store._declare_typed_report(
|
|
91
97
|
key=key,
|
|
@@ -8,6 +8,7 @@ from pam.reports.definition import ReportDefinition
|
|
|
8
8
|
from pam.reports.errors import ReportStoreValidationError
|
|
9
9
|
from pam.reports.fields import Dimension, Measure
|
|
10
10
|
from pam.reports.range_transform import TimeRangeTransform, normalize_range_transform
|
|
11
|
+
from pam.reports.temporal import TemporalMode, normalize_temporal_mode
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
LINE_RENDERER = "line"
|
|
@@ -46,6 +47,7 @@ class LineChartReport(TypedCollectionReport):
|
|
|
46
47
|
values: Iterable[Measure],
|
|
47
48
|
series: Optional[Dimension] = None,
|
|
48
49
|
range_transform: Optional[TimeRangeTransform] = None,
|
|
50
|
+
temporal_mode: Optional[TemporalMode] = None,
|
|
49
51
|
):
|
|
50
52
|
if granularity == "NONE":
|
|
51
53
|
raise ReportStoreValidationError("Line granularity must be DAY or MONTH")
|
|
@@ -55,6 +57,13 @@ class LineChartReport(TypedCollectionReport):
|
|
|
55
57
|
normalized_range_transform = normalize_range_transform(
|
|
56
58
|
range_transform, granularity, normalized_values, "Line"
|
|
57
59
|
)
|
|
60
|
+
normalized_temporal_mode = normalize_temporal_mode(
|
|
61
|
+
temporal_mode,
|
|
62
|
+
granularity,
|
|
63
|
+
normalized_values,
|
|
64
|
+
"Line",
|
|
65
|
+
range_transform,
|
|
66
|
+
)
|
|
58
67
|
definition = ReportDefinition.create(
|
|
59
68
|
report_type="line",
|
|
60
69
|
contract_version=LINE_CONTRACT_VERSION,
|
|
@@ -65,6 +74,7 @@ class LineChartReport(TypedCollectionReport):
|
|
|
65
74
|
"values": [value.name for value in normalized_values],
|
|
66
75
|
},
|
|
67
76
|
range_transform=normalized_range_transform,
|
|
77
|
+
temporal_mode=normalized_temporal_mode,
|
|
68
78
|
)
|
|
69
79
|
report = store._declare_typed_report(
|
|
70
80
|
key=key,
|
|
@@ -7,6 +7,7 @@ from pam.reports.errors import ReportStoreValidationError
|
|
|
7
7
|
from pam.reports.fields import Measure
|
|
8
8
|
from pam.reports.record import ReportRecord, build_record_key, validate_partition_key
|
|
9
9
|
from pam.reports.range_transform import TimeRangeTransform, normalize_range_transform
|
|
10
|
+
from pam.reports.temporal import TemporalMode, normalize_temporal_mode
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
METRIC_RENDERER = "metric"
|
|
@@ -31,6 +32,7 @@ class MetricReport:
|
|
|
31
32
|
granularity: str,
|
|
32
33
|
metrics: Iterable[Measure],
|
|
33
34
|
range_transform: Optional[TimeRangeTransform] = None,
|
|
35
|
+
temporal_mode: Optional[TemporalMode] = None,
|
|
34
36
|
):
|
|
35
37
|
if isinstance(metrics, (str, bytes)):
|
|
36
38
|
raise ReportStoreValidationError("metrics must contain Measure declarations")
|
|
@@ -50,6 +52,13 @@ class MetricReport:
|
|
|
50
52
|
normalized_range_transform = normalize_range_transform(
|
|
51
53
|
range_transform, granularity, normalized_metrics, "Metric"
|
|
52
54
|
)
|
|
55
|
+
normalized_temporal_mode = normalize_temporal_mode(
|
|
56
|
+
temporal_mode,
|
|
57
|
+
granularity,
|
|
58
|
+
normalized_metrics,
|
|
59
|
+
"Metric",
|
|
60
|
+
range_transform,
|
|
61
|
+
)
|
|
53
62
|
|
|
54
63
|
definition = ReportDefinition.create(
|
|
55
64
|
report_type="metric",
|
|
@@ -57,6 +66,7 @@ class MetricReport:
|
|
|
57
66
|
fields=normalized_metrics,
|
|
58
67
|
binding={"metrics": [metric.name for metric in normalized_metrics]},
|
|
59
68
|
range_transform=normalized_range_transform,
|
|
69
|
+
temporal_mode=normalized_temporal_mode,
|
|
60
70
|
)
|
|
61
71
|
report = store._declare_typed_report(
|
|
62
72
|
key=key,
|
|
@@ -8,6 +8,7 @@ from pam.reports.definition import ReportDefinition
|
|
|
8
8
|
from pam.reports.errors import ReportStoreValidationError
|
|
9
9
|
from pam.reports.fields import Dimension, Measure
|
|
10
10
|
from pam.reports.range_transform import TimeRangeTransform, normalize_range_transform
|
|
11
|
+
from pam.reports.temporal import TemporalMode, normalize_temporal_mode
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
PIE_RENDERER = "pie"
|
|
@@ -51,6 +52,7 @@ class PieChartReport(TypedCollectionReport):
|
|
|
51
52
|
category: Dimension,
|
|
52
53
|
value: Measure,
|
|
53
54
|
range_transform: Optional[TimeRangeTransform] = None,
|
|
55
|
+
temporal_mode: Optional[TemporalMode] = None,
|
|
54
56
|
):
|
|
55
57
|
if not isinstance(category, Dimension):
|
|
56
58
|
raise ReportStoreValidationError(
|
|
@@ -80,12 +82,16 @@ class PieChartReport(TypedCollectionReport):
|
|
|
80
82
|
normalized_range_transform = normalize_range_transform(
|
|
81
83
|
range_transform, granularity, (value,), "Pie"
|
|
82
84
|
)
|
|
85
|
+
normalized_temporal_mode = normalize_temporal_mode(
|
|
86
|
+
temporal_mode, granularity, (value,), "Pie", range_transform
|
|
87
|
+
)
|
|
83
88
|
definition = ReportDefinition.create(
|
|
84
89
|
report_type="pie",
|
|
85
90
|
contract_version=PIE_CONTRACT_VERSION,
|
|
86
91
|
fields=(category, value),
|
|
87
92
|
binding={"category": category.name, "value": value.name},
|
|
88
93
|
range_transform=normalized_range_transform,
|
|
94
|
+
temporal_mode=normalized_temporal_mode,
|
|
89
95
|
)
|
|
90
96
|
report = store._declare_typed_report(
|
|
91
97
|
key=key,
|
|
@@ -8,6 +8,7 @@ from pam.reports.definition import ReportDefinition
|
|
|
8
8
|
from pam.reports.errors import ReportStoreValidationError
|
|
9
9
|
from pam.reports.fields import Dimension, Measure
|
|
10
10
|
from pam.reports.range_transform import TimeRangeTransform, normalize_range_transform
|
|
11
|
+
from pam.reports.temporal import TemporalMode, normalize_temporal_mode
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
POLAR_AREA_RENDERER = "polar_area"
|
|
@@ -52,6 +53,7 @@ class PolarAreaChartReport(TypedCollectionReport):
|
|
|
52
53
|
category: Dimension,
|
|
53
54
|
value: Measure,
|
|
54
55
|
range_transform: Optional[TimeRangeTransform] = None,
|
|
56
|
+
temporal_mode: Optional[TemporalMode] = None,
|
|
55
57
|
):
|
|
56
58
|
if not isinstance(category, Dimension):
|
|
57
59
|
raise ReportStoreValidationError(
|
|
@@ -81,12 +83,16 @@ class PolarAreaChartReport(TypedCollectionReport):
|
|
|
81
83
|
normalized_range_transform = normalize_range_transform(
|
|
82
84
|
range_transform, granularity, (value,), "Polar Area"
|
|
83
85
|
)
|
|
86
|
+
normalized_temporal_mode = normalize_temporal_mode(
|
|
87
|
+
temporal_mode, granularity, (value,), "Polar Area", range_transform
|
|
88
|
+
)
|
|
84
89
|
definition = ReportDefinition.create(
|
|
85
90
|
report_type="polar_area",
|
|
86
91
|
contract_version=POLAR_AREA_CONTRACT_VERSION,
|
|
87
92
|
fields=(category, value),
|
|
88
93
|
binding={"category": category.name, "value": value.name},
|
|
89
94
|
range_transform=normalized_range_transform,
|
|
95
|
+
temporal_mode=normalized_temporal_mode,
|
|
90
96
|
)
|
|
91
97
|
report = store._declare_typed_report(
|
|
92
98
|
key=key,
|
|
@@ -9,6 +9,7 @@ from pam.reports.errors import ReportStoreValidationError
|
|
|
9
9
|
from pam.reports.fields import Measure
|
|
10
10
|
from pam.reports.record import ReportRecord, build_record_key, validate_partition_key
|
|
11
11
|
from pam.reports.range_transform import TimeRangeTransform, normalize_range_transform
|
|
12
|
+
from pam.reports.temporal import TemporalMode, normalize_temporal_mode
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
RADIAL_BAR_RENDERER = "radial_bar"
|
|
@@ -32,6 +33,7 @@ class RadialBarChartReport:
|
|
|
32
33
|
cls, store, key: str, granularity: str, metrics: Iterable[Measure],
|
|
33
34
|
min_value: float, max_value: float,
|
|
34
35
|
range_transform: Optional[TimeRangeTransform] = None,
|
|
36
|
+
temporal_mode: Optional[TemporalMode] = None,
|
|
35
37
|
):
|
|
36
38
|
try:
|
|
37
39
|
normalized_metrics = tuple(metrics)
|
|
@@ -66,6 +68,13 @@ class RadialBarChartReport:
|
|
|
66
68
|
normalized_transform = normalize_range_transform(
|
|
67
69
|
range_transform, granularity, normalized_metrics, "Radial Bar"
|
|
68
70
|
)
|
|
71
|
+
normalized_temporal_mode = normalize_temporal_mode(
|
|
72
|
+
temporal_mode,
|
|
73
|
+
granularity,
|
|
74
|
+
normalized_metrics,
|
|
75
|
+
"Radial Bar",
|
|
76
|
+
range_transform,
|
|
77
|
+
)
|
|
69
78
|
definition = ReportDefinition.create(
|
|
70
79
|
report_type="radial_bar",
|
|
71
80
|
contract_version=RADIAL_BAR_CONTRACT_VERSION,
|
|
@@ -73,6 +82,7 @@ class RadialBarChartReport:
|
|
|
73
82
|
binding={"metrics": [metric.name for metric in normalized_metrics]},
|
|
74
83
|
options={"min_value": min_value, "max_value": max_value},
|
|
75
84
|
range_transform=normalized_transform,
|
|
85
|
+
temporal_mode=normalized_temporal_mode,
|
|
76
86
|
)
|
|
77
87
|
report = store._declare_typed_report(
|
|
78
88
|
key, RADIAL_BAR_RENDERER, RADIAL_BAR_SCHEMA, granularity,
|
|
@@ -45,6 +45,7 @@ from pam.reports.schema import (
|
|
|
45
45
|
STORE_VERSION,
|
|
46
46
|
)
|
|
47
47
|
from pam.reports.table import TableReport
|
|
48
|
+
from pam.reports.temporal import TemporalMode
|
|
48
49
|
from pam.reports.treemap import TreemapReport
|
|
49
50
|
|
|
50
51
|
|
|
@@ -155,9 +156,10 @@ class ReportStore:
|
|
|
155
156
|
key: str,
|
|
156
157
|
granularity: str,
|
|
157
158
|
columns: Iterable[Field],
|
|
159
|
+
temporal_mode: Optional[TemporalMode] = None,
|
|
158
160
|
) -> TableReport:
|
|
159
161
|
"""Declare a typed Table report."""
|
|
160
|
-
return TableReport.declare(self, key, granularity, columns)
|
|
162
|
+
return TableReport.declare(self, key, granularity, columns, temporal_mode)
|
|
161
163
|
|
|
162
164
|
def metric(
|
|
163
165
|
self,
|
|
@@ -165,10 +167,11 @@ class ReportStore:
|
|
|
165
167
|
granularity: str,
|
|
166
168
|
metrics: Iterable[Measure],
|
|
167
169
|
range_transform: Optional[TimeRangeTransform] = None,
|
|
170
|
+
temporal_mode: Optional[TemporalMode] = None,
|
|
168
171
|
) -> MetricReport:
|
|
169
172
|
"""Declare a typed Metric report."""
|
|
170
173
|
return MetricReport.declare(
|
|
171
|
-
self, key, granularity, metrics, range_transform
|
|
174
|
+
self, key, granularity, metrics, range_transform, temporal_mode
|
|
172
175
|
)
|
|
173
176
|
|
|
174
177
|
def line_chart(
|
|
@@ -178,10 +181,11 @@ class ReportStore:
|
|
|
178
181
|
values: Iterable[Measure],
|
|
179
182
|
series: Optional[Dimension] = None,
|
|
180
183
|
range_transform: Optional[TimeRangeTransform] = None,
|
|
184
|
+
temporal_mode: Optional[TemporalMode] = None,
|
|
181
185
|
) -> LineChartReport:
|
|
182
186
|
"""Declare a typed Line Chart report."""
|
|
183
187
|
return LineChartReport.declare(
|
|
184
|
-
self, key, granularity, values, series, range_transform
|
|
188
|
+
self, key, granularity, values, series, range_transform, temporal_mode
|
|
185
189
|
)
|
|
186
190
|
|
|
187
191
|
def area_chart(
|
|
@@ -192,10 +196,18 @@ class ReportStore:
|
|
|
192
196
|
series: Optional[Dimension] = None,
|
|
193
197
|
stacked: bool = False,
|
|
194
198
|
range_transform: Optional[TimeRangeTransform] = None,
|
|
199
|
+
temporal_mode: Optional[TemporalMode] = None,
|
|
195
200
|
) -> AreaChartReport:
|
|
196
201
|
"""Declare a typed Area Chart report."""
|
|
197
202
|
return AreaChartReport.declare(
|
|
198
|
-
self,
|
|
203
|
+
self,
|
|
204
|
+
key,
|
|
205
|
+
granularity,
|
|
206
|
+
values,
|
|
207
|
+
series,
|
|
208
|
+
stacked,
|
|
209
|
+
range_transform,
|
|
210
|
+
temporal_mode,
|
|
199
211
|
)
|
|
200
212
|
|
|
201
213
|
def bar_chart(
|
|
@@ -208,6 +220,7 @@ class ReportStore:
|
|
|
208
220
|
orientation: str = "vertical",
|
|
209
221
|
stacked: bool = False,
|
|
210
222
|
range_transform: Optional[TimeRangeTransform] = None,
|
|
223
|
+
temporal_mode: Optional[TemporalMode] = None,
|
|
211
224
|
) -> BarChartReport:
|
|
212
225
|
"""Declare a typed Bar/Column Chart report."""
|
|
213
226
|
return BarChartReport.declare(
|
|
@@ -220,6 +233,7 @@ class ReportStore:
|
|
|
220
233
|
orientation,
|
|
221
234
|
stacked,
|
|
222
235
|
range_transform,
|
|
236
|
+
temporal_mode,
|
|
223
237
|
)
|
|
224
238
|
|
|
225
239
|
def pie_chart(
|
|
@@ -229,10 +243,11 @@ class ReportStore:
|
|
|
229
243
|
category: Dimension,
|
|
230
244
|
value: Measure,
|
|
231
245
|
range_transform: Optional[TimeRangeTransform] = None,
|
|
246
|
+
temporal_mode: Optional[TemporalMode] = None,
|
|
232
247
|
) -> PieChartReport:
|
|
233
248
|
"""Declare a typed Pie Chart report."""
|
|
234
249
|
return PieChartReport.declare(
|
|
235
|
-
self, key, granularity, category, value, range_transform
|
|
250
|
+
self, key, granularity, category, value, range_transform, temporal_mode
|
|
236
251
|
)
|
|
237
252
|
|
|
238
253
|
def donut_chart(
|
|
@@ -242,10 +257,11 @@ class ReportStore:
|
|
|
242
257
|
category: Dimension,
|
|
243
258
|
value: Measure,
|
|
244
259
|
range_transform: Optional[TimeRangeTransform] = None,
|
|
260
|
+
temporal_mode: Optional[TemporalMode] = None,
|
|
245
261
|
) -> DonutChartReport:
|
|
246
262
|
"""Declare a typed Donut Chart report."""
|
|
247
263
|
return DonutChartReport.declare(
|
|
248
|
-
self, key, granularity, category, value, range_transform
|
|
264
|
+
self, key, granularity, category, value, range_transform, temporal_mode
|
|
249
265
|
)
|
|
250
266
|
|
|
251
267
|
def radial_bar_chart(
|
|
@@ -256,10 +272,18 @@ class ReportStore:
|
|
|
256
272
|
min_value: float,
|
|
257
273
|
max_value: float,
|
|
258
274
|
range_transform: Optional[TimeRangeTransform] = None,
|
|
275
|
+
temporal_mode: Optional[TemporalMode] = None,
|
|
259
276
|
) -> RadialBarChartReport:
|
|
260
277
|
"""Declare a typed Radial Bar Chart report."""
|
|
261
278
|
return RadialBarChartReport.declare(
|
|
262
|
-
self,
|
|
279
|
+
self,
|
|
280
|
+
key,
|
|
281
|
+
granularity,
|
|
282
|
+
metrics,
|
|
283
|
+
min_value,
|
|
284
|
+
max_value,
|
|
285
|
+
range_transform,
|
|
286
|
+
temporal_mode,
|
|
263
287
|
)
|
|
264
288
|
|
|
265
289
|
def polar_area_chart(
|
|
@@ -269,10 +293,11 @@ class ReportStore:
|
|
|
269
293
|
category: Dimension,
|
|
270
294
|
value: Measure,
|
|
271
295
|
range_transform: Optional[TimeRangeTransform] = None,
|
|
296
|
+
temporal_mode: Optional[TemporalMode] = None,
|
|
272
297
|
) -> PolarAreaChartReport:
|
|
273
298
|
"""Declare a typed Polar Area Chart report."""
|
|
274
299
|
return PolarAreaChartReport.declare(
|
|
275
|
-
self, key, granularity, category, value, range_transform
|
|
300
|
+
self, key, granularity, category, value, range_transform, temporal_mode
|
|
276
301
|
)
|
|
277
302
|
|
|
278
303
|
def scatter_chart(
|
|
@@ -6,6 +6,7 @@ from pam.reports.definition import ReportDefinition
|
|
|
6
6
|
from pam.reports.errors import ReportStoreValidationError
|
|
7
7
|
from pam.reports.fields import Dimension, Field, Measure
|
|
8
8
|
from pam.reports.record import ReportRecord, build_record_key, validate_partition_key
|
|
9
|
+
from pam.reports.temporal import TemporalMode, normalize_temporal_mode
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
TABLE_RENDERER = "table"
|
|
@@ -40,6 +41,7 @@ class TableReport:
|
|
|
40
41
|
key: str,
|
|
41
42
|
granularity: str,
|
|
42
43
|
columns: Iterable[Field],
|
|
44
|
+
temporal_mode: Optional[TemporalMode] = None,
|
|
43
45
|
):
|
|
44
46
|
if isinstance(columns, (str, bytes)):
|
|
45
47
|
raise ReportStoreValidationError("columns must contain field declarations")
|
|
@@ -63,11 +65,19 @@ class TableReport:
|
|
|
63
65
|
"A Table report requires at least one Dimension column"
|
|
64
66
|
)
|
|
65
67
|
|
|
68
|
+
measures = tuple(
|
|
69
|
+
column for column in normalized_columns if isinstance(column, Measure)
|
|
70
|
+
)
|
|
71
|
+
normalized_temporal_mode = normalize_temporal_mode(
|
|
72
|
+
temporal_mode, granularity, measures, "Table"
|
|
73
|
+
)
|
|
74
|
+
|
|
66
75
|
definition = ReportDefinition.create(
|
|
67
76
|
report_type="table",
|
|
68
77
|
contract_version=TABLE_CONTRACT_VERSION,
|
|
69
78
|
fields=normalized_columns,
|
|
70
79
|
binding={"columns": [column.name for column in normalized_columns]},
|
|
80
|
+
temporal_mode=normalized_temporal_mode,
|
|
71
81
|
)
|
|
72
82
|
report = store._declare_typed_report(
|
|
73
83
|
key=key,
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Business meaning for records stored across report partitions."""
|
|
2
|
+
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from typing import Optional, Sequence
|
|
5
|
+
|
|
6
|
+
from pam.reports.axis import NUMERIC_TYPES
|
|
7
|
+
from pam.reports.errors import ReportStoreValidationError
|
|
8
|
+
from pam.reports.fields import Measure
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TemporalMode(str, Enum):
|
|
12
|
+
"""How CMS must interpret a report's values across partitions."""
|
|
13
|
+
|
|
14
|
+
AGGREGATE = "AGGREGATE"
|
|
15
|
+
SNAPSHOT = "SNAPSHOT"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def normalize_temporal_mode(
|
|
19
|
+
temporal_mode,
|
|
20
|
+
granularity: str,
|
|
21
|
+
measures: Sequence[Measure],
|
|
22
|
+
report_name: str,
|
|
23
|
+
range_transform=None,
|
|
24
|
+
) -> Optional[str]:
|
|
25
|
+
"""Validate one optional temporal declaration and serialize its value."""
|
|
26
|
+
if temporal_mode is None:
|
|
27
|
+
return None
|
|
28
|
+
if not isinstance(temporal_mode, TemporalMode):
|
|
29
|
+
raise ReportStoreValidationError(
|
|
30
|
+
f"{report_name} temporal_mode must be TemporalMode.AGGREGATE or "
|
|
31
|
+
"TemporalMode.SNAPSHOT"
|
|
32
|
+
)
|
|
33
|
+
if range_transform is not None:
|
|
34
|
+
raise ReportStoreValidationError(
|
|
35
|
+
f"{report_name} cannot declare both temporal_mode and range_transform"
|
|
36
|
+
)
|
|
37
|
+
if granularity not in {"DAY", "MONTH"}:
|
|
38
|
+
raise ReportStoreValidationError(
|
|
39
|
+
f"{report_name} temporal_mode requires DAY or MONTH granularity"
|
|
40
|
+
)
|
|
41
|
+
if not measures:
|
|
42
|
+
raise ReportStoreValidationError(
|
|
43
|
+
f"{report_name} temporal_mode requires at least one numeric measure"
|
|
44
|
+
)
|
|
45
|
+
non_numeric = sorted(
|
|
46
|
+
measure.name
|
|
47
|
+
for measure in measures
|
|
48
|
+
if measure.value_type not in NUMERIC_TYPES
|
|
49
|
+
)
|
|
50
|
+
if non_numeric:
|
|
51
|
+
raise ReportStoreValidationError(
|
|
52
|
+
f"{report_name} temporal_mode requires numeric measures; "
|
|
53
|
+
f"non-numeric measures: {non_numeric}"
|
|
54
|
+
)
|
|
55
|
+
return temporal_mode.value
|
|
@@ -503,10 +503,15 @@ name per report merely to separate reports. Never write `_tracker_name` yourself
|
|
|
503
503
|
the framework propagates the store's tracker name to every V2 pointer. Reopening the
|
|
504
504
|
same file in one service run with a different tracker name is an error.
|
|
505
505
|
|
|
506
|
-
The
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
506
|
+
The Dynamic Report is a reader of the latest successfully published store. Opening
|
|
507
|
+
or reloading a report must never force-start its Data Plugin: plugin execution is an
|
|
508
|
+
expensive dataflow operation owned by its scheduler or an explicit Data Adapter
|
|
509
|
+
operation. A V2 Dynamic Report may keep `adapter_id` as Data Plugin ownership and
|
|
510
|
+
tracker lookup metadata, together with the selected `adapter_data_attr`, matching
|
|
511
|
+
the original Data Plugin report flow. It must never use `adapter_id` for
|
|
512
|
+
report-triggered execution. Do not require a V2 pointer to move to the
|
|
513
|
+
`analytics-reports` database or to be selected through an Analytic Report Group.
|
|
514
|
+
A report reload only reads the latest published SQLite.
|
|
510
515
|
|
|
511
516
|
## Required lifecycle
|
|
512
517
|
|