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.
Files changed (170) hide show
  1. equiframe/__init__.py +832 -0
  2. equiframe/__main__.py +3 -0
  3. equiframe/_base.py +84 -0
  4. equiframe/_cli_support.py +125 -0
  5. equiframe/_comparison_io.py +161 -0
  6. equiframe/_dataset_provenance.py +146 -0
  7. equiframe/_decimal.py +22 -0
  8. equiframe/_diff_capture.py +433 -0
  9. equiframe/_doctor.py +123 -0
  10. equiframe/_document_io.py +61 -0
  11. equiframe/_documents.py +79 -0
  12. equiframe/_duckdb_transport.py +149 -0
  13. equiframe/_error_reporting.py +52 -0
  14. equiframe/_first_use.py +102 -0
  15. equiframe/_path_compat.py +26 -0
  16. equiframe/_physical.py +133 -0
  17. equiframe/_r_review.R +308 -0
  18. equiframe/_regular_io.py +167 -0
  19. equiframe/_report_format_v1.py +298 -0
  20. equiframe/_report_io.py +591 -0
  21. equiframe/_stability_inventory.py +1421 -0
  22. equiframe/_timezones.py +257 -0
  23. equiframe/_vendor/cyclonedx/LICENSE +201 -0
  24. equiframe/_vendor/cyclonedx/PROVENANCE.md +18 -0
  25. equiframe/_vendor/cyclonedx/bom-1.6.schema.json +5673 -0
  26. equiframe/_vendor/cyclonedx/jsf-0.82.schema.json +240 -0
  27. equiframe/_vendor/cyclonedx/spdx.schema.json +712 -0
  28. equiframe/adapters.py +454 -0
  29. equiframe/assertion_types.py +122 -0
  30. equiframe/assertions.py +271 -0
  31. equiframe/benchmark.py +101 -0
  32. equiframe/bundle.py +240 -0
  33. equiframe/cli.py +693 -0
  34. equiframe/comparison.py +937 -0
  35. equiframe/comparison_report.py +660 -0
  36. equiframe/comparison_types.py +257 -0
  37. equiframe/compatibility.py +546 -0
  38. equiframe/contracts.py +879 -0
  39. equiframe/datasets.py +1301 -0
  40. equiframe/decision.py +34 -0
  41. equiframe/destinations.py +295 -0
  42. equiframe/detector_types.py +431 -0
  43. equiframe/detectors.py +389 -0
  44. equiframe/diff.py +192 -0
  45. equiframe/diff_contracts.py +523 -0
  46. equiframe/docs/adoption.md +329 -0
  47. equiframe/docs/api.md +534 -0
  48. equiframe/docs/arquitecto.md +143 -0
  49. equiframe/docs/choosing-api.md +84 -0
  50. equiframe/docs/cientifico.md +180 -0
  51. equiframe/docs/compatibility.md +254 -0
  52. equiframe/docs/contributing.md +171 -0
  53. equiframe/docs/dependencies.md +237 -0
  54. equiframe/docs/eda.md +166 -0
  55. equiframe/docs/extending.md +158 -0
  56. equiframe/docs/fast-ci.md +52 -0
  57. equiframe/docs/first-use.md +78 -0
  58. equiframe/docs/index.md +142 -0
  59. equiframe/docs/ingestion.md +145 -0
  60. equiframe/docs/inicio.md +129 -0
  61. equiframe/docs/installed-private.md +97 -0
  62. equiframe/docs/journeys.md +362 -0
  63. equiframe/docs/lectura-conversion.md +211 -0
  64. equiframe/docs/licensing.md +17 -0
  65. equiframe/docs/maintenance.md +233 -0
  66. equiframe/docs/migration-ir.md +333 -0
  67. equiframe/docs/option-audit.md +45 -0
  68. equiframe/docs/outcome-gates.md +134 -0
  69. equiframe/docs/performance.md +263 -0
  70. equiframe/docs/piloto-simulado.md +56 -0
  71. equiframe/docs/quickstart.md +156 -0
  72. equiframe/docs/r_review.md +212 -0
  73. equiframe/docs/recipes.md +248 -0
  74. equiframe/docs/releasing.md +183 -0
  75. equiframe/docs/security.md +218 -0
  76. equiframe/docs/semantics.md +274 -0
  77. equiframe/docs/sqlite-snapshots.md +262 -0
  78. equiframe/docs/universal-sources.md +49 -0
  79. equiframe/docs/universalidad-cinco-pasos.md +107 -0
  80. equiframe/docs/upgrading.md +538 -0
  81. equiframe/duckdb_capture.py +410 -0
  82. equiframe/eda/__init__.py +36 -0
  83. equiframe/eda/__main__.py +3 -0
  84. equiframe/eda/_backend.py +612 -0
  85. equiframe/eda/_inventory.py +49 -0
  86. equiframe/eda/_paths.py +49 -0
  87. equiframe/eda/_report.py +291 -0
  88. equiframe/eda/api.py +535 -0
  89. equiframe/eda/cli.py +102 -0
  90. equiframe/eda/models.py +239 -0
  91. equiframe/errors.py +83 -0
  92. equiframe/evidence.py +375 -0
  93. equiframe/examples/integrations/eda_generic.py +60 -0
  94. equiframe/examples/integrations/outcome_gates.py +94 -0
  95. equiframe/examples/integrations/sqlite_snapshot.py +246 -0
  96. equiframe/examples/integrations/sqlite_snapshot_demo.py +226 -0
  97. equiframe/examples/integrations/universal_first_use.py +296 -0
  98. equiframe/examples/journeys/README.md +31 -0
  99. equiframe/examples/journeys/__init__.py +1 -0
  100. equiframe/examples/journeys/a_pandas_refactor.py +88 -0
  101. equiframe/examples/journeys/b_native_migration.py +181 -0
  102. equiframe/examples/journeys/c_retail_etl.py +460 -0
  103. equiframe/examples/journeys/common.py +136 -0
  104. equiframe/examples/journeys/d_financial_reconciliation.py +96 -0
  105. equiframe/examples/journeys/e_telemetry.py +135 -0
  106. equiframe/examples/journeys/f_data_science_preparation.py +84 -0
  107. equiframe/examples/journeys/g_external_snapshots.py +197 -0
  108. equiframe/examples/journeys/h_portable_ci.py +129 -0
  109. equiframe/examples/journeys/requirements.lock +249 -0
  110. equiframe/examples/journeys/verify_journeys.py +117 -0
  111. equiframe/examples/load_convert_compare.py +85 -0
  112. equiframe/execution.py +355 -0
  113. equiframe/execution_types.py +177 -0
  114. equiframe/ingestion.py +744 -0
  115. equiframe/local.py +126 -0
  116. equiframe/migration/__init__.py +94 -0
  117. equiframe/migration/_physical.py +8 -0
  118. equiframe/migration/backends/_timestamp_transport.py +109 -0
  119. equiframe/migration/backends/duckdb.py +651 -0
  120. equiframe/migration/backends/duckdb_decimal.py +125 -0
  121. equiframe/migration/backends/duckdb_time.py +87 -0
  122. equiframe/migration/backends/pandas.py +544 -0
  123. equiframe/migration/backends/polars.py +915 -0
  124. equiframe/migration/backends/polars_decimal.py +231 -0
  125. equiframe/migration/io.py +198 -0
  126. equiframe/migration/ports.py +96 -0
  127. equiframe/migration/runner.py +545 -0
  128. equiframe/migration/runner_types.py +405 -0
  129. equiframe/migration/types.py +354 -0
  130. equiframe/migration/validation.py +619 -0
  131. equiframe/normalization.py +358 -0
  132. equiframe/pipeline.py +274 -0
  133. equiframe/pipeline_types.py +247 -0
  134. equiframe/ports.py +39 -0
  135. equiframe/preprocessing.py +110 -0
  136. equiframe/profile_types.py +343 -0
  137. equiframe/profiles.py +447 -0
  138. equiframe/py.typed +0 -0
  139. equiframe/r_review.py +706 -0
  140. equiframe/reduction.py +518 -0
  141. equiframe/registry.py +121 -0
  142. equiframe/report_rendering.py +366 -0
  143. equiframe/report_types.py +294 -0
  144. equiframe/reporting.py +26 -0
  145. equiframe/rule_types.py +178 -0
  146. equiframe/rules.py +819 -0
  147. equiframe/schemas/r-execution-binding-1.0.schema.json +46 -0
  148. equiframe/schemas/r-review-binding-1.0.schema.json +28 -0
  149. equiframe/schemas/r-review-options-1.0.schema.json +38 -0
  150. equiframe/schemas/r-review-result-1.0.schema.json +207 -0
  151. equiframe/schemas/reviewed-decision-1.0.schema.json +3394 -0
  152. equiframe/serialization.py +152 -0
  153. equiframe/snapshot.py +296 -0
  154. equiframe/sources.py +283 -0
  155. equiframe/tables.py +459 -0
  156. equiframe/types.py +71 -0
  157. equiframe/values.py +252 -0
  158. equiframe/verification_request.py +35 -0
  159. equiframe/verify.py +799 -0
  160. equiframe/verify_types.py +255 -0
  161. equiframe/workflow.py +510 -0
  162. equiframe/workflow_request.py +38 -0
  163. equiframe-0.12.4.dist-info/METADATA +268 -0
  164. equiframe-0.12.4.dist-info/RECORD +170 -0
  165. equiframe-0.12.4.dist-info/WHEEL +4 -0
  166. equiframe-0.12.4.dist-info/entry_points.txt +3 -0
  167. equiframe-0.12.4.dist-info/licenses/LICENSE +21 -0
  168. equiframe-0.12.4.dist-info/licenses/THIRD_PARTY_NOTICES.md +42 -0
  169. equiframe-0.12.4.dist-info/licenses/tools/cyclonedx/LICENSE +201 -0
  170. equiframe-0.12.4.dist-info/licenses/tools/cyclonedx/PROVENANCE.md +18 -0
