schemadriftgate 0.1.0__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.
- schemadriftgate-0.1.0/LICENSE +202 -0
- schemadriftgate-0.1.0/PKG-INFO +349 -0
- schemadriftgate-0.1.0/README.md +308 -0
- schemadriftgate-0.1.0/pyproject.toml +67 -0
- schemadriftgate-0.1.0/setup.cfg +4 -0
- schemadriftgate-0.1.0/src/schemadriftgate.egg-info/PKG-INFO +349 -0
- schemadriftgate-0.1.0/src/schemadriftgate.egg-info/SOURCES.txt +66 -0
- schemadriftgate-0.1.0/src/schemadriftgate.egg-info/dependency_links.txt +1 -0
- schemadriftgate-0.1.0/src/schemadriftgate.egg-info/entry_points.txt +2 -0
- schemadriftgate-0.1.0/src/schemadriftgate.egg-info/requires.txt +27 -0
- schemadriftgate-0.1.0/src/schemadriftgate.egg-info/top_level.txt +1 -0
- schemadriftgate-0.1.0/src/schemagate/__init__.py +25 -0
- schemadriftgate-0.1.0/src/schemagate/cli.py +423 -0
- schemadriftgate-0.1.0/src/schemagate/config.py +43 -0
- schemadriftgate-0.1.0/src/schemagate/drift/__init__.py +21 -0
- schemadriftgate-0.1.0/src/schemagate/drift/detect.py +163 -0
- schemadriftgate-0.1.0/src/schemagate/drift/remediate.py +80 -0
- schemadriftgate-0.1.0/src/schemagate/errors.py +38 -0
- schemadriftgate-0.1.0/src/schemagate/ingest/__init__.py +15 -0
- schemadriftgate-0.1.0/src/schemagate/ingest/header.py +71 -0
- schemadriftgate-0.1.0/src/schemagate/ingest/loader.py +149 -0
- schemadriftgate-0.1.0/src/schemagate/ingest/profile.py +133 -0
- schemadriftgate-0.1.0/src/schemagate/ingest/readers.py +94 -0
- schemadriftgate-0.1.0/src/schemagate/ingest/values.py +214 -0
- schemadriftgate-0.1.0/src/schemagate/llm/__init__.py +41 -0
- schemadriftgate-0.1.0/src/schemagate/llm/anthropic_provider.py +39 -0
- schemadriftgate-0.1.0/src/schemagate/llm/base.py +89 -0
- schemadriftgate-0.1.0/src/schemagate/llm/fake.py +29 -0
- schemadriftgate-0.1.0/src/schemagate/llm/heuristic.py +135 -0
- schemadriftgate-0.1.0/src/schemagate/llm/openai_provider.py +36 -0
- schemadriftgate-0.1.0/src/schemagate/llm/prompts/map_columns_v1.txt +26 -0
- schemadriftgate-0.1.0/src/schemagate/llm/prompts.py +35 -0
- schemadriftgate-0.1.0/src/schemagate/llm/validation.py +95 -0
- schemadriftgate-0.1.0/src/schemagate/materialize/__init__.py +24 -0
- schemadriftgate-0.1.0/src/schemagate/materialize/dialects.py +343 -0
- schemadriftgate-0.1.0/src/schemagate/materialize/executor.py +98 -0
- schemadriftgate-0.1.0/src/schemagate/materialize/plan.py +98 -0
- schemadriftgate-0.1.0/src/schemagate/models.py +67 -0
- schemadriftgate-0.1.0/src/schemagate/project.py +152 -0
- schemadriftgate-0.1.0/src/schemagate/resolve/__init__.py +22 -0
- schemadriftgate-0.1.0/src/schemagate/resolve/base.py +42 -0
- schemadriftgate-0.1.0/src/schemagate/resolve/collisions.py +63 -0
- schemadriftgate-0.1.0/src/schemagate/resolve/compat.py +53 -0
- schemadriftgate-0.1.0/src/schemagate/resolve/deterministic.py +136 -0
- schemadriftgate-0.1.0/src/schemagate/resolve/model.py +65 -0
- schemadriftgate-0.1.0/src/schemagate/resolve/pipeline.py +105 -0
- schemadriftgate-0.1.0/src/schemagate/schema/__init__.py +20 -0
- schemadriftgate-0.1.0/src/schemagate/schema/data/synonyms.yaml +66 -0
- schemadriftgate-0.1.0/src/schemagate/schema/normalize.py +85 -0
- schemadriftgate-0.1.0/src/schemagate/schema/target.py +106 -0
- schemadriftgate-0.1.0/src/schemagate/schema/vocabulary.py +48 -0
- schemadriftgate-0.1.0/src/schemagate/store/__init__.py +5 -0
- schemadriftgate-0.1.0/src/schemagate/store/audit.py +110 -0
- schemadriftgate-0.1.0/src/schemagate/store/db.py +221 -0
- schemadriftgate-0.1.0/src/schemagate/store/service.py +472 -0
- schemadriftgate-0.1.0/tests/test_audit.py +78 -0
- schemadriftgate-0.1.0/tests/test_benchmark.py +65 -0
- schemadriftgate-0.1.0/tests/test_cli.py +96 -0
- schemadriftgate-0.1.0/tests/test_collisions.py +39 -0
- schemadriftgate-0.1.0/tests/test_confidence_cap.py +99 -0
- schemadriftgate-0.1.0/tests/test_drift.py +136 -0
- schemadriftgate-0.1.0/tests/test_governance.py +165 -0
- schemadriftgate-0.1.0/tests/test_hardening.py +95 -0
- schemadriftgate-0.1.0/tests/test_ingest.py +158 -0
- schemadriftgate-0.1.0/tests/test_llm_validation.py +155 -0
- schemadriftgate-0.1.0/tests/test_materialize.py +145 -0
- schemadriftgate-0.1.0/tests/test_normalize.py +48 -0
- schemadriftgate-0.1.0/tests/test_resolvers.py +126 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
|
|
2
|
+
Apache License
|
|
3
|
+
Version 2.0, January 2004
|
|
4
|
+
http://www.apache.org/licenses/
|
|
5
|
+
|
|
6
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
7
|
+
|
|
8
|
+
1. Definitions.
|
|
9
|
+
|
|
10
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
11
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
12
|
+
|
|
13
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
14
|
+
the copyright owner that is granting the License.
|
|
15
|
+
|
|
16
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
17
|
+
other entities that control, are controlled by, or are under common
|
|
18
|
+
control with that entity. For the purposes of this definition,
|
|
19
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
20
|
+
direction or management of such entity, whether by contract or
|
|
21
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
23
|
+
|
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
25
|
+
exercising permissions granted by this License.
|
|
26
|
+
|
|
27
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
28
|
+
including but not limited to software source code, documentation
|
|
29
|
+
source, and configuration files.
|
|
30
|
+
|
|
31
|
+
"Object" form shall mean any form resulting from mechanical
|
|
32
|
+
transformation or translation of a Source form, including but
|
|
33
|
+
not limited to compiled object code, generated documentation,
|
|
34
|
+
and conversions to other media types.
|
|
35
|
+
|
|
36
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
37
|
+
Object form, made available under the License, as indicated by a
|
|
38
|
+
copyright notice that is included in or attached to the work
|
|
39
|
+
(an example is provided in the Appendix below).
|
|
40
|
+
|
|
41
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
42
|
+
form, that is based on (or derived from) the Work and for which the
|
|
43
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
44
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
45
|
+
of this License, Derivative Works shall not include works that remain
|
|
46
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
47
|
+
the Work and Derivative Works thereof.
|
|
48
|
+
|
|
49
|
+
"Contribution" shall mean any work of authorship, including
|
|
50
|
+
the original version of the Work and any modifications or additions
|
|
51
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
52
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
53
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
54
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
55
|
+
means any form of electronic, verbal, or written communication sent
|
|
56
|
+
to the Licensor or its representatives, including but not limited to
|
|
57
|
+
communication on electronic mailing lists, source code control systems,
|
|
58
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
59
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
60
|
+
excluding communication that is conspicuously marked or otherwise
|
|
61
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
62
|
+
|
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
64
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
65
|
+
subsequently incorporated within the Work.
|
|
66
|
+
|
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
|
73
|
+
|
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
79
|
+
where such license applies only to those patent claims licensable
|
|
80
|
+
by such Contributor that are necessarily infringed by their
|
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
83
|
+
institute patent litigation against any entity (including a
|
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
86
|
+
or contributory patent infringement, then any patent licenses
|
|
87
|
+
granted to You under this License for that Work shall terminate
|
|
88
|
+
as of the date such litigation is filed.
|
|
89
|
+
|
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
92
|
+
modifications, and in Source or Object form, provided that You
|
|
93
|
+
meet the following conditions:
|
|
94
|
+
|
|
95
|
+
(a) You must give any other recipients of the Work or
|
|
96
|
+
Derivative Works a copy of this License; and
|
|
97
|
+
|
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
|
99
|
+
stating that You changed the files; and
|
|
100
|
+
|
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
|
103
|
+
attribution notices from the Source form of the Work,
|
|
104
|
+
excluding those notices that do not pertain to any part of
|
|
105
|
+
the Derivative Works; and
|
|
106
|
+
|
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
|
109
|
+
include a readable copy of the attribution notices contained
|
|
110
|
+
within such NOTICE file, excluding those notices that do not
|
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
|
112
|
+
of the following places: within a NOTICE text file distributed
|
|
113
|
+
as part of the Derivative Works; within the Source form or
|
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
|
115
|
+
within a display generated by the Derivative Works, if and
|
|
116
|
+
wherever such third-party notices normally appear. The contents
|
|
117
|
+
of the NOTICE file are for informational purposes only and
|
|
118
|
+
do not modify the License. You may add Your own attribution
|
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
121
|
+
that such additional attribution notices cannot be construed
|
|
122
|
+
as modifying the License.
|
|
123
|
+
|
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
|
125
|
+
may provide additional or different license terms and conditions
|
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
129
|
+
the conditions stated in this License.
|
|
130
|
+
|
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
134
|
+
this License, without any additional terms or conditions.
|
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
136
|
+
the terms of any separate license agreement you may have executed
|
|
137
|
+
with Licensor regarding such Contributions.
|
|
138
|
+
|
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
141
|
+
except as required for reasonable and customary use in describing the
|
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
143
|
+
|
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
|
153
|
+
|
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
|
159
|
+
incidental, or consequential damages of any character arising as a
|
|
160
|
+
result of this License or out of the use or inability to use the
|
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
163
|
+
other commercial damages or losses), even if such Contributor
|
|
164
|
+
has been advised of the possibility of such damages.
|
|
165
|
+
|
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
169
|
+
or other liability obligations and/or rights consistent with this
|
|
170
|
+
License. However, in accepting such obligations, You may act only
|
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
175
|
+
of your accepting any such warranty or additional liability.
|
|
176
|
+
|
|
177
|
+
END OF TERMS AND CONDITIONS
|
|
178
|
+
|
|
179
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
180
|
+
|
|
181
|
+
To apply the Apache License to your work, attach the following
|
|
182
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
183
|
+
replaced with your own identifying information. (Don't include
|
|
184
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
185
|
+
comment syntax for the file format. We also recommend that a
|
|
186
|
+
file or class name and description of purpose be included on the
|
|
187
|
+
same "printed page" as the copyright notice for easier
|
|
188
|
+
identification within third-party archives.
|
|
189
|
+
|
|
190
|
+
Copyright [yyyy] [name of copyright owner]
|
|
191
|
+
|
|
192
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
193
|
+
you may not use this file except in compliance with the License.
|
|
194
|
+
You may obtain a copy of the License at
|
|
195
|
+
|
|
196
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
197
|
+
|
|
198
|
+
Unless required by applicable law or agreed to in writing, software
|
|
199
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
200
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
201
|
+
See the License for the specific language governing permissions and
|
|
202
|
+
limitations under the License.
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: schemadriftgate
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Governed schema mapping for sender-controlled spreadsheets: deterministic first, model last, nothing published without a recorded human decision.
|
|
5
|
+
Author: Daniel De Brun
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/daniel-debrun/schemagate
|
|
8
|
+
Keywords: schema-matching,data-governance,etl,llm,data-quality
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Topic :: Database
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
Requires-Dist: pydantic>=2.5
|
|
21
|
+
Requires-Dist: PyYAML>=6.0
|
|
22
|
+
Provides-Extra: anthropic
|
|
23
|
+
Requires-Dist: anthropic>=0.40; extra == "anthropic"
|
|
24
|
+
Provides-Extra: openai
|
|
25
|
+
Requires-Dist: openai>=1.40; extra == "openai"
|
|
26
|
+
Provides-Extra: bench-agent
|
|
27
|
+
Requires-Dist: mcp<3,>=2.2; extra == "bench-agent"
|
|
28
|
+
Provides-Extra: excel
|
|
29
|
+
Requires-Dist: openpyxl>=3.1; extra == "excel"
|
|
30
|
+
Provides-Extra: duckdb
|
|
31
|
+
Requires-Dist: duckdb>=1.1; extra == "duckdb"
|
|
32
|
+
Provides-Extra: postgres
|
|
33
|
+
Requires-Dist: psycopg[binary]>=3.1; extra == "postgres"
|
|
34
|
+
Provides-Extra: dev
|
|
35
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
36
|
+
Requires-Dist: hypothesis>=6.100; extra == "dev"
|
|
37
|
+
Requires-Dist: ruff>=0.5; extra == "dev"
|
|
38
|
+
Requires-Dist: openpyxl>=3.1; extra == "dev"
|
|
39
|
+
Requires-Dist: duckdb>=1.1; extra == "dev"
|
|
40
|
+
Dynamic: license-file
|
|
41
|
+
|
|
42
|
+
# schemagate
|
|
43
|
+
|
|
44
|
+
Governed schema mapping for spreadsheets and CSVs that arrive in someone else's layout.
|
|
45
|
+
Deterministic resolution first, the model last, and nothing reaches a published table without a
|
|
46
|
+
recorded human decision.
|
|
47
|
+
|
|
48
|
+
## The problem
|
|
49
|
+
|
|
50
|
+
Companies receive recurring files from many senders: suppliers, clinical sites, stores, carriers.
|
|
51
|
+
Each sender uses its own column names, puts title rows above the header, splits data across sheets,
|
|
52
|
+
and changes the layout without telling anyone. Analysts re-map the same columns by hand every month.
|
|
53
|
+
|
|
54
|
+
Automation tends to fail in one of two ways. Tools that hand every column to a model produce mappings
|
|
55
|
+
that look confident and are occasionally wrong, and a wrong mapping in a shared table is worse than a
|
|
56
|
+
missing one because nobody notices it. Tools that stop at a mapping UI never get the data into the
|
|
57
|
+
warehouse, so the manual work just moves.
|
|
58
|
+
|
|
59
|
+
schemagate is a small pipeline and governance store that sits between those files and a warehouse
|
|
60
|
+
table. It resolves what can be resolved from facts, asks a model only about the rest, records who
|
|
61
|
+
decided what and why, and refuses to write a table from a mapping that was not approved.
|
|
62
|
+
|
|
63
|
+
## Where it fits
|
|
64
|
+
|
|
65
|
+
schemagate is a governance layer, not a better matcher. Research matchers are more accurate at
|
|
66
|
+
proposing column correspondences. What schemagate adds is everything around the proposal: who is
|
|
67
|
+
allowed to trust it, the record of that decision, what happens when the sender's layout changes, and
|
|
68
|
+
the refusal to load data without approval.
|
|
69
|
+
|
|
70
|
+
| Tool | What it does | Relation to schemagate |
|
|
71
|
+
|---|---|---|
|
|
72
|
+
| [Valentine](https://github.com/delftdata/valentine), [Magneto](https://github.com/VIDA-NYU/magneto-matcher) | Schema-matching algorithms and benchmarks (Magneto combines small and large language models) | Stronger matchers. They produce ranked correspondences but have no approval, audit, drift, or load step. A matcher like these could sit behind the model tier as a provider. |
|
|
73
|
+
| [bdi-kit](https://github.com/VIDA-NYU/bdi-kit) | Matching plus value mapping and materialization for biomedical data integration | Closest on the pipeline side. It has no approval gate or tamper-evident decision record. |
|
|
74
|
+
| Flatfile, OneSchema (commercial); Impler, YoBulk (open source) | Embedded importers where an end user maps columns while uploading | Built for one-off uploads by the person who owns the file. schemagate is for recurring feeds from third parties into a shared table, where the reviewer is not the uploader. |
|
|
75
|
+
| Great Expectations, data-contract tools | Validate data against expectations and detect drift | Validate data once columns are known. They do not decide which source column is which target field. |
|
|
76
|
+
|
|
77
|
+
## Principles
|
|
78
|
+
|
|
79
|
+
1. **Deterministic resolution first.** Exact names, a maintained alias dictionary and token-level
|
|
80
|
+
synonyms run before any model. The model only sees columns those could not resolve, and it is told
|
|
81
|
+
which targets are already taken.
|
|
82
|
+
2. **A model proposal cannot outrank a verified fact.** Confidence tiers are exact 0.95, dictionary
|
|
83
|
+
0.90, synonym 0.85; model proposals are clamped to 0.75. The tiers are configurable, but
|
|
84
|
+
`ConfidencePolicy` rejects any configuration where the cap is not strictly below every deterministic
|
|
85
|
+
tier. Collisions go to the highest confidence, then the stronger tier.
|
|
86
|
+
3. **Nothing is published without a recorded approval.** The materializer re-derives approval from the
|
|
87
|
+
approval rows and the audit log before generating any SQL, and raises `UnapprovedSpecError`
|
|
88
|
+
otherwise. Model-originated proposals need an explicit per-column accept.
|
|
89
|
+
4. **Drift detection is separate from remediation.** Detecting that a sender changed its layout blocks
|
|
90
|
+
the load and records events. Fixing it is a separate command that proposes a new spec version, which
|
|
91
|
+
is approved and then switched to explicitly.
|
|
92
|
+
5. **Every decision is auditable.** Each proposal records the proposer (resolver or model id), rationale,
|
|
93
|
+
cited evidence, prompt version and raw model confidence; approvals, overrides, demotions, switches
|
|
94
|
+
and loads land in an append-only, hash-chained audit log.
|
|
95
|
+
|
|
96
|
+
## Quickstart
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
pip install schemadriftgate # import name and CLI: schemagate
|
|
100
|
+
pip install 'schemadriftgate[excel,duckdb]' # extras: excel, duckdb, postgres, anthropic, openai
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
From source, with the example walkthrough:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
python -m venv .venv && . .venv/bin/activate
|
|
107
|
+
pip install -e '.[dev]' # extras: excel, duckdb, postgres, anthropic, openai
|
|
108
|
+
bash examples/walkthrough.sh # full run on the synthetic example data, into ./walkthrough-run
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The same flow by hand for one sender (run from the repository root; ids are the ones this sequence
|
|
112
|
+
produces on a fresh project):
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
schemagate -C proj init --schema examples/schemas/supplier_invoice.yaml
|
|
116
|
+
schemagate -C proj --actor dana ingest --feed cedar --schema supplier_invoice \
|
|
117
|
+
examples/data/cedar/cedar_export_2026-07.csv
|
|
118
|
+
schemagate -C proj --actor dana propose --feed cedar # offline heuristic provider by default
|
|
119
|
+
schemagate -C proj review --spec 1 # rationale per column; * = needs a decision
|
|
120
|
+
schemagate -C proj --actor omar accept 4 6 8 10 11 12 13 # the model-tier proposals
|
|
121
|
+
schemagate -C proj --actor omar approve 1 # dana (the proposer) would be refused
|
|
122
|
+
schemagate -C proj --actor omar materialize --feed cedar # upsert into the SQLite warehouse
|
|
123
|
+
|
|
124
|
+
schemagate -C proj --actor dana ingest --feed cedar examples/data/cedar/cedar_export_2026-08.csv
|
|
125
|
+
schemagate -C proj drift --feed cedar # rename + date format change: breaking
|
|
126
|
+
schemagate -C proj --actor dana drift --feed cedar --remediate # proposes expand spec 2
|
|
127
|
+
schemagate -C proj --actor omar accept 23 # the detected rename
|
|
128
|
+
schemagate -C proj --actor omar approve 2
|
|
129
|
+
schemagate -C proj --actor lee switch 2
|
|
130
|
+
schemagate -C proj --actor omar materialize --feed cedar
|
|
131
|
+
schemagate -C proj materialize --feed cedar --object 2 --dialect snowflake # SQL text only
|
|
132
|
+
schemagate -C proj audit verify
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
[`examples/walkthrough.md`](examples/walkthrough.md) is the unedited output of a full run: three
|
|
136
|
+
senders with different layouts feeding one `invoice_lines` table, the two-approver rule kicking in once
|
|
137
|
+
the table is shared, and one sender whose month-2 export renames a column, changes its date format and
|
|
138
|
+
adds a column.
|
|
139
|
+
|
|
140
|
+
Using Claude instead of the offline heuristic:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
pip install -e '.[anthropic]'
|
|
144
|
+
export ANTHROPIC_API_KEY=...
|
|
145
|
+
schemagate -C proj propose --feed cedar --provider anthropic # default model id: claude-sonnet-5
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Architecture
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
files ─► ingest ──────────► profile ──► resolve ───────────────────────────► store ───────► materialize
|
|
152
|
+
CSV: encoding, type, exact ─► dictionary ─► synonym ─► pending spec plan (verified
|
|
153
|
+
delimiter sniff null rate, │ value-compat veto/downgrade proposals approval, drift
|
|
154
|
+
XLSX: all sheets distinct, ▼ approvals and key checks)
|
|
155
|
+
header-row score samples, model (only unresolved columns; audit chain ─► SQLite / DuckDB
|
|
156
|
+
SHA-256 skip patterns, strict JSON validation, cap) drift events (executed)
|
|
157
|
+
lineage columns parse rates ▼ ─► Postgres /
|
|
158
|
+
collisions ─► ext_ columns Snowflake /
|
|
159
|
+
Databricks (text)
|
|
160
|
+
new file ─► drift detect (events only) ──► drift --remediate (expand spec) ──► approve ──► switch
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
| module | responsibility |
|
|
164
|
+
|---|---|
|
|
165
|
+
| `schemagate.ingest` | CSV/XLSX readers, header-row detection (scores string-ness, label uniqueness, row fill and type consistency of the rows below), dedup of header names, content hash, lineage, column profiles |
|
|
166
|
+
| `schemagate.schema` | YAML target schemas (type, description, required, unit, allowed values, pattern, aliases), name normalization (case, punctuation, camelCase, unit suffixes such as `(USD)` or `%`), synonym vocabulary |
|
|
167
|
+
| `schemagate.resolve` | resolver chain, value-compatibility checks, collision handling |
|
|
168
|
+
| `schemagate.llm` | provider protocol; heuristic, Anthropic, OpenAI and fake providers; versioned prompt template; response validation |
|
|
169
|
+
| `schemagate.store` | SQLite (default) or Postgres governance store; `GovernanceService` is the only write path |
|
|
170
|
+
| `schemagate.drift` | drift detection and Expand remediation |
|
|
171
|
+
| `schemagate.materialize` | approval-gated plans, dialect SQL generation, SQLite/DuckDB execution |
|
|
172
|
+
| `schemagate.cli` | `init`, `ingest`, `propose`, `review`, `accept`, `demote`, `override`, `approve`, `reject`, `switch`, `materialize`, `drift`, `audit`, `status` |
|
|
173
|
+
|
|
174
|
+
Every ingested value is kept as text in a raw staging table (`raw_<feed>_<object>`) with
|
|
175
|
+
`_source_file`, `_sheet`, `_row_number`, `_content_hash` and `_ingested_at`. Typing happens in the
|
|
176
|
+
generated SQL, using the value formats observed in the approved profile (currency symbols, thousands
|
|
177
|
+
separators, percent signs, accounting negatives, date layouts). Unmapped columns and collision losers
|
|
178
|
+
are kept as `ext_<name>` text columns rather than dropped.
|
|
179
|
+
|
|
180
|
+
Value checks run against every proposal, deterministic or not: a header that matches `invoice_date`
|
|
181
|
+
exactly but whose values do not parse as dates is vetoed and passed on; a partial fit (50-90% of values)
|
|
182
|
+
lowers confidence by 0.15.
|
|
183
|
+
|
|
184
|
+
More detail: [docs/governance.md](docs/governance.md), [docs/drift.md](docs/drift.md),
|
|
185
|
+
[docs/materialize.md](docs/materialize.md), [docs/providers.md](docs/providers.md),
|
|
186
|
+
[docs/benchmark.md](docs/benchmark.md).
|
|
187
|
+
|
|
188
|
+
## Governance model
|
|
189
|
+
|
|
190
|
+
- A **feed** is one sender stream bound to one target schema. A **spec** is a versioned mapping for a
|
|
191
|
+
feed; it starts `pending`, becomes `approved`, and is made active either automatically (the feed's
|
|
192
|
+
first approved spec) or with `switch`.
|
|
193
|
+
- **Who can approve.** The proposer cannot approve their own spec or review its proposals. Each person
|
|
194
|
+
counts once. A table private to one feed needs one approver; a table marked `published: true`, or one
|
|
195
|
+
that already has an approved spec from another feed, needs two distinct approvers. The requirement is
|
|
196
|
+
re-evaluated at each approval.
|
|
197
|
+
- **Per-column decisions.** Model and rename proposals must be accepted, overridden (recorded as tier
|
|
198
|
+
`human` with a reason) or demoted to an extension column before the spec can be approved.
|
|
199
|
+
- **Enforcement.** `build_plan` calls `verify_approved`, which counts approval rows from distinct
|
|
200
|
+
non-proposers against the required number and requires a `spec.approved` audit entry. A spec whose
|
|
201
|
+
status was edited to `approved` directly in the database is still refused. Open breaking drift or
|
|
202
|
+
missing mapped columns raise `DriftBlockedError`.
|
|
203
|
+
- **Audit.** Every transition is written in the same transaction as its audit entry. Entries are
|
|
204
|
+
hash-chained and the table rejects UPDATE and DELETE via triggers. `schemagate audit verify` recomputes
|
|
205
|
+
the chain and reports the first bad entry; `--expect-head` compares against an externally recorded
|
|
206
|
+
head hash to detect truncation.
|
|
207
|
+
|
|
208
|
+
## Benchmark
|
|
209
|
+
|
|
210
|
+
Synthetic, reproducible, and described in full in [docs/benchmark.md](docs/benchmark.md). 200 sender
|
|
211
|
+
variants over four target schemas (supplier invoices, clinical trial site reports, retail POS lines,
|
|
212
|
+
freight shipments): 2971 source columns, 2444 with a true target and 527 decoys. Headers are
|
|
213
|
+
perturbed with registered aliases, held-out paraphrases, abbreviations, casing, unit suffixes, word
|
|
214
|
+
reversal, typos and foreign-language tokens; value formats vary per variant.
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
python -m benchmarks.run --variants 50 --seed 7 --out benchmarks/results # 55 s to 1 min 45 s in our runs (single process)
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
| config | deterministic resolution | precision | recall | decoy FP rate | gate queue (conf >= 0.85) | wrong in gate queue |
|
|
221
|
+
|---|---|---|---|---|---|---|
|
|
222
|
+
| schemagate (chain + heuristic model, cap 0.75) | 0.425 | 0.938 | 0.883 | 0.076 | 1039 | 0 |
|
|
223
|
+
| heuristic model only, uncapped | 0.000 | 0.938 | 0.883 | 0.076 | 954 | 0 |
|
|
224
|
+
| schemagate, simulated overconfident model (+0.25) | 0.425 | 0.938 | 0.883 | 0.076 | 1039 | 0 |
|
|
225
|
+
| overconfident model only, uncapped | 0.000 | 0.938 | 0.883 | 0.076 | 1507 | 8 |
|
|
226
|
+
|
|
227
|
+
By tier in the schemagate run: exact 336 mappings, dictionary 412, synonym 291, all correct; model 1261
|
|
228
|
+
mappings at 0.887 precision (about 143 wrong, all of which sit below the gate and require a per-column
|
|
229
|
+
decision). Recall by perturbation ranges from 0.99 for registered aliases to 0.78 for paraphrases and
|
|
230
|
+
0.68 for foreign-language tokens. Full tables: [benchmarks/results/results.md](benchmarks/results/results.md).
|
|
231
|
+
|
|
232
|
+
How to read this honestly:
|
|
233
|
+
|
|
234
|
+
- **Final accuracy is identical across configurations.** The heuristic provider scores headers against
|
|
235
|
+
the same names, aliases and synonyms the deterministic tiers use, so on this suite it reaches the same
|
|
236
|
+
final mappings. The chain does not make matching smarter; it changes where decisions come from and
|
|
237
|
+
what they are allowed to claim.
|
|
238
|
+
- **What the chain does change:** 42.5% of mappable columns never reach a model (with an LLM provider,
|
|
239
|
+
that is the share of columns you do not pay for or have to trust), and every high-confidence mapping is
|
|
240
|
+
backed by a name, alias or synonym match with a stated rationale.
|
|
241
|
+
- **What the cap does:** with a well-calibrated model the uncapped gate queue is also clean. With a
|
|
242
|
+
simulated miscalibrated model, 8 wrong mappings enter the batch-approval queue in the uncapped setup
|
|
243
|
+
and none do with the cap. The price is review effort: the capped setup sends all 1261 model mappings to
|
|
244
|
+
individual review, versus 793 in the overconfident uncapped one.
|
|
245
|
+
- The overconfidence is simulated with a fixed +0.25 offset, not measured on a real LLM. The generator's
|
|
246
|
+
abbreviations overlap with the built-in synonym table and a quarter of headers are registered aliases,
|
|
247
|
+
which favours the deterministic tiers on those headers.
|
|
248
|
+
- These numbers use the offline heuristic as the model tier. Results with Claude Haiku 4.5 as the model
|
|
249
|
+
tier are below.
|
|
250
|
+
|
|
251
|
+
### Valentine (real, third-party data)
|
|
252
|
+
|
|
253
|
+
The same resolver chain on all 551 dataset pairs of the Valentine schema-matching benchmark (Koutras
|
|
254
|
+
et al., ICDE 2021: TPC-DI, OpenData, ChEMBL, Magellan, Wikidata). These tables were not written by
|
|
255
|
+
this project. The target schemas are built from the target tables' column names with no aliases, so
|
|
256
|
+
the dictionary tier contributes nothing. Reproduce with `python -m benchmarks.valentine
|
|
257
|
+
<Valentine-datasets> --out benchmarks/results` (43 s per configuration).
|
|
258
|
+
|
|
259
|
+
| config | matchable columns | decoys | precision | recall | decoy FP rate | gate queue (conf >= 0.85) | wrong in gate queue |
|
|
260
|
+
|---|---|---|---|---|---|---|---|
|
|
261
|
+
| schemagate (chain + heuristic model, cap 0.75) | 8683 | 4270 | 0.777 | 0.566 | 0.266 | 1959 | 0 |
|
|
262
|
+
| heuristic model only, uncapped | 8683 | 4270 | 0.777 | 0.566 | 0.266 | 1636 | 0 |
|
|
263
|
+
|
|
264
|
+
- **Deterministic tiers held up on real data.** Exact and synonym matches made 1971 mappings, all
|
|
265
|
+
correct, and they covered 22.7% of matchable columns. The heuristic model's 4357 mappings were
|
|
266
|
+
67.6% correct. Every one of those sits below the gate and needs a per-column decision, which is what
|
|
267
|
+
the cap is for.
|
|
268
|
+
- **The matcher is weak, as expected.** Recall is 0.57, and on the ChEMBL pairs precision is about
|
|
269
|
+
0.55, because the offline heuristic only compares names. This is the case for plugging a stronger
|
|
270
|
+
matcher or an LLM into the model tier; the governance around it does not change.
|
|
271
|
+
- As on the synthetic suite, final accuracy is the same with and without the chain, and the heuristic
|
|
272
|
+
was not overconfident here, so the cap kept no wrong mappings out of the queue on this data. Per-group
|
|
273
|
+
results: [benchmarks/results/valentine.md](benchmarks/results/valentine.md).
|
|
274
|
+
|
|
275
|
+
### Claude Haiku 4.5 as the model tier
|
|
276
|
+
|
|
277
|
+
The same chain and 0.75 cap with `claude-haiku-4-5` answering the model tier, compared with the
|
|
278
|
+
heuristic on the same variants: 20 synthetic variants and 28 Valentine pairs (2 per dataset group).
|
|
279
|
+
|
|
280
|
+
| suite | model tier | precision | recall | decoy FP rate | model-tier precision | wrong in gate queue | confident model mappings held by cap | wrong among held |
|
|
281
|
+
|---|---|---|---|---|---|---|---|---|
|
|
282
|
+
| Valentine | heuristic | 0.617 | 0.850 | 0.412 | 0.529 (n=261) | 0 | 13 | 0 |
|
|
283
|
+
| Valentine | claude-haiku-4-5 | 0.916 | 0.888 | 0.066 | 0.885 (n=166) | 0 | 147 | 6 |
|
|
284
|
+
| synthetic | heuristic | 0.935 | 0.886 | 0.082 | 0.876 (n=121) | 0 | 0 | 0 |
|
|
285
|
+
| synthetic | claude-haiku-4-5 | 0.992 | 1.000 | 0.041 | 0.985 (n=136) | 0 | 134 | 0 |
|
|
286
|
+
|
|
287
|
+
- **Haiku is a much stronger model tier on real data.** On Valentine its mappings were 88.5% correct,
|
|
288
|
+
against 52.9% for the heuristic, and the share of decoy columns wrongly mapped fell from 41% to 7%.
|
|
289
|
+
- **The cap caught real overconfidence.** On Valentine, Haiku gave 6 wrong mappings a confidence of
|
|
290
|
+
0.85 or more, enough for batch approval. The cap sent them to per-column review, and the batch queue
|
|
291
|
+
stayed free of errors.
|
|
292
|
+
- **The price is review effort.** 147 confident Haiku mappings on Valentine went to individual review;
|
|
293
|
+
141 of them were right.
|
|
294
|
+
- The sample is small (28 of the 551 Valentine pairs) and comes from a single run. Full tables:
|
|
295
|
+
[benchmarks/results/haiku.md](benchmarks/results/haiku.md).
|
|
296
|
+
|
|
297
|
+
## Tests
|
|
298
|
+
|
|
299
|
+
```bash
|
|
300
|
+
pytest # 144 tests, no network or API keys
|
|
301
|
+
ruff check .
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Covered: normalization, header detection on messy sheets, CSV sniffing and XLSX multi-sheet ingest,
|
|
305
|
+
each resolver, the confidence-cap invariant (Hypothesis property tests over random model responses and
|
|
306
|
+
collisions), collision demotion, approval policy (self-approval, duplicate approver, two-approver rule,
|
|
307
|
+
published tables, per-column review of model proposals), audit chain verification and tamper
|
|
308
|
+
detection, drift cases including rename and PSI, Expand remediation and Switch gating, the materializer
|
|
309
|
+
refusing unapproved specs and writing correctly typed rows in SQLite and DuckDB, SQL generation for
|
|
310
|
+
all dialects, response validation with a fake provider (invalid JSON, hallucinated target, over-cap
|
|
311
|
+
confidence), and the CLI end to end on the example data.
|
|
312
|
+
|
|
313
|
+
## Limitations
|
|
314
|
+
|
|
315
|
+
- **Identity is asserted, not authenticated.** Approver distinctness is by actor string (compared
|
|
316
|
+
case-insensitively with whitespace collapsed, so `Dana ` and `dana` are one person). Put the CLI or
|
|
317
|
+
service behind something that sets the actor from a real identity before relying on it.
|
|
318
|
+
- **Postgres, Snowflake and Databricks SQL is generated, not executed** in this repository. The Postgres
|
|
319
|
+
governance store backend is implemented but not exercised by the test suite. Only SQLite and DuckDB
|
|
320
|
+
are run.
|
|
321
|
+
- **Header detection handles one header row.** Multi-row headers (merged group labels above column
|
|
322
|
+
labels) and pivoted/cross-tab layouts are not reconstructed.
|
|
323
|
+
- **Value parsing is locale-light.** Decimal commas (`1.234,50`, `2,5`) are not supported and load as
|
|
324
|
+
NULL rather than as a wrong number; ambiguous
|
|
325
|
+
day/month dates default to month-first when every value fits both.
|
|
326
|
+
- **The heuristic provider is a baseline**, not a language model. It does poorly on paraphrases and
|
|
327
|
+
other languages, as the benchmark shows.
|
|
328
|
+
- **Backfill and Contract are manual.** Expand and Switch are implemented and gated; see
|
|
329
|
+
[docs/drift.md](docs/drift.md).
|
|
330
|
+
- **Single-process SQLite.** The store uses `BEGIN IMMEDIATE` transactions and is fine for a team's
|
|
331
|
+
CLI usage, not for a multi-writer service.
|
|
332
|
+
- **Benchmark data is synthetic** and generated by the same author as the resolvers.
|
|
333
|
+
|
|
334
|
+
## Roadmap
|
|
335
|
+
|
|
336
|
+
- Run the model-tier benchmark on the full Valentine suite and more models, and publish calibration
|
|
337
|
+
per perturbation type.
|
|
338
|
+
- Execute generated SQL against Postgres in CI (service container) and add a DuckDB-backed warehouse
|
|
339
|
+
option to the CLI.
|
|
340
|
+
- Backfill planner: find source objects whose layout matches a new spec version and re-land them.
|
|
341
|
+
- Multi-row header reconstruction and decimal-comma locales.
|
|
342
|
+
- Reviewer UX: a small web view over `review` with sample values side by side, and a queue for
|
|
343
|
+
accepting proposals in bulk that never includes model-tier items.
|
|
344
|
+
- Pluggable identity for actors (OIDC token subject) and signed audit heads.
|
|
345
|
+
- Plug a dedicated matcher such as Magneto into the model tier and compare it with Claude Haiku 4.5.
|
|
346
|
+
|
|
347
|
+
## License
|
|
348
|
+
|
|
349
|
+
Apache-2.0. See [LICENSE](LICENSE).
|