equiframe 0.12.4__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.
- equiframe/__init__.py +832 -0
- equiframe/__main__.py +3 -0
- equiframe/_base.py +84 -0
- equiframe/_cli_support.py +125 -0
- equiframe/_comparison_io.py +161 -0
- equiframe/_dataset_provenance.py +146 -0
- equiframe/_decimal.py +22 -0
- equiframe/_diff_capture.py +433 -0
- equiframe/_doctor.py +123 -0
- equiframe/_document_io.py +61 -0
- equiframe/_documents.py +79 -0
- equiframe/_duckdb_transport.py +149 -0
- equiframe/_error_reporting.py +52 -0
- equiframe/_first_use.py +102 -0
- equiframe/_path_compat.py +26 -0
- equiframe/_physical.py +133 -0
- equiframe/_r_review.R +308 -0
- equiframe/_regular_io.py +167 -0
- equiframe/_report_format_v1.py +298 -0
- equiframe/_report_io.py +591 -0
- equiframe/_stability_inventory.py +1421 -0
- equiframe/_timezones.py +257 -0
- equiframe/_vendor/cyclonedx/LICENSE +201 -0
- equiframe/_vendor/cyclonedx/PROVENANCE.md +18 -0
- equiframe/_vendor/cyclonedx/bom-1.6.schema.json +5673 -0
- equiframe/_vendor/cyclonedx/jsf-0.82.schema.json +240 -0
- equiframe/_vendor/cyclonedx/spdx.schema.json +712 -0
- equiframe/adapters.py +454 -0
- equiframe/assertion_types.py +122 -0
- equiframe/assertions.py +271 -0
- equiframe/benchmark.py +101 -0
- equiframe/bundle.py +240 -0
- equiframe/cli.py +693 -0
- equiframe/comparison.py +937 -0
- equiframe/comparison_report.py +660 -0
- equiframe/comparison_types.py +257 -0
- equiframe/compatibility.py +546 -0
- equiframe/contracts.py +879 -0
- equiframe/datasets.py +1301 -0
- equiframe/decision.py +34 -0
- equiframe/destinations.py +295 -0
- equiframe/detector_types.py +431 -0
- equiframe/detectors.py +389 -0
- equiframe/diff.py +192 -0
- equiframe/diff_contracts.py +523 -0
- equiframe/docs/adoption.md +329 -0
- equiframe/docs/api.md +534 -0
- equiframe/docs/arquitecto.md +143 -0
- equiframe/docs/choosing-api.md +84 -0
- equiframe/docs/cientifico.md +180 -0
- equiframe/docs/compatibility.md +254 -0
- equiframe/docs/contributing.md +171 -0
- equiframe/docs/dependencies.md +237 -0
- equiframe/docs/eda.md +166 -0
- equiframe/docs/extending.md +158 -0
- equiframe/docs/fast-ci.md +52 -0
- equiframe/docs/first-use.md +78 -0
- equiframe/docs/index.md +142 -0
- equiframe/docs/ingestion.md +145 -0
- equiframe/docs/inicio.md +129 -0
- equiframe/docs/installed-private.md +97 -0
- equiframe/docs/journeys.md +362 -0
- equiframe/docs/lectura-conversion.md +211 -0
- equiframe/docs/licensing.md +17 -0
- equiframe/docs/maintenance.md +233 -0
- equiframe/docs/migration-ir.md +333 -0
- equiframe/docs/option-audit.md +45 -0
- equiframe/docs/outcome-gates.md +134 -0
- equiframe/docs/performance.md +263 -0
- equiframe/docs/piloto-simulado.md +56 -0
- equiframe/docs/quickstart.md +156 -0
- equiframe/docs/r_review.md +212 -0
- equiframe/docs/recipes.md +248 -0
- equiframe/docs/releasing.md +183 -0
- equiframe/docs/security.md +218 -0
- equiframe/docs/semantics.md +274 -0
- equiframe/docs/sqlite-snapshots.md +262 -0
- equiframe/docs/universal-sources.md +49 -0
- equiframe/docs/universalidad-cinco-pasos.md +107 -0
- equiframe/docs/upgrading.md +538 -0
- equiframe/duckdb_capture.py +410 -0
- equiframe/eda/__init__.py +36 -0
- equiframe/eda/__main__.py +3 -0
- equiframe/eda/_backend.py +612 -0
- equiframe/eda/_inventory.py +49 -0
- equiframe/eda/_paths.py +49 -0
- equiframe/eda/_report.py +291 -0
- equiframe/eda/api.py +535 -0
- equiframe/eda/cli.py +102 -0
- equiframe/eda/models.py +239 -0
- equiframe/errors.py +83 -0
- equiframe/evidence.py +375 -0
- equiframe/examples/integrations/eda_generic.py +60 -0
- equiframe/examples/integrations/outcome_gates.py +94 -0
- equiframe/examples/integrations/sqlite_snapshot.py +246 -0
- equiframe/examples/integrations/sqlite_snapshot_demo.py +226 -0
- equiframe/examples/integrations/universal_first_use.py +296 -0
- equiframe/examples/journeys/README.md +31 -0
- equiframe/examples/journeys/__init__.py +1 -0
- equiframe/examples/journeys/a_pandas_refactor.py +88 -0
- equiframe/examples/journeys/b_native_migration.py +181 -0
- equiframe/examples/journeys/c_retail_etl.py +460 -0
- equiframe/examples/journeys/common.py +136 -0
- equiframe/examples/journeys/d_financial_reconciliation.py +96 -0
- equiframe/examples/journeys/e_telemetry.py +135 -0
- equiframe/examples/journeys/f_data_science_preparation.py +84 -0
- equiframe/examples/journeys/g_external_snapshots.py +197 -0
- equiframe/examples/journeys/h_portable_ci.py +129 -0
- equiframe/examples/journeys/requirements.lock +249 -0
- equiframe/examples/journeys/verify_journeys.py +117 -0
- equiframe/examples/load_convert_compare.py +85 -0
- equiframe/execution.py +355 -0
- equiframe/execution_types.py +177 -0
- equiframe/ingestion.py +744 -0
- equiframe/local.py +126 -0
- equiframe/migration/__init__.py +94 -0
- equiframe/migration/_physical.py +8 -0
- equiframe/migration/backends/_timestamp_transport.py +109 -0
- equiframe/migration/backends/duckdb.py +651 -0
- equiframe/migration/backends/duckdb_decimal.py +125 -0
- equiframe/migration/backends/duckdb_time.py +87 -0
- equiframe/migration/backends/pandas.py +544 -0
- equiframe/migration/backends/polars.py +915 -0
- equiframe/migration/backends/polars_decimal.py +231 -0
- equiframe/migration/io.py +198 -0
- equiframe/migration/ports.py +96 -0
- equiframe/migration/runner.py +545 -0
- equiframe/migration/runner_types.py +405 -0
- equiframe/migration/types.py +354 -0
- equiframe/migration/validation.py +619 -0
- equiframe/normalization.py +358 -0
- equiframe/pipeline.py +274 -0
- equiframe/pipeline_types.py +247 -0
- equiframe/ports.py +39 -0
- equiframe/preprocessing.py +110 -0
- equiframe/profile_types.py +343 -0
- equiframe/profiles.py +447 -0
- equiframe/py.typed +0 -0
- equiframe/r_review.py +706 -0
- equiframe/reduction.py +518 -0
- equiframe/registry.py +121 -0
- equiframe/report_rendering.py +366 -0
- equiframe/report_types.py +294 -0
- equiframe/reporting.py +26 -0
- equiframe/rule_types.py +178 -0
- equiframe/rules.py +819 -0
- equiframe/schemas/r-execution-binding-1.0.schema.json +46 -0
- equiframe/schemas/r-review-binding-1.0.schema.json +28 -0
- equiframe/schemas/r-review-options-1.0.schema.json +38 -0
- equiframe/schemas/r-review-result-1.0.schema.json +207 -0
- equiframe/schemas/reviewed-decision-1.0.schema.json +3394 -0
- equiframe/serialization.py +152 -0
- equiframe/snapshot.py +296 -0
- equiframe/sources.py +283 -0
- equiframe/tables.py +459 -0
- equiframe/types.py +71 -0
- equiframe/values.py +252 -0
- equiframe/verification_request.py +35 -0
- equiframe/verify.py +799 -0
- equiframe/verify_types.py +255 -0
- equiframe/workflow.py +510 -0
- equiframe/workflow_request.py +38 -0
- equiframe-0.12.4.dist-info/METADATA +268 -0
- equiframe-0.12.4.dist-info/RECORD +170 -0
- equiframe-0.12.4.dist-info/WHEEL +4 -0
- equiframe-0.12.4.dist-info/entry_points.txt +3 -0
- equiframe-0.12.4.dist-info/licenses/LICENSE +21 -0
- equiframe-0.12.4.dist-info/licenses/THIRD_PARTY_NOTICES.md +42 -0
- equiframe-0.12.4.dist-info/licenses/tools/cyclonedx/LICENSE +201 -0
- equiframe-0.12.4.dist-info/licenses/tools/cyclonedx/PROVENANCE.md +18 -0
equiframe/rule_types.py
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"""Supplementary v1 evidence for operational rules; contract 1.0 stays unchanged."""
|
|
2
|
+
|
|
3
|
+
from typing import Annotated, Literal
|
|
4
|
+
|
|
5
|
+
from pydantic import Field, model_validator
|
|
6
|
+
from typing_extensions import Self
|
|
7
|
+
|
|
8
|
+
from equiframe._base import (
|
|
9
|
+
FrozenModel,
|
|
10
|
+
Identifier,
|
|
11
|
+
NonNegativeInt,
|
|
12
|
+
PositiveInt,
|
|
13
|
+
Text,
|
|
14
|
+
_ExplicitOutcome,
|
|
15
|
+
fail,
|
|
16
|
+
)
|
|
17
|
+
from equiframe.comparison_types import ComparisonValue
|
|
18
|
+
from equiframe.types import ArtifactRef, DecisionState, Sha256
|
|
19
|
+
|
|
20
|
+
CheckState = Literal["satisfied", "violated", "not_applicable", "not_evaluable"]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class RuleOptions(FrozenModel):
|
|
24
|
+
max_rows: PositiveInt = 1_000_000
|
|
25
|
+
max_artifact_bytes: PositiveInt = 128 * 1024 * 1024
|
|
26
|
+
max_evidence_bytes: PositiveInt = 64 * 1024 * 1024
|
|
27
|
+
max_work: PositiveInt = 10_000_000
|
|
28
|
+
max_examples: NonNegativeInt = 20
|
|
29
|
+
timeout_seconds: Annotated[float, Field(gt=0)] | None = None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class RuleFinding(FrozenModel):
|
|
33
|
+
schema_version: Literal["1.0"] = "1.0"
|
|
34
|
+
check_id: Identifier
|
|
35
|
+
code: Text
|
|
36
|
+
table: Identifier
|
|
37
|
+
position: NonNegativeInt | None = None
|
|
38
|
+
column: str | None = None
|
|
39
|
+
observed: tuple[ComparisonValue, ...] = ()
|
|
40
|
+
expected: tuple[ComparisonValue, ...] = ()
|
|
41
|
+
message: Text
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class CheckResult(_ExplicitOutcome, FrozenModel):
|
|
45
|
+
_truth_value_guidance = (
|
|
46
|
+
"inspect .state and .complete explicitly; not_applicable is not approval"
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
check_id: Identifier
|
|
50
|
+
revision: PositiveInt | None = None
|
|
51
|
+
kind: Literal["precondition", "rule"]
|
|
52
|
+
implementation: Text | None = None
|
|
53
|
+
state: CheckState
|
|
54
|
+
complete: bool
|
|
55
|
+
required: bool | None = None
|
|
56
|
+
severity: Literal["info", "warning", "error"] | None = None
|
|
57
|
+
reason: Text | None = None
|
|
58
|
+
violation_count: NonNegativeInt = 0
|
|
59
|
+
examined_rows: NonNegativeInt = 0
|
|
60
|
+
examples: tuple[RuleFinding, ...] = ()
|
|
61
|
+
findings: ArtifactRef | None = None
|
|
62
|
+
|
|
63
|
+
@model_validator(mode="after")
|
|
64
|
+
def consistent(self) -> Self:
|
|
65
|
+
if self.kind == "rule" and (
|
|
66
|
+
self.revision is None or self.required is None or self.severity is None
|
|
67
|
+
):
|
|
68
|
+
fail("rule_metadata", "rule requires revision, required and severity", "kind")
|
|
69
|
+
if self.kind == "precondition" and (self.required is not None or self.severity is not None):
|
|
70
|
+
fail("precondition_metadata", "precondition does not carry rule policy", "kind")
|
|
71
|
+
if self.state == "satisfied" and (not self.complete or self.violation_count):
|
|
72
|
+
fail("rule_state", "satisfied requires complete check without violations", "state")
|
|
73
|
+
if self.state == "violated" and not self.violation_count:
|
|
74
|
+
fail("rule_state", "violated requires observed findings", "state")
|
|
75
|
+
if self.state in ("not_applicable", "not_evaluable") and not self.reason:
|
|
76
|
+
fail("rule_state", "skipped or unavailable checks require a reason", "reason")
|
|
77
|
+
if self.state == "not_applicable" and (not self.complete or self.violation_count):
|
|
78
|
+
fail("rule_state", "not_applicable requires a verified reason", "state")
|
|
79
|
+
if self.state == "not_evaluable" and (self.complete or self.violation_count):
|
|
80
|
+
fail("rule_state", "not_evaluable cannot claim a completed check or findings", "state")
|
|
81
|
+
if len(self.examples) > self.violation_count:
|
|
82
|
+
fail("rule_examples", "examples exceed finding count", "examples")
|
|
83
|
+
if any(example.check_id != self.check_id for example in self.examples):
|
|
84
|
+
fail("rule_examples", "example belongs to another check", "examples")
|
|
85
|
+
if self.findings is not None and self.findings.format != "jsonl":
|
|
86
|
+
fail("rule_evidence", "findings must be JSONL", "findings")
|
|
87
|
+
return self
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class RuleCoverage(FrozenModel):
|
|
91
|
+
declared: NonNegativeInt
|
|
92
|
+
checked: NonNegativeInt
|
|
93
|
+
skipped: NonNegativeInt
|
|
94
|
+
unavailable: NonNegativeInt
|
|
95
|
+
required_declared: NonNegativeInt
|
|
96
|
+
required_checked: NonNegativeInt
|
|
97
|
+
alerts: NonNegativeInt
|
|
98
|
+
finding_count: NonNegativeInt
|
|
99
|
+
preconditions_declared: NonNegativeInt
|
|
100
|
+
preconditions_verified: NonNegativeInt
|
|
101
|
+
preconditions_unavailable: NonNegativeInt
|
|
102
|
+
highest_alert_severity: Literal["info", "warning", "error"] | None = None
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class RuleEvaluation(_ExplicitOutcome, FrozenModel):
|
|
106
|
+
_truth_value_guidance = (
|
|
107
|
+
"inspect .decision, .complete and .coverage explicitly; no_alert is not equivalence"
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
schema_version: Literal["1.0"] = "1.0"
|
|
111
|
+
evaluation_id: Identifier
|
|
112
|
+
run_id: Identifier
|
|
113
|
+
execution_id: Identifier
|
|
114
|
+
contract_id: Identifier
|
|
115
|
+
contract_revision: PositiveInt
|
|
116
|
+
contract_sha256: Sha256
|
|
117
|
+
rule_set_sha256: Sha256
|
|
118
|
+
execution_state: Text
|
|
119
|
+
options: RuleOptions
|
|
120
|
+
decision: DecisionState
|
|
121
|
+
complete: bool
|
|
122
|
+
preconditions: tuple[CheckResult, ...]
|
|
123
|
+
rules: tuple[CheckResult, ...]
|
|
124
|
+
coverage: RuleCoverage
|
|
125
|
+
started_at: Text
|
|
126
|
+
elapsed_seconds: Annotated[float, Field(ge=0)]
|
|
127
|
+
work_units: NonNegativeInt
|
|
128
|
+
diagnostic: Text | None = None
|
|
129
|
+
|
|
130
|
+
@model_validator(mode="after")
|
|
131
|
+
def consistent(self) -> Self:
|
|
132
|
+
if any(item.kind != "precondition" for item in self.preconditions):
|
|
133
|
+
fail("rule_evidence", "precondition result has wrong kind", "preconditions")
|
|
134
|
+
if any(item.kind != "rule" for item in self.rules):
|
|
135
|
+
fail("rule_evidence", "rule result has wrong kind", "rules")
|
|
136
|
+
checked = tuple(r for r in self.rules if r.state in ("satisfied", "violated"))
|
|
137
|
+
alerts = tuple(r for r in self.rules if r.state == "violated")
|
|
138
|
+
severities = {"info": 1, "warning": 2, "error": 3}
|
|
139
|
+
highest = max(
|
|
140
|
+
(r.severity for r in alerts if r.severity is not None),
|
|
141
|
+
key=severities.__getitem__,
|
|
142
|
+
default=None,
|
|
143
|
+
)
|
|
144
|
+
expected_coverage = RuleCoverage(
|
|
145
|
+
declared=len(self.rules),
|
|
146
|
+
checked=len(checked),
|
|
147
|
+
skipped=sum(r.state == "not_applicable" for r in self.rules),
|
|
148
|
+
unavailable=sum(r.state == "not_evaluable" for r in self.rules),
|
|
149
|
+
required_declared=sum(r.required is True for r in self.rules),
|
|
150
|
+
required_checked=sum(r.required is True for r in checked),
|
|
151
|
+
alerts=len(alerts),
|
|
152
|
+
finding_count=sum(r.violation_count for r in self.rules),
|
|
153
|
+
preconditions_declared=len(self.preconditions),
|
|
154
|
+
preconditions_verified=sum(r.complete for r in self.preconditions),
|
|
155
|
+
preconditions_unavailable=sum(not r.complete for r in self.preconditions),
|
|
156
|
+
highest_alert_severity=highest,
|
|
157
|
+
)
|
|
158
|
+
if self.coverage != expected_coverage:
|
|
159
|
+
fail("rule_coverage", "coverage does not match check results", "coverage")
|
|
160
|
+
expected_complete = (
|
|
161
|
+
bool(self.rules)
|
|
162
|
+
and self.diagnostic is None
|
|
163
|
+
and self.execution_state == "completed"
|
|
164
|
+
and all(item.complete for item in (*self.preconditions, *self.rules))
|
|
165
|
+
and any(item.state in ("satisfied", "violated") for item in self.rules)
|
|
166
|
+
)
|
|
167
|
+
if self.complete != expected_complete:
|
|
168
|
+
fail(
|
|
169
|
+
"rule_completeness",
|
|
170
|
+
"completeness does not match checked rules and execution",
|
|
171
|
+
"complete",
|
|
172
|
+
)
|
|
173
|
+
expected_decision: DecisionState = (
|
|
174
|
+
"alert" if alerts else "no_alert" if self.complete else "not_evaluable"
|
|
175
|
+
)
|
|
176
|
+
if self.decision != expected_decision:
|
|
177
|
+
fail("rule_decision", "decision does not match completed checks and alerts", "decision")
|
|
178
|
+
return self
|