truthound-dashboard 1.3.1__py3-none-any.whl → 1.4.1__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.
- truthound_dashboard/api/alerts.py +258 -0
- truthound_dashboard/api/anomaly.py +1302 -0
- truthound_dashboard/api/cross_alerts.py +352 -0
- truthound_dashboard/api/deps.py +143 -0
- truthound_dashboard/api/drift_monitor.py +540 -0
- truthound_dashboard/api/lineage.py +1151 -0
- truthound_dashboard/api/maintenance.py +363 -0
- truthound_dashboard/api/middleware.py +373 -1
- truthound_dashboard/api/model_monitoring.py +805 -0
- truthound_dashboard/api/notifications_advanced.py +2452 -0
- truthound_dashboard/api/plugins.py +2096 -0
- truthound_dashboard/api/profile.py +211 -14
- truthound_dashboard/api/reports.py +853 -0
- truthound_dashboard/api/router.py +147 -0
- truthound_dashboard/api/rule_suggestions.py +310 -0
- truthound_dashboard/api/schema_evolution.py +231 -0
- truthound_dashboard/api/sources.py +47 -3
- truthound_dashboard/api/triggers.py +190 -0
- truthound_dashboard/api/validations.py +13 -0
- truthound_dashboard/api/validators.py +333 -4
- truthound_dashboard/api/versioning.py +309 -0
- truthound_dashboard/api/websocket.py +301 -0
- truthound_dashboard/core/__init__.py +27 -0
- truthound_dashboard/core/anomaly.py +1395 -0
- truthound_dashboard/core/anomaly_explainer.py +633 -0
- truthound_dashboard/core/cache.py +206 -0
- truthound_dashboard/core/cached_services.py +422 -0
- truthound_dashboard/core/charts.py +352 -0
- truthound_dashboard/core/connections.py +1069 -42
- truthound_dashboard/core/cross_alerts.py +837 -0
- truthound_dashboard/core/drift_monitor.py +1477 -0
- truthound_dashboard/core/drift_sampling.py +669 -0
- truthound_dashboard/core/i18n/__init__.py +42 -0
- truthound_dashboard/core/i18n/detector.py +173 -0
- truthound_dashboard/core/i18n/messages.py +564 -0
- truthound_dashboard/core/lineage.py +971 -0
- truthound_dashboard/core/maintenance.py +443 -5
- truthound_dashboard/core/model_monitoring.py +1043 -0
- truthound_dashboard/core/notifications/channels.py +1020 -1
- truthound_dashboard/core/notifications/deduplication/__init__.py +143 -0
- truthound_dashboard/core/notifications/deduplication/policies.py +274 -0
- truthound_dashboard/core/notifications/deduplication/service.py +400 -0
- truthound_dashboard/core/notifications/deduplication/stores.py +2365 -0
- truthound_dashboard/core/notifications/deduplication/strategies.py +422 -0
- truthound_dashboard/core/notifications/dispatcher.py +43 -0
- truthound_dashboard/core/notifications/escalation/__init__.py +149 -0
- truthound_dashboard/core/notifications/escalation/backends.py +1384 -0
- truthound_dashboard/core/notifications/escalation/engine.py +429 -0
- truthound_dashboard/core/notifications/escalation/models.py +336 -0
- truthound_dashboard/core/notifications/escalation/scheduler.py +1187 -0
- truthound_dashboard/core/notifications/escalation/state_machine.py +330 -0
- truthound_dashboard/core/notifications/escalation/stores.py +2896 -0
- truthound_dashboard/core/notifications/events.py +49 -0
- truthound_dashboard/core/notifications/metrics/__init__.py +115 -0
- truthound_dashboard/core/notifications/metrics/base.py +528 -0
- truthound_dashboard/core/notifications/metrics/collectors.py +583 -0
- truthound_dashboard/core/notifications/routing/__init__.py +169 -0
- truthound_dashboard/core/notifications/routing/combinators.py +184 -0
- truthound_dashboard/core/notifications/routing/config.py +375 -0
- truthound_dashboard/core/notifications/routing/config_parser.py +867 -0
- truthound_dashboard/core/notifications/routing/engine.py +382 -0
- truthound_dashboard/core/notifications/routing/expression_engine.py +1269 -0
- truthound_dashboard/core/notifications/routing/jinja2_engine.py +774 -0
- truthound_dashboard/core/notifications/routing/rules.py +625 -0
- truthound_dashboard/core/notifications/routing/validator.py +678 -0
- truthound_dashboard/core/notifications/service.py +2 -0
- truthound_dashboard/core/notifications/stats_aggregator.py +850 -0
- truthound_dashboard/core/notifications/throttling/__init__.py +83 -0
- truthound_dashboard/core/notifications/throttling/builder.py +311 -0
- truthound_dashboard/core/notifications/throttling/stores.py +1859 -0
- truthound_dashboard/core/notifications/throttling/throttlers.py +633 -0
- truthound_dashboard/core/openlineage.py +1028 -0
- truthound_dashboard/core/plugins/__init__.py +39 -0
- truthound_dashboard/core/plugins/docs/__init__.py +39 -0
- truthound_dashboard/core/plugins/docs/extractor.py +703 -0
- truthound_dashboard/core/plugins/docs/renderers.py +804 -0
- truthound_dashboard/core/plugins/hooks/__init__.py +63 -0
- truthound_dashboard/core/plugins/hooks/decorators.py +367 -0
- truthound_dashboard/core/plugins/hooks/manager.py +403 -0
- truthound_dashboard/core/plugins/hooks/protocols.py +265 -0
- truthound_dashboard/core/plugins/lifecycle/__init__.py +41 -0
- truthound_dashboard/core/plugins/lifecycle/hot_reload.py +584 -0
- truthound_dashboard/core/plugins/lifecycle/machine.py +419 -0
- truthound_dashboard/core/plugins/lifecycle/states.py +266 -0
- truthound_dashboard/core/plugins/loader.py +504 -0
- truthound_dashboard/core/plugins/registry.py +810 -0
- truthound_dashboard/core/plugins/reporter_executor.py +588 -0
- truthound_dashboard/core/plugins/sandbox/__init__.py +59 -0
- truthound_dashboard/core/plugins/sandbox/code_validator.py +243 -0
- truthound_dashboard/core/plugins/sandbox/engines.py +770 -0
- truthound_dashboard/core/plugins/sandbox/protocols.py +194 -0
- truthound_dashboard/core/plugins/sandbox.py +617 -0
- truthound_dashboard/core/plugins/security/__init__.py +68 -0
- truthound_dashboard/core/plugins/security/analyzer.py +535 -0
- truthound_dashboard/core/plugins/security/policies.py +311 -0
- truthound_dashboard/core/plugins/security/protocols.py +296 -0
- truthound_dashboard/core/plugins/security/signing.py +842 -0
- truthound_dashboard/core/plugins/security.py +446 -0
- truthound_dashboard/core/plugins/validator_executor.py +401 -0
- truthound_dashboard/core/plugins/versioning/__init__.py +51 -0
- truthound_dashboard/core/plugins/versioning/constraints.py +377 -0
- truthound_dashboard/core/plugins/versioning/dependencies.py +541 -0
- truthound_dashboard/core/plugins/versioning/semver.py +266 -0
- truthound_dashboard/core/profile_comparison.py +601 -0
- truthound_dashboard/core/report_history.py +570 -0
- truthound_dashboard/core/reporters/__init__.py +57 -0
- truthound_dashboard/core/reporters/base.py +296 -0
- truthound_dashboard/core/reporters/csv_reporter.py +155 -0
- truthound_dashboard/core/reporters/html_reporter.py +598 -0
- truthound_dashboard/core/reporters/i18n/__init__.py +65 -0
- truthound_dashboard/core/reporters/i18n/base.py +494 -0
- truthound_dashboard/core/reporters/i18n/catalogs.py +930 -0
- truthound_dashboard/core/reporters/json_reporter.py +160 -0
- truthound_dashboard/core/reporters/junit_reporter.py +233 -0
- truthound_dashboard/core/reporters/markdown_reporter.py +207 -0
- truthound_dashboard/core/reporters/pdf_reporter.py +209 -0
- truthound_dashboard/core/reporters/registry.py +272 -0
- truthound_dashboard/core/rule_generator.py +2088 -0
- truthound_dashboard/core/scheduler.py +822 -12
- truthound_dashboard/core/schema_evolution.py +858 -0
- truthound_dashboard/core/services.py +152 -9
- truthound_dashboard/core/statistics.py +718 -0
- truthound_dashboard/core/streaming_anomaly.py +883 -0
- truthound_dashboard/core/triggers/__init__.py +45 -0
- truthound_dashboard/core/triggers/base.py +226 -0
- truthound_dashboard/core/triggers/evaluators.py +609 -0
- truthound_dashboard/core/triggers/factory.py +363 -0
- truthound_dashboard/core/unified_alerts.py +870 -0
- truthound_dashboard/core/validation_limits.py +509 -0
- truthound_dashboard/core/versioning.py +709 -0
- truthound_dashboard/core/websocket/__init__.py +59 -0
- truthound_dashboard/core/websocket/manager.py +512 -0
- truthound_dashboard/core/websocket/messages.py +130 -0
- truthound_dashboard/db/__init__.py +30 -0
- truthound_dashboard/db/models.py +3375 -3
- truthound_dashboard/main.py +22 -0
- truthound_dashboard/schemas/__init__.py +396 -1
- truthound_dashboard/schemas/anomaly.py +1258 -0
- truthound_dashboard/schemas/base.py +4 -0
- truthound_dashboard/schemas/cross_alerts.py +334 -0
- truthound_dashboard/schemas/drift_monitor.py +890 -0
- truthound_dashboard/schemas/lineage.py +428 -0
- truthound_dashboard/schemas/maintenance.py +154 -0
- truthound_dashboard/schemas/model_monitoring.py +374 -0
- truthound_dashboard/schemas/notifications_advanced.py +1363 -0
- truthound_dashboard/schemas/openlineage.py +704 -0
- truthound_dashboard/schemas/plugins.py +1293 -0
- truthound_dashboard/schemas/profile.py +420 -34
- truthound_dashboard/schemas/profile_comparison.py +242 -0
- truthound_dashboard/schemas/reports.py +285 -0
- truthound_dashboard/schemas/rule_suggestion.py +434 -0
- truthound_dashboard/schemas/schema_evolution.py +164 -0
- truthound_dashboard/schemas/source.py +117 -2
- truthound_dashboard/schemas/triggers.py +511 -0
- truthound_dashboard/schemas/unified_alerts.py +223 -0
- truthound_dashboard/schemas/validation.py +25 -1
- truthound_dashboard/schemas/validators/__init__.py +11 -0
- truthound_dashboard/schemas/validators/base.py +151 -0
- truthound_dashboard/schemas/versioning.py +152 -0
- truthound_dashboard/static/index.html +2 -2
- {truthound_dashboard-1.3.1.dist-info → truthound_dashboard-1.4.1.dist-info}/METADATA +147 -23
- truthound_dashboard-1.4.1.dist-info/RECORD +239 -0
- truthound_dashboard/static/assets/index-BZG20KuF.js +0 -586
- truthound_dashboard/static/assets/index-D_HyZ3pb.css +0 -1
- truthound_dashboard/static/assets/unmerged_dictionaries-CtpqQBm0.js +0 -1
- truthound_dashboard-1.3.1.dist-info/RECORD +0 -110
- {truthound_dashboard-1.3.1.dist-info → truthound_dashboard-1.4.1.dist-info}/WHEEL +0 -0
- {truthound_dashboard-1.3.1.dist-info → truthound_dashboard-1.4.1.dist-info}/entry_points.txt +0 -0
- {truthound_dashboard-1.3.1.dist-info → truthound_dashboard-1.4.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"""Profile comparison Pydantic schemas.
|
|
2
|
+
|
|
3
|
+
This module defines schemas for profile comparison,
|
|
4
|
+
including time-series trends and version-to-version comparison.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
from enum import Enum
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from pydantic import Field
|
|
14
|
+
|
|
15
|
+
from .base import BaseSchema
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TrendDirection(str, Enum):
|
|
19
|
+
"""Direction of metric trend."""
|
|
20
|
+
|
|
21
|
+
UP = "up"
|
|
22
|
+
DOWN = "down"
|
|
23
|
+
STABLE = "stable"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# =============================================================================
|
|
27
|
+
# Profile Summary Schemas
|
|
28
|
+
# =============================================================================
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ProfileSummary(BaseSchema):
|
|
32
|
+
"""Summary of a profile for listing."""
|
|
33
|
+
|
|
34
|
+
id: str = Field(..., description="Profile ID")
|
|
35
|
+
source_id: str = Field(..., description="Source ID")
|
|
36
|
+
row_count: int = Field(..., description="Number of rows")
|
|
37
|
+
column_count: int = Field(..., description="Number of columns")
|
|
38
|
+
size_bytes: int = Field(default=0, description="Data size in bytes")
|
|
39
|
+
created_at: datetime = Field(..., description="When profile was created")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class ProfileListResponse(BaseSchema):
|
|
43
|
+
"""List response for profiles."""
|
|
44
|
+
|
|
45
|
+
profiles: list[ProfileSummary] = Field(
|
|
46
|
+
default_factory=list, description="List of profiles"
|
|
47
|
+
)
|
|
48
|
+
total: int = Field(default=0, description="Total count")
|
|
49
|
+
source_id: str = Field(..., description="Source ID")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# =============================================================================
|
|
53
|
+
# Column Comparison Schemas
|
|
54
|
+
# =============================================================================
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class StatisticalTestInfo(BaseSchema):
|
|
58
|
+
"""Information about statistical significance test results."""
|
|
59
|
+
|
|
60
|
+
test_name: str = Field(..., description="Name of the statistical test used")
|
|
61
|
+
p_value: float = Field(..., description="P-value from the test")
|
|
62
|
+
is_significant: bool = Field(..., description="Whether result is significant at alpha=0.05")
|
|
63
|
+
effect_size: float | None = Field(default=None, description="Effect size (Cohen's d or r)")
|
|
64
|
+
interpretation: str = Field(default="", description="Human-readable interpretation")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class ColumnComparison(BaseSchema):
|
|
68
|
+
"""Comparison result for a single column metric."""
|
|
69
|
+
|
|
70
|
+
column: str = Field(..., description="Column name")
|
|
71
|
+
metric: str = Field(..., description="Metric name (e.g., null_pct, unique_pct)")
|
|
72
|
+
baseline_value: Any = Field(..., description="Value in baseline profile")
|
|
73
|
+
current_value: Any = Field(..., description="Value in current profile")
|
|
74
|
+
change: float | None = Field(
|
|
75
|
+
default=None, description="Absolute change in value"
|
|
76
|
+
)
|
|
77
|
+
change_pct: float | None = Field(
|
|
78
|
+
default=None, description="Percentage change"
|
|
79
|
+
)
|
|
80
|
+
is_significant: bool = Field(
|
|
81
|
+
default=False, description="Whether change is statistically significant"
|
|
82
|
+
)
|
|
83
|
+
trend: TrendDirection = Field(
|
|
84
|
+
default=TrendDirection.STABLE, description="Direction of change"
|
|
85
|
+
)
|
|
86
|
+
statistical_test: dict[str, Any] | None = Field(
|
|
87
|
+
default=None,
|
|
88
|
+
description="Statistical test results (test_name, p_value, effect_size, interpretation)"
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class ColumnComparisonSummary(BaseSchema):
|
|
93
|
+
"""Summary of changes for a single column."""
|
|
94
|
+
|
|
95
|
+
column: str = Field(..., description="Column name")
|
|
96
|
+
metrics_changed: int = Field(default=0, description="Number of metrics changed")
|
|
97
|
+
significant_changes: int = Field(
|
|
98
|
+
default=0, description="Number of significant changes"
|
|
99
|
+
)
|
|
100
|
+
comparisons: list[ColumnComparison] = Field(
|
|
101
|
+
default_factory=list, description="Individual metric comparisons"
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
# =============================================================================
|
|
106
|
+
# Profile Comparison Schemas
|
|
107
|
+
# =============================================================================
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class ProfileComparisonRequest(BaseSchema):
|
|
111
|
+
"""Request to compare two profiles."""
|
|
112
|
+
|
|
113
|
+
baseline_profile_id: str = Field(..., description="Baseline profile ID")
|
|
114
|
+
current_profile_id: str = Field(..., description="Current profile ID")
|
|
115
|
+
significance_threshold: float = Field(
|
|
116
|
+
default=0.1,
|
|
117
|
+
ge=0.0,
|
|
118
|
+
le=1.0,
|
|
119
|
+
description="Threshold for significant change detection",
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class ProfileComparisonResponse(BaseSchema):
|
|
124
|
+
"""Response containing profile comparison results."""
|
|
125
|
+
|
|
126
|
+
source_id: str = Field(..., description="Source ID")
|
|
127
|
+
source_name: str = Field(..., description="Source name")
|
|
128
|
+
baseline_profile_id: str = Field(..., description="Baseline profile ID")
|
|
129
|
+
current_profile_id: str = Field(..., description="Current profile ID")
|
|
130
|
+
baseline_timestamp: datetime = Field(..., description="Baseline profile timestamp")
|
|
131
|
+
current_timestamp: datetime = Field(..., description="Current profile timestamp")
|
|
132
|
+
row_count_change: int = Field(default=0, description="Change in row count")
|
|
133
|
+
row_count_change_pct: float = Field(
|
|
134
|
+
default=0.0, description="Percentage change in row count"
|
|
135
|
+
)
|
|
136
|
+
column_count_change: int = Field(default=0, description="Change in column count")
|
|
137
|
+
column_comparisons: list[ColumnComparison] = Field(
|
|
138
|
+
default_factory=list, description="Per-column metric comparisons"
|
|
139
|
+
)
|
|
140
|
+
significant_changes: int = Field(
|
|
141
|
+
default=0, description="Number of significant changes"
|
|
142
|
+
)
|
|
143
|
+
summary: dict[str, Any] = Field(
|
|
144
|
+
default_factory=dict, description="Summary statistics"
|
|
145
|
+
)
|
|
146
|
+
compared_at: datetime = Field(..., description="When comparison was performed")
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
# =============================================================================
|
|
150
|
+
# Profile Trend Schemas
|
|
151
|
+
# =============================================================================
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class ProfileTrendPoint(BaseSchema):
|
|
155
|
+
"""A single point in profile trend data."""
|
|
156
|
+
|
|
157
|
+
timestamp: datetime = Field(..., description="Profile timestamp")
|
|
158
|
+
profile_id: str = Field(..., description="Profile ID")
|
|
159
|
+
row_count: int = Field(..., description="Row count at this point")
|
|
160
|
+
column_count: int = Field(default=0, description="Column count")
|
|
161
|
+
avg_null_pct: float = Field(default=0.0, description="Average null percentage")
|
|
162
|
+
avg_unique_pct: float = Field(default=0.0, description="Average unique percentage")
|
|
163
|
+
size_bytes: int = Field(default=0, description="Data size in bytes")
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class ColumnTrend(BaseSchema):
|
|
167
|
+
"""Trend data for a specific column metric."""
|
|
168
|
+
|
|
169
|
+
column: str = Field(..., description="Column name")
|
|
170
|
+
metric: str = Field(..., description="Metric name")
|
|
171
|
+
values: list[tuple[datetime, float]] = Field(
|
|
172
|
+
default_factory=list, description="Time-value pairs"
|
|
173
|
+
)
|
|
174
|
+
trend_direction: TrendDirection = Field(
|
|
175
|
+
default=TrendDirection.STABLE, description="Overall trend direction"
|
|
176
|
+
)
|
|
177
|
+
change_pct: float = Field(
|
|
178
|
+
default=0.0, description="Overall percentage change"
|
|
179
|
+
)
|
|
180
|
+
min_value: float | None = Field(default=None, description="Minimum value in period")
|
|
181
|
+
max_value: float | None = Field(default=None, description="Maximum value in period")
|
|
182
|
+
avg_value: float | None = Field(default=None, description="Average value in period")
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class ProfileTrendRequest(BaseSchema):
|
|
186
|
+
"""Request for profile trend data."""
|
|
187
|
+
|
|
188
|
+
period: str = Field(
|
|
189
|
+
default="30d",
|
|
190
|
+
description="Time period (e.g., 7d, 30d, 90d)",
|
|
191
|
+
)
|
|
192
|
+
granularity: str = Field(
|
|
193
|
+
default="daily",
|
|
194
|
+
description="Data granularity (hourly, daily, weekly)",
|
|
195
|
+
)
|
|
196
|
+
columns: list[str] | None = Field(
|
|
197
|
+
default=None,
|
|
198
|
+
description="Specific columns to include in trends",
|
|
199
|
+
)
|
|
200
|
+
metrics: list[str] | None = Field(
|
|
201
|
+
default=None,
|
|
202
|
+
description="Specific metrics to include (null_pct, unique_pct, etc.)",
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
class ProfileTrendResponse(BaseSchema):
|
|
207
|
+
"""Response containing profile trend data."""
|
|
208
|
+
|
|
209
|
+
source_id: str = Field(..., description="Source ID")
|
|
210
|
+
source_name: str = Field(..., description="Source name")
|
|
211
|
+
period: str = Field(..., description="Time period analyzed")
|
|
212
|
+
granularity: str = Field(..., description="Data granularity")
|
|
213
|
+
data_points: list[ProfileTrendPoint] = Field(
|
|
214
|
+
default_factory=list, description="Overall trend points"
|
|
215
|
+
)
|
|
216
|
+
column_trends: list[ColumnTrend] = Field(
|
|
217
|
+
default_factory=list, description="Per-column trends"
|
|
218
|
+
)
|
|
219
|
+
total_profiles: int = Field(default=0, description="Total profiles in period")
|
|
220
|
+
row_count_trend: TrendDirection = Field(
|
|
221
|
+
default=TrendDirection.STABLE, description="Overall row count trend"
|
|
222
|
+
)
|
|
223
|
+
summary: dict[str, Any] = Field(
|
|
224
|
+
default_factory=dict, description="Summary statistics"
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
# =============================================================================
|
|
229
|
+
# Latest Comparison Shortcut
|
|
230
|
+
# =============================================================================
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
class LatestComparisonResponse(BaseSchema):
|
|
234
|
+
"""Response for comparing latest profile with previous."""
|
|
235
|
+
|
|
236
|
+
source_id: str = Field(..., description="Source ID")
|
|
237
|
+
has_previous: bool = Field(
|
|
238
|
+
..., description="Whether a previous profile exists"
|
|
239
|
+
)
|
|
240
|
+
comparison: ProfileComparisonResponse | None = Field(
|
|
241
|
+
default=None, description="Comparison result (if previous exists)"
|
|
242
|
+
)
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
"""Report-related Pydantic schemas.
|
|
2
|
+
|
|
3
|
+
This module defines schemas for report generation API operations.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
from enum import Enum
|
|
10
|
+
from typing import Any, Literal
|
|
11
|
+
|
|
12
|
+
from pydantic import Field
|
|
13
|
+
|
|
14
|
+
from .base import BaseSchema, IDMixin, ListResponseWrapper, TimestampMixin
|
|
15
|
+
|
|
16
|
+
# Report format types
|
|
17
|
+
ReportFormatType = Literal["html", "csv", "json", "markdown", "pdf", "junit", "excel", "custom"]
|
|
18
|
+
|
|
19
|
+
# Report theme types
|
|
20
|
+
ReportThemeType = Literal["light", "dark", "professional", "minimal", "high_contrast"]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ReportStatus(str, Enum):
|
|
24
|
+
"""Status of report generation."""
|
|
25
|
+
|
|
26
|
+
PENDING = "pending"
|
|
27
|
+
GENERATING = "generating"
|
|
28
|
+
COMPLETED = "completed"
|
|
29
|
+
FAILED = "failed"
|
|
30
|
+
EXPIRED = "expired"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class ReportGenerateRequest(BaseSchema):
|
|
34
|
+
"""Request to generate a validation report.
|
|
35
|
+
|
|
36
|
+
All parameters are optional with sensible defaults.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
format: ReportFormatType = Field(
|
|
40
|
+
default="html",
|
|
41
|
+
description="Output format for the report",
|
|
42
|
+
)
|
|
43
|
+
theme: ReportThemeType = Field(
|
|
44
|
+
default="professional",
|
|
45
|
+
description="Visual theme for the report (mainly for HTML)",
|
|
46
|
+
)
|
|
47
|
+
title: str | None = Field(
|
|
48
|
+
default=None,
|
|
49
|
+
description="Custom title for the report. Auto-generated if not provided.",
|
|
50
|
+
)
|
|
51
|
+
include_samples: bool = Field(
|
|
52
|
+
default=True,
|
|
53
|
+
description="Include sample problematic values in the report",
|
|
54
|
+
)
|
|
55
|
+
include_statistics: bool = Field(
|
|
56
|
+
default=True,
|
|
57
|
+
description="Include data statistics section",
|
|
58
|
+
)
|
|
59
|
+
custom_metadata: dict[str, Any] | None = Field(
|
|
60
|
+
default=None,
|
|
61
|
+
description="Additional metadata to include in the report",
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class ReportMetadataResponse(BaseSchema):
|
|
66
|
+
"""Report metadata returned in API responses."""
|
|
67
|
+
|
|
68
|
+
title: str = Field(..., description="Report title")
|
|
69
|
+
generated_at: datetime = Field(..., description="Generation timestamp")
|
|
70
|
+
source_name: str | None = Field(default=None, description="Data source name")
|
|
71
|
+
source_id: str | None = Field(default=None, description="Data source ID")
|
|
72
|
+
validation_id: str | None = Field(default=None, description="Validation ID")
|
|
73
|
+
theme: str = Field(..., description="Visual theme used")
|
|
74
|
+
format: str = Field(..., description="Output format")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class ReportResponse(BaseSchema):
|
|
78
|
+
"""Response for report generation.
|
|
79
|
+
|
|
80
|
+
Contains metadata about the generated report and download info.
|
|
81
|
+
Note: The actual content is returned as a file download, not in this response.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
filename: str = Field(..., description="Suggested filename for download")
|
|
85
|
+
content_type: str = Field(..., description="MIME content type")
|
|
86
|
+
size_bytes: int = Field(..., description="Size of report content in bytes")
|
|
87
|
+
generation_time_ms: int = Field(
|
|
88
|
+
..., description="Time taken to generate in milliseconds"
|
|
89
|
+
)
|
|
90
|
+
metadata: ReportMetadataResponse = Field(..., description="Report metadata")
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class AvailableFormatsResponse(BaseSchema):
|
|
94
|
+
"""Response listing available report formats."""
|
|
95
|
+
|
|
96
|
+
formats: list[str] = Field(
|
|
97
|
+
...,
|
|
98
|
+
description="List of available format names",
|
|
99
|
+
examples=[["html", "csv", "json", "markdown"]],
|
|
100
|
+
)
|
|
101
|
+
themes: list[str] = Field(
|
|
102
|
+
...,
|
|
103
|
+
description="List of available theme names",
|
|
104
|
+
examples=[["light", "dark", "professional", "minimal", "high_contrast"]],
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
# =============================================================================
|
|
109
|
+
# Report History Schemas
|
|
110
|
+
# =============================================================================
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class GeneratedReportBase(BaseSchema):
|
|
114
|
+
"""Base schema for generated report."""
|
|
115
|
+
|
|
116
|
+
name: str = Field(max_length=255, description="Report name")
|
|
117
|
+
description: str | None = Field(default=None, description="Report description")
|
|
118
|
+
format: ReportFormatType = Field(default="html", description="Output format")
|
|
119
|
+
theme: ReportThemeType | None = Field(default=None, description="Visual theme")
|
|
120
|
+
locale: str = Field(default="en", max_length=10, description="Language locale")
|
|
121
|
+
config: dict[str, Any] | None = Field(default=None, description="Generation config")
|
|
122
|
+
metadata: dict[str, Any] | None = Field(default=None, description="Additional metadata")
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class GeneratedReportCreate(GeneratedReportBase):
|
|
126
|
+
"""Schema for creating a generated report record."""
|
|
127
|
+
|
|
128
|
+
validation_id: str | None = Field(default=None, description="Associated validation ID")
|
|
129
|
+
source_id: str | None = Field(default=None, description="Associated source ID")
|
|
130
|
+
reporter_id: str | None = Field(default=None, description="Custom reporter ID if used")
|
|
131
|
+
expires_in_days: int | None = Field(
|
|
132
|
+
default=30,
|
|
133
|
+
ge=1,
|
|
134
|
+
le=365,
|
|
135
|
+
description="Days until report expires (1-365)",
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class GeneratedReportUpdate(BaseSchema):
|
|
140
|
+
"""Schema for updating a generated report record."""
|
|
141
|
+
|
|
142
|
+
name: str | None = Field(default=None, max_length=255)
|
|
143
|
+
description: str | None = None
|
|
144
|
+
metadata: dict[str, Any] | None = None
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class GeneratedReportResponse(GeneratedReportBase, IDMixin, TimestampMixin):
|
|
148
|
+
"""Response schema for generated report."""
|
|
149
|
+
|
|
150
|
+
validation_id: str | None = None
|
|
151
|
+
source_id: str | None = None
|
|
152
|
+
reporter_id: str | None = None
|
|
153
|
+
status: ReportStatus = ReportStatus.PENDING
|
|
154
|
+
file_path: str | None = None
|
|
155
|
+
file_size: int | None = None
|
|
156
|
+
content_hash: str | None = None
|
|
157
|
+
error_message: str | None = None
|
|
158
|
+
generation_time_ms: float | None = None
|
|
159
|
+
expires_at: datetime | None = None
|
|
160
|
+
downloaded_count: int = 0
|
|
161
|
+
last_downloaded_at: datetime | None = None
|
|
162
|
+
|
|
163
|
+
# Enriched fields (populated in API)
|
|
164
|
+
source_name: str | None = Field(default=None, description="Source name for display")
|
|
165
|
+
reporter_name: str | None = Field(default=None, description="Reporter name for display")
|
|
166
|
+
download_url: str | None = Field(default=None, description="URL to download the report")
|
|
167
|
+
|
|
168
|
+
@classmethod
|
|
169
|
+
def from_model(cls, model: Any) -> "GeneratedReportResponse":
|
|
170
|
+
"""Create response from database model."""
|
|
171
|
+
return cls(
|
|
172
|
+
id=str(model.id),
|
|
173
|
+
name=model.name,
|
|
174
|
+
description=model.description,
|
|
175
|
+
format=model.format.value if hasattr(model.format, "value") else model.format,
|
|
176
|
+
theme=model.theme,
|
|
177
|
+
locale=model.locale,
|
|
178
|
+
config=model.config,
|
|
179
|
+
metadata=model.metadata,
|
|
180
|
+
validation_id=str(model.validation_id) if model.validation_id else None,
|
|
181
|
+
source_id=str(model.source_id) if model.source_id else None,
|
|
182
|
+
reporter_id=str(model.reporter_id) if model.reporter_id else None,
|
|
183
|
+
status=ReportStatus(model.status.value if hasattr(model.status, "value") else model.status),
|
|
184
|
+
file_path=model.file_path,
|
|
185
|
+
file_size=model.file_size,
|
|
186
|
+
content_hash=model.content_hash,
|
|
187
|
+
error_message=model.error_message,
|
|
188
|
+
generation_time_ms=model.generation_time_ms,
|
|
189
|
+
expires_at=model.expires_at,
|
|
190
|
+
downloaded_count=model.downloaded_count,
|
|
191
|
+
last_downloaded_at=model.last_downloaded_at,
|
|
192
|
+
created_at=model.created_at,
|
|
193
|
+
updated_at=model.updated_at,
|
|
194
|
+
source_name=model.source.name if model.source else None,
|
|
195
|
+
reporter_name=model.reporter.display_name if model.reporter else None,
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
class GeneratedReportListResponse(ListResponseWrapper[GeneratedReportResponse]):
|
|
200
|
+
"""Paginated list response for generated reports."""
|
|
201
|
+
|
|
202
|
+
pass
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
class BulkReportGenerateRequest(BaseSchema):
|
|
206
|
+
"""Request to generate reports for multiple validations."""
|
|
207
|
+
|
|
208
|
+
validation_ids: list[str] = Field(
|
|
209
|
+
...,
|
|
210
|
+
min_length=1,
|
|
211
|
+
max_length=100,
|
|
212
|
+
description="List of validation IDs (1-100)",
|
|
213
|
+
)
|
|
214
|
+
format: ReportFormatType = Field(default="html", description="Output format")
|
|
215
|
+
theme: ReportThemeType = Field(default="professional", description="Visual theme")
|
|
216
|
+
locale: str = Field(default="en", max_length=10, description="Language locale")
|
|
217
|
+
reporter_id: str | None = Field(default=None, description="Custom reporter ID")
|
|
218
|
+
config: dict[str, Any] | None = Field(default=None, description="Reporter config")
|
|
219
|
+
save_to_history: bool = Field(
|
|
220
|
+
default=True,
|
|
221
|
+
description="Save reports to history",
|
|
222
|
+
)
|
|
223
|
+
expires_in_days: int | None = Field(
|
|
224
|
+
default=30,
|
|
225
|
+
ge=1,
|
|
226
|
+
le=365,
|
|
227
|
+
description="Days until reports expire",
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
class BulkReportGenerateResponse(BaseSchema):
|
|
232
|
+
"""Response for bulk report generation."""
|
|
233
|
+
|
|
234
|
+
total: int = Field(..., description="Total reports requested")
|
|
235
|
+
successful: int = Field(..., description="Successfully generated count")
|
|
236
|
+
failed: int = Field(..., description="Failed generation count")
|
|
237
|
+
reports: list[GeneratedReportResponse] = Field(
|
|
238
|
+
default_factory=list,
|
|
239
|
+
description="Generated report records",
|
|
240
|
+
)
|
|
241
|
+
errors: list[dict[str, Any]] = Field(
|
|
242
|
+
default_factory=list,
|
|
243
|
+
description="Error details for failed reports",
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
class ReportHistoryQuery(BaseSchema):
|
|
248
|
+
"""Query parameters for report history."""
|
|
249
|
+
|
|
250
|
+
source_id: str | None = Field(default=None, description="Filter by source ID")
|
|
251
|
+
validation_id: str | None = Field(default=None, description="Filter by validation ID")
|
|
252
|
+
reporter_id: str | None = Field(default=None, description="Filter by reporter ID")
|
|
253
|
+
format: ReportFormatType | None = Field(default=None, description="Filter by format")
|
|
254
|
+
status: ReportStatus | None = Field(default=None, description="Filter by status")
|
|
255
|
+
include_expired: bool = Field(
|
|
256
|
+
default=False,
|
|
257
|
+
description="Include expired reports",
|
|
258
|
+
)
|
|
259
|
+
search: str | None = Field(default=None, description="Search by name")
|
|
260
|
+
sort_by: str = Field(default="created_at", description="Sort field")
|
|
261
|
+
sort_order: Literal["asc", "desc"] = Field(default="desc", description="Sort order")
|
|
262
|
+
page: int = Field(default=1, ge=1, description="Page number")
|
|
263
|
+
page_size: int = Field(default=20, ge=1, le=100, description="Items per page")
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
class ReportStatistics(BaseSchema):
|
|
267
|
+
"""Statistics for generated reports."""
|
|
268
|
+
|
|
269
|
+
total_reports: int = Field(..., description="Total reports generated")
|
|
270
|
+
total_size_bytes: int = Field(..., description="Total storage used")
|
|
271
|
+
reports_by_format: dict[str, int] = Field(
|
|
272
|
+
default_factory=dict,
|
|
273
|
+
description="Count by format",
|
|
274
|
+
)
|
|
275
|
+
reports_by_status: dict[str, int] = Field(
|
|
276
|
+
default_factory=dict,
|
|
277
|
+
description="Count by status",
|
|
278
|
+
)
|
|
279
|
+
total_downloads: int = Field(..., description="Total download count")
|
|
280
|
+
avg_generation_time_ms: float | None = Field(
|
|
281
|
+
default=None,
|
|
282
|
+
description="Average generation time",
|
|
283
|
+
)
|
|
284
|
+
expired_count: int = Field(default=0, description="Expired reports count")
|
|
285
|
+
reporters_used: int = Field(default=0, description="Unique reporters used")
|