@@ -0,0 +1,298 @@
1
+ """Frozen persisted report layouts for evidence schema 1.0.
2
+
3
+ Readers bind these functions to a validated 1.0 result, never to public/current
4
+ presentation functions. Do not restyle or extend these implementations in place:
5
+ a new persisted layout requires an explicit format/reader compatibility decision.
6
+
7
+ Migration 1.0 has two historically emitted closed layouts. The reader selects
8
+ legacy only when the stored JSON equals migration_legacy_summary_v1 exactly;
9
+ otherwise the complete migration_summary_v1 and its HTML must match exactly.
10
+ These checks complement closed inventories and hashes, not replace them.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import html
16
+ import json
17
+ from html import escape
18
+ from typing import TYPE_CHECKING
19
+
20
+ if TYPE_CHECKING:
21
+ from equiframe.comparison_types import ComparisonResult
22
+ from equiframe.migration.runner_types import MigrationResult
23
+ from equiframe.pipeline_types import PipelineResult
24
+ from equiframe.verify_types import VerificationResult
25
+
26
+
27
+ def pipeline_summary_v1(result: PipelineResult) -> dict[str, object]:
28
+ from equiframe.pipeline_types import PipelineResult
29
+
30
+ result = PipelineResult.model_validate(result)
31
+ return {
32
+ "schema_version": "1.0",
33
+ "pipeline_id": result.pipeline_id,
34
+ "contract_sha256": result.execution.contract_sha256
35
+ if result.execution is not None
36
+ else None,
37
+ "spec_sha256": result.spec_sha256,
38
+ "engine": result.spec.execution.engine,
39
+ "family": result.spec.execution.contract.family,
40
+ "complete": result.complete,
41
+ "cancelled": result.cancelled,
42
+ "exit_code": result.exit_code,
43
+ "elapsed_seconds": result.elapsed_seconds,
44
+ "execution_state": result.execution.state
45
+ if result.execution is not None
46
+ else "unavailable",
47
+ "stages": [stage.model_dump(mode="json") for stage in result.stages],
48
+ "R": {"decision": result.hybrid.r.decision, "complete": result.hybrid.r.complete},
49
+ "M": {"decision": result.hybrid.m.decision, "complete": result.hybrid.m.complete},
50
+ "B": {
51
+ "decision": result.detection.b[0].result.decision,
52
+ "complete": result.detection.b[0].result.complete,
53
+ }
54
+ if result.detection is not None
55
+ else None,
56
+ "H": {"decision": result.hybrid.result.decision, "complete": result.hybrid.result.complete},
57
+ "comparison": {
58
+ "state": result.comparison.state,
59
+ "complete": result.comparison.complete,
60
+ "reference": result.spec.comparison_reference,
61
+ }
62
+ if result.comparison is not None
63
+ else None,
64
+ "detector_bundle_sha256": result.detection.bundle_sha256
65
+ if result.detection is not None
66
+ else None,
67
+ "scientific_equivalence_confirmed": False,
68
+ "scope": "Operational evidence only; no oracle review or scientific label.",
69
+ }
70
+
71
+
72
+ def pipeline_html_v1(summary: dict[str, object]) -> str:
73
+ """No scripts, remote assets, user URLs or raw HTML; every supplied value is escaped."""
74
+ body = html.escape(json.dumps(summary, ensure_ascii=False, allow_nan=False, indent=2))
75
+ rows = ""
76
+ for name in ("R", "M", "B", "H"):
77
+ component = summary.get(name)
78
+ if isinstance(component, dict):
79
+ decision = html.escape(str(component.get("decision", "unavailable")))
80
+ complete = html.escape(str(component.get("complete", False)))
81
+ else:
82
+ decision, complete = "not requested / unavailable", "False"
83
+ rows += f"<tr><th scope='row'>{name}</th><td>{decision}</td><td>{complete}</td></tr>"
84
+ stage_rows = ""
85
+ stages = summary.get("stages")
86
+ if isinstance(stages, list):
87
+ for stage in stages:
88
+ if isinstance(stage, dict):
89
+ stage_rows += (
90
+ "<tr>"
91
+ + "".join(
92
+ "<td>" + html.escape(str(stage.get(key, ""))) + "</td>"
93
+ for key in ("stage", "state", "code", "elapsed_seconds")
94
+ )
95
+ + "</tr>"
96
+ )
97
+ return (
98
+ '<!doctype html><html lang="en"><meta charset="utf-8">'
99
+ '<meta http-equiv="Content-Security-Policy" '
100
+ "content=\"default-src 'none'; style-src 'unsafe-inline'; base-uri 'none'\">"
101
+ '<meta name="viewport" content="width=device-width, initial-scale=1">'
102
+ "<title>EquiFrame evidence</title>"
103
+ "<style>body{max-width:1000px;margin:3rem auto;padding:0 1rem;"
104
+ "font:16px system-ui;color:#172b3a;background:#f6f8fa}"
105
+ "pre{white-space:pre-wrap;overflow-wrap:anywhere;background:white;padding:1.5rem;"
106
+ "border:1px solid #ccd5df;border-radius:8px}h1{color:#174b57}"
107
+ "table{width:100%;border-collapse:collapse;background:white;margin:1rem 0 2rem}"
108
+ "th,td{padding:.7rem;border-bottom:1px solid #ccd5df;text-align:left}"
109
+ "caption{text-align:left;font-weight:bold;padding:.5rem 0}"
110
+ "@media print{body{margin:0;background:white}details{display:block}}</style>"
111
+ "<h1>EquiFrame · Operational evidence</h1>"
112
+ "<p>Completion, alerts and equivalence are separate results. "
113
+ "An unavailable component never means absence of alert.</p>"
114
+ "<table><caption>Independent decisions</caption><thead><tr><th>Method</th>"
115
+ "<th>Decision</th><th>Complete</th></tr></thead><tbody>" + rows + "</tbody></table>"
116
+ "<table><caption>Stage trace</caption><thead><tr><th>Stage</th><th>State</th>"
117
+ "<th>Code</th><th>Seconds</th></tr></thead><tbody>" + stage_rows + "</tbody></table>"
118
+ "<details open><summary>Full operational summary</summary><pre>"
119
+ + body
120
+ + "</pre></details></html>"
121
+ )
122
+
123
+
124
+ def migration_legacy_summary_v1(result: MigrationResult) -> dict[str, object]:
125
+ from equiframe.migration.runner_types import MigrationResult
126
+
127
+ result = MigrationResult.model_validate(result)
128
+ return {
129
+ "schema_version": "1.0",
130
+ "migration_id": result.migration_id,
131
+ "source_engine": result.spec.source_engine,
132
+ "target_engine": result.spec.target_engine,
133
+ "complete": result.complete,
134
+ "state": result.state,
135
+ "independent_verification": result.verification,
136
+ "expected_requested": result.expected_requested,
137
+ "detection_requested": result.detection_requested,
138
+ "rules_required": result.spec.require_rules,
139
+ "exit_code": result.exit_code,
140
+ "cancelled": result.cancelled,
141
+ "stages": [s.model_dump(mode="json") for s in result.stages],
142
+ "source_pipeline_id": result.source.pipeline_id if result.source is not None else None,
143
+ "target_pipeline_id": result.target.pipeline_id if result.target is not None else None,
144
+ "source_exit_code": result.source.exit_code if result.source is not None else None,
145
+ "target_exit_code": result.target.exit_code if result.target is not None else None,
146
+ "difference_records": result.comparison.counts.difference_records
147
+ if result.comparison is not None
148
+ else None,
149
+ "equivalence_is_independent_correctness": False,
150
+ }
151
+
152
+
153
+ def _comparison_summary_v1(
154
+ result: ComparisonResult | None, *, requested: bool
155
+ ) -> dict[str, object]:
156
+ return {
157
+ "state": result.state
158
+ if result is not None
159
+ else "not_evaluable"
160
+ if requested
161
+ else "not_requested",
162
+ "complete": result.complete if result is not None else False,
163
+ "comparison_id": result.comparison_id if result is not None else None,
164
+ "difference_records": result.counts.difference_records if result is not None else None,
165
+ }
166
+
167
+
168
+ def migration_summary_v1(result: MigrationResult) -> dict[str, object]:
169
+ """Keep each comparison visible without attributing human validation to C."""
170
+ from equiframe.migration.runner_types import MigrationResult
171
+
172
+ result = MigrationResult.model_validate(result)
173
+ return {
174
+ **migration_legacy_summary_v1(result),
175
+ "expected_output_comparison": result.verification,
176
+ "comparisons": {
177
+ "source_target": _comparison_summary_v1(result.comparison, requested=True),
178
+ "source_expected": _comparison_summary_v1(
179
+ result.verification_source, requested=result.expected_requested
180
+ ),
181
+ "target_expected": _comparison_summary_v1(
182
+ result.verification_target, requested=result.expected_requested
183
+ ),
184
+ },
185
+ "equiframe_verify": {
186
+ "procedure": "EquiFrame Verify",
187
+ "identifier": "O",
188
+ "status": "not_established",
189
+ "reason": (
190
+ "Migration compares supplied expected content but does not establish "
191
+ "independent expected authorship or collect two independent human reviews."
192
+ ),
193
+ },
194
+ }
195
+
196
+
197
+ def migration_html_v1(result: MigrationResult, *, legacy: bool = False) -> str:
198
+ summary = migration_summary_v1(result)
199
+ rows = "".join(
200
+ "<tr><td>"
201
+ + escape(s.stage)
202
+ + "</td><td>"
203
+ + escape(s.state)
204
+ + "</td><td>"
205
+ + escape(s.code)
206
+ + "</td></tr>"
207
+ for s in result.stages
208
+ )
209
+ return (
210
+ '<!doctype html><html lang="en"><head><meta charset="utf-8">'
211
+ "<title>EquiFrame migration</title></head><body>"
212
+ "<h1>EquiFrame migration</h1><p>"
213
+ + escape(result.spec.source_engine)
214
+ + " to "
215
+ + escape(result.spec.target_engine)
216
+ + "</p><p>Observed equivalence: "
217
+ + escape(result.state)
218
+ + (". Independent verification: " if legacy else ". Supplied expected comparison: ")
219
+ + escape(result.verification)
220
+ + ". Complete: "
221
+ + str(summary["complete"])
222
+ + ".</p>"
223
+ "<p>Matching native outputs do not establish independent correctness.</p>"
224
+ + (
225
+ ""
226
+ if legacy
227
+ else "<p>EquiFrame Verify (O): not established. Comparing supplied expected "
228
+ "content does not establish independent expected authorship or collect "
229
+ "two independent human reviews.</p>"
230
+ )
231
+ + "<table><thead><tr><th>Stage</th><th>State</th><th>Code</th></tr></thead><tbody>"
232
+ + rows
233
+ + "</tbody></table></body></html>"
234
+ )
235
+
236
+
237
+ def verification_summary_v1(result: VerificationResult) -> dict[str, object]:
238
+ return {
239
+ "schema_version": "1.0",
240
+ "verification_id": result.verification_id,
241
+ "operational_state": result.operational_state,
242
+ "publishable": result.publishable,
243
+ "exit_code": result.exit_code,
244
+ "contract_sha256": result.binding.contract_sha256,
245
+ "binding": result.binding.model_dump(mode="json"),
246
+ "dimensions": {
247
+ name: getattr(result, name).model_dump(mode="json")
248
+ for name in ("inputs", "technical", "integrity", "agreement", "independent_review")
249
+ },
250
+ "expected": [c.model_dump(mode="json") for c in result.expected],
251
+ "rules": [c.model_dump(mode="json") for c in result.rules],
252
+ "rule_evaluations": [r.model_dump(mode="json") for r in result.rule_evaluations],
253
+ "coverage": result.coverage.model_dump(mode="json"),
254
+ "provenance": result.provenance.model_dump(mode="json") if result.provenance else None,
255
+ "expected_source_engine": result.expected_source_engine,
256
+ "elapsed_seconds": result.elapsed_seconds,
257
+ "engines": [
258
+ {
259
+ "output": name,
260
+ "engine": e.plan.engine,
261
+ "versions": [v.model_dump(mode="json") for v in e.versions],
262
+ }
263
+ for name, e in result.executions
264
+ ],
265
+ "comparisons": [
266
+ {
267
+ "comparison_id": c.comparison_id,
268
+ "state": c.state,
269
+ "reference": c.reference,
270
+ "counts": c.counts.model_dump(mode="json"),
271
+ "examples": [d.model_dump(mode="json") for d in c.examples],
272
+ "examples_truncated": c.counts.difference_records > len(c.examples),
273
+ "policy": c.spec.policy.model_dump(mode="json"),
274
+ "differences": c.differences.model_dump(mode="json"),
275
+ }
276
+ for c in (*result.comparisons, *result.input_comparisons)
277
+ ],
278
+ "identity_authentication": (
279
+ "Declared identities are not authenticated; hashes prove integrity"
280
+ ),
281
+ }
282
+
283
+
284
+ def verification_html_v1(result: VerificationResult) -> str:
285
+ return (
286
+ '<!doctype html><html lang="en"><meta charset="utf-8"><title>EquiFrame Verify</title>'
287
+ "<h1>EquiFrame Verify</h1><pre>"
288
+ + escape(
289
+ json.dumps(
290
+ verification_summary_v1(result),
291
+ sort_keys=True,
292
+ ensure_ascii=True,
293
+ allow_nan=False,
294
+ separators=(",", ":"),
295
+ )
296
+ )
297
+ + "</pre></html>"
298
+ )