datasentry-core 0.8.2__tar.gz → 0.8.4__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.
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/.gitignore +6 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/PKG-INFO +1 -1
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/pyproject.toml +1 -1
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/connectors/csv.py +17 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/detectors/initial/missing.py +4 -2
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/detectors/initial/numeric.py +8 -5
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/detectors/missingness.py +4 -2
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/engine/__init__.py +2 -1
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/engine/base.py +12 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/engine/profiler.py +22 -7
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/models/scan.py +1 -1
- datasentry_core-0.8.4/src/datasentry_core/repair/engine.py +852 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/reporting/i18n.py +16 -0
- datasentry_core-0.8.2/src/datasentry_core/repair/engine.py +0 -498
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/README.md +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/__init__.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/connectors/__init__.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/connectors/base.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/connectors/duckdb.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/connectors/errors.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/connectors/file_based.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/connectors/jsonl.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/connectors/mysql.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/connectors/parquet.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/connectors/postgres.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/connectors/registry.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/connectors/remote_file.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/connectors/sampling.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/connectors/spec.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/connectors/sqlite.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/connectors/xlsx.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/contracts/__init__.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/contracts/exporters.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/detectors/__init__.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/detectors/base.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/detectors/common.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/detectors/cross_field.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/detectors/cross_table.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/detectors/datetime.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/detectors/initial/__init__.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/detectors/initial/anomaly_ml.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/detectors/initial/categorical.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/detectors/initial/formula.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/detectors/initial/textual.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/detectors/initial/uniqueness.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/detectors/runner.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/detectors/safe_eval.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/detectors/textual_variants.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/drift/__init__.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/drift/engine.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/engine/duckdb.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/engine/fusion.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/engine/sql_guard.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/llm/provider.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/models/__init__.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/models/audit.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/models/contract.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/models/detector.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/models/drift.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/models/enums.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/models/evidence.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/models/feedback.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/models/fingerprint.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/models/issue.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/models/llm.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/models/profile.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/models/quality.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/models/repair.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/models/rules.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/models/validation.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/plugin_fixtures.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/plugin_locks.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/plugins.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/privacy/redactor.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/repair/__init__.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/reporting/__init__.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/reporting/column_profiles.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/reporting/evidence_desc.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/reporting/html.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/reporting/interactive.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/reporting/junit.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/reporting/markdown.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/reporting/sarif.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/reporting/suggestions.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/reporting/translate.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/rules/engine.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/scoring/__init__.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/scoring/engine.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/scoring/gate.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/scoring/quality.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/scoring/weights.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/secrets.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/storage/__init__.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/storage/paths.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/storage/schema.py +0 -0
- {datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/storage/store.py +0 -0
|
@@ -79,6 +79,23 @@ def _sniff_delimiter(path: Path, encoding: str) -> str:
|
|
|
79
79
|
return ","
|
|
80
80
|
|
|
81
81
|
|
|
82
|
+
def sniff_profile(path: Path) -> tuple[str, str]:
|
|
83
|
+
"""The (encoding, delimiter) this connector would use for `path`.
|
|
84
|
+
|
|
85
|
+
Public for exactly one reason: the repair auditor reads the artefacts, and reading them under
|
|
86
|
+
a different encoding than the bytes are in is not a smaller lie than reading them under a
|
|
87
|
+
different delimiter. A cp1250 snapshot raised `invalid UTF8 data` and the repair lost its audit
|
|
88
|
+
evidence while `apply` had already succeeded (F2).
|
|
89
|
+
"""
|
|
90
|
+
encoding = _detect_encoding(path)
|
|
91
|
+
return encoding, _sniff_delimiter(path, encoding)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def sniff_dialect(path: Path) -> str:
|
|
95
|
+
"""The delimiter this connector would use for `path`."""
|
|
96
|
+
return sniff_profile(path)[1]
|
|
97
|
+
|
|
98
|
+
|
|
82
99
|
def _schema_hash(column_signature: list[tuple[str, str]]) -> str:
|
|
83
100
|
"""schema_hash = 列签名规范 JSON 的 SHA-256(19.1)。"""
|
|
84
101
|
canonical = json.dumps(column_signature, sort_keys=True, ensure_ascii=True)
|
{datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/detectors/initial/missing.py
RENAMED
|
@@ -54,8 +54,10 @@ class ExcessiveNullRateDetector(DetectorBase):
|
|
|
54
54
|
table = context.handle.sql_aggregate(
|
|
55
55
|
f"SELECT count(*) AS n, sum({q} IS NULL) AS nulls FROM data"
|
|
56
56
|
).table
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
raw_total = table.column("n").to_pylist()[0]
|
|
58
|
+
raw_nulls = table.column("nulls").to_pylist()[0]
|
|
59
|
+
total = int(raw_total) if raw_total is not None else 0
|
|
60
|
+
nulls = int(raw_nulls) if raw_nulls is not None else 0
|
|
59
61
|
if total <= 0:
|
|
60
62
|
continue
|
|
61
63
|
ratio = nulls / total
|
{datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/detectors/initial/numeric.py
RENAMED
|
@@ -12,6 +12,7 @@ from datasentry_core.detectors.common import (
|
|
|
12
12
|
numeric_columns,
|
|
13
13
|
quote_ident,
|
|
14
14
|
)
|
|
15
|
+
from datasentry_core.engine.base import finite_only
|
|
15
16
|
from datasentry_core.models.detector import DetectorCapabilities, IssueCandidate
|
|
16
17
|
from datasentry_core.models.enums import EvidenceType, QualityDimension, Severity
|
|
17
18
|
from datasentry_core.reporting.evidence_desc import ev
|
|
@@ -33,7 +34,8 @@ class IqrOutlierDetector(DetectorBase):
|
|
|
33
34
|
for col in numeric_columns(context):
|
|
34
35
|
q = quote_ident(col)
|
|
35
36
|
stat = context.handle.sql_aggregate(
|
|
36
|
-
f"SELECT quantile_cont({q}, 0.25) AS q25,
|
|
37
|
+
f"SELECT quantile_cont({finite_only(q)}, 0.25) AS q25, "
|
|
38
|
+
f"quantile_cont({finite_only(q)}, 0.75) AS q75 FROM data"
|
|
37
39
|
).table
|
|
38
40
|
row = stat.to_pylist()[0]
|
|
39
41
|
q25, q75 = row["q25"], row["q75"]
|
|
@@ -102,7 +104,7 @@ class ModifiedZScoreDetector(DetectorBase):
|
|
|
102
104
|
for col in numeric_columns(context):
|
|
103
105
|
q = quote_ident(col)
|
|
104
106
|
stat = context.handle.sql_aggregate(
|
|
105
|
-
f"SELECT median({q}) AS m, mad({q}) AS mad FROM data"
|
|
107
|
+
f"SELECT median({finite_only(q)}) AS m, mad({finite_only(q)}) AS mad FROM data"
|
|
106
108
|
).table
|
|
107
109
|
row = stat.to_pylist()[0]
|
|
108
110
|
median, mad = row["m"], row["mad"]
|
|
@@ -211,8 +213,8 @@ class PercentileOutlierDetector(DetectorBase):
|
|
|
211
213
|
for col in numeric_columns(context):
|
|
212
214
|
q = quote_ident(col)
|
|
213
215
|
stat = context.handle.sql_aggregate(
|
|
214
|
-
f"SELECT quantile_cont({q}, 0.001) AS p_low,
|
|
215
|
-
f"FROM data"
|
|
216
|
+
f"SELECT quantile_cont({finite_only(q)}, 0.001) AS p_low, "
|
|
217
|
+
f"quantile_cont({finite_only(q)}, 0.999) AS p_high FROM data"
|
|
216
218
|
).table
|
|
217
219
|
row = stat.to_pylist()[0]
|
|
218
220
|
p_low, p_high = row["p_low"], row["p_high"]
|
|
@@ -271,7 +273,8 @@ class HistogramRarityDetector(DetectorBase):
|
|
|
271
273
|
for col in numeric_columns(context):
|
|
272
274
|
q = quote_ident(col)
|
|
273
275
|
stat = context.handle.sql_aggregate(
|
|
274
|
-
f"SELECT min({q}) AS lo, max({q}) AS hi,
|
|
276
|
+
f"SELECT min({finite_only(q)}) AS lo, max({finite_only(q)}) AS hi, "
|
|
277
|
+
f"count({q}) AS n FROM data"
|
|
275
278
|
).table
|
|
276
279
|
row = stat.to_pylist()[0]
|
|
277
280
|
lo, hi, total = row["lo"], row["hi"], row["n"]
|
{datasentry_core-0.8.2 → datasentry_core-0.8.4}/src/datasentry_core/detectors/missingness.py
RENAMED
|
@@ -61,8 +61,10 @@ def _column_nulls(context: DetectionContext, column: str) -> tuple[int, int]:
|
|
|
61
61
|
table = context.handle.sql_aggregate(
|
|
62
62
|
f"SELECT count(*) AS n, sum({q} IS NULL) AS nulls FROM data"
|
|
63
63
|
).table
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
raw_total = table.column("n").to_pylist()[0]
|
|
65
|
+
raw_nulls = table.column("nulls").to_pylist()[0]
|
|
66
|
+
total = int(raw_total) if raw_total is not None else 0
|
|
67
|
+
nulls = int(raw_nulls) if raw_nulls is not None else 0
|
|
66
68
|
return total, nulls
|
|
67
69
|
|
|
68
70
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""执行层:Step 3 DuckDB 只读执行器 + Step 4 Profiling + Step 7 证据融合(Polars 归 V1)。"""
|
|
2
2
|
|
|
3
|
-
from datasentry_core.engine.base import SqlExecutor, SqlParams
|
|
3
|
+
from datasentry_core.engine.base import SqlExecutor, SqlParams, finite_only
|
|
4
4
|
from datasentry_core.engine.duckdb import DuckDBExecutor
|
|
5
5
|
from datasentry_core.engine.fusion import EvidenceFusionEngine
|
|
6
6
|
from datasentry_core.engine.profiler import Profiler
|
|
@@ -13,4 +13,5 @@ __all__ = [
|
|
|
13
13
|
"SqlExecutor",
|
|
14
14
|
"SqlParams",
|
|
15
15
|
"assert_read_only_sql",
|
|
16
|
+
"finite_only",
|
|
16
17
|
]
|
|
@@ -59,3 +59,15 @@ class SetupExecutor(Protocol):
|
|
|
59
59
|
) -> Iterator[pa.RecordBatch]: ...
|
|
60
60
|
|
|
61
61
|
def close(self) -> None: ...
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def finite_only(quoted: str) -> str:
|
|
65
|
+
"""The expression naming only the finite values of a numeric column.
|
|
66
|
+
|
|
67
|
+
NaN and ±Inf are values -- they still count and they are not NULL -- but they are not numbers.
|
|
68
|
+
Unguarded, DuckDB's `stddev` aborts the whole aggregate (`OutOfRangeException`), `max([1.0,
|
|
69
|
+
nan, 3.0])` answers `nan`, a median orders NaN as a value, and a detector that splices a NaN
|
|
70
|
+
bound into a predicate fails with `Referenced column "nan" not found`. One rule for the
|
|
71
|
+
profiler and for every detector, so the same column cannot be summarised two ways (G-2).
|
|
72
|
+
"""
|
|
73
|
+
return f"CASE WHEN isfinite({quoted}) THEN {quoted} END"
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
约定:
|
|
7
7
|
- 数值列(INTEGER/FLOAT/DOUBLE/DECIMAL…)计算 min/max/mean/std/q25/median/q75;
|
|
8
8
|
字符串列仅统计 distinct/min/max;其余类型取 min/max
|
|
9
|
+
- 数值列的统计量只在**有限值**上计算:NaN/±Inf 仍算一个"值"(进 count 与 distinct、
|
|
10
|
+
不算 NULL),但不进 min/max/mean/std/分位(G-2:不守卫则 stddev 直接抛,整次扫描失败)
|
|
9
11
|
- 空字符串在 duckdb 视图路径已被折叠为 NULL(见 CSV 语义约定),null 统计包含空串
|
|
10
12
|
- examples 字段刻意留空:脱敏设施在 Step 15(LLM Provider 抽象)之后提供,
|
|
11
13
|
在此之前避免把原始样本写入画像输出
|
|
@@ -19,6 +21,7 @@ from typing import Any
|
|
|
19
21
|
import pyarrow as pa
|
|
20
22
|
|
|
21
23
|
from datasentry_core.connectors.base import DataHandle
|
|
24
|
+
from datasentry_core.engine.base import finite_only
|
|
22
25
|
from datasentry_core.models.profile import ColumnProfile, DatasetProfile
|
|
23
26
|
|
|
24
27
|
_NUMERIC_TYPES = frozenset(
|
|
@@ -46,16 +49,28 @@ def _column_exprs(column: str, quoted: str, numeric: bool) -> list[str]:
|
|
|
46
49
|
exprs = [
|
|
47
50
|
f"count({quoted}) AS {alias}",
|
|
48
51
|
f"count(DISTINCT {quoted}) AS {_quote_ident(f'{column}__distinct')}",
|
|
49
|
-
f"min({quoted}) AS {_quote_ident(f'{column}__min')}",
|
|
50
|
-
f"max({quoted}) AS {_quote_ident(f'{column}__max')}",
|
|
51
52
|
]
|
|
52
53
|
if numeric:
|
|
54
|
+
# NaN and ±Inf are values (they still count) but they are not numbers. Left unguarded,
|
|
55
|
+
# `stddev` aborts the whole aggregate -- `OutOfRangeException: STDDEV_SAMP is out of
|
|
56
|
+
# range!` -- so `scan_file` fails on an ordinary CSV whose numeric column carries the token
|
|
57
|
+
# `nan`, which is what pandas-style exports produce; and the aggregates that do not abort
|
|
58
|
+
# answer wrongly (`max([1.0, nan, 3.0])` is `nan`, the median becomes `3.0`). Statistics are
|
|
59
|
+
# therefore taken over the finite values only (G-2).
|
|
60
|
+
stat = finite_only(quoted)
|
|
53
61
|
exprs += [
|
|
54
|
-
f"
|
|
55
|
-
f"
|
|
56
|
-
f"
|
|
57
|
-
f"
|
|
58
|
-
f"quantile_cont({
|
|
62
|
+
f"min({stat}) AS {_quote_ident(f'{column}__min')}",
|
|
63
|
+
f"max({stat}) AS {_quote_ident(f'{column}__max')}",
|
|
64
|
+
f"avg({stat}) AS {_quote_ident(f'{column}__mean')}",
|
|
65
|
+
f"stddev({stat}) AS {_quote_ident(f'{column}__std')}",
|
|
66
|
+
f"quantile_cont({stat}, 0.25) AS {_quote_ident(f'{column}__q25')}",
|
|
67
|
+
f"quantile_cont({stat}, 0.5) AS {_quote_ident(f'{column}__median')}",
|
|
68
|
+
f"quantile_cont({stat}, 0.75) AS {_quote_ident(f'{column}__q75')}",
|
|
69
|
+
]
|
|
70
|
+
else:
|
|
71
|
+
exprs += [
|
|
72
|
+
f"min({quoted}) AS {_quote_ident(f'{column}__min')}",
|
|
73
|
+
f"max({quoted}) AS {_quote_ident(f'{column}__max')}",
|
|
59
74
|
]
|
|
60
75
|
return exprs
|
|
61
76
|
|
|
@@ -21,7 +21,7 @@ class SamplingConfig(BaseModel):
|
|
|
21
21
|
|
|
22
22
|
method: Literal[
|
|
23
23
|
"random", "stratified", "reservoir", "time_based", "rare_oversampling", "none"
|
|
24
|
-
] = "
|
|
24
|
+
] = "reservoir"
|
|
25
25
|
sample_size: int | None = Field(default=None, ge=1)
|
|
26
26
|
ratio: float | None = Field(default=None, gt=0.0, le=1.0)
|
|
27
27
|
seed: int = 42
|