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.
Files changed (68) hide show
  1. schemadriftgate-0.1.0/LICENSE +202 -0
  2. schemadriftgate-0.1.0/PKG-INFO +349 -0
  3. schemadriftgate-0.1.0/README.md +308 -0
  4. schemadriftgate-0.1.0/pyproject.toml +67 -0
  5. schemadriftgate-0.1.0/setup.cfg +4 -0
  6. schemadriftgate-0.1.0/src/schemadriftgate.egg-info/PKG-INFO +349 -0
  7. schemadriftgate-0.1.0/src/schemadriftgate.egg-info/SOURCES.txt +66 -0
  8. schemadriftgate-0.1.0/src/schemadriftgate.egg-info/dependency_links.txt +1 -0
  9. schemadriftgate-0.1.0/src/schemadriftgate.egg-info/entry_points.txt +2 -0
  10. schemadriftgate-0.1.0/src/schemadriftgate.egg-info/requires.txt +27 -0
  11. schemadriftgate-0.1.0/src/schemadriftgate.egg-info/top_level.txt +1 -0
  12. schemadriftgate-0.1.0/src/schemagate/__init__.py +25 -0
  13. schemadriftgate-0.1.0/src/schemagate/cli.py +423 -0
  14. schemadriftgate-0.1.0/src/schemagate/config.py +43 -0
  15. schemadriftgate-0.1.0/src/schemagate/drift/__init__.py +21 -0
  16. schemadriftgate-0.1.0/src/schemagate/drift/detect.py +163 -0
  17. schemadriftgate-0.1.0/src/schemagate/drift/remediate.py +80 -0
  18. schemadriftgate-0.1.0/src/schemagate/errors.py +38 -0
  19. schemadriftgate-0.1.0/src/schemagate/ingest/__init__.py +15 -0
  20. schemadriftgate-0.1.0/src/schemagate/ingest/header.py +71 -0
  21. schemadriftgate-0.1.0/src/schemagate/ingest/loader.py +149 -0
  22. schemadriftgate-0.1.0/src/schemagate/ingest/profile.py +133 -0
  23. schemadriftgate-0.1.0/src/schemagate/ingest/readers.py +94 -0
  24. schemadriftgate-0.1.0/src/schemagate/ingest/values.py +214 -0
  25. schemadriftgate-0.1.0/src/schemagate/llm/__init__.py +41 -0
  26. schemadriftgate-0.1.0/src/schemagate/llm/anthropic_provider.py +39 -0
  27. schemadriftgate-0.1.0/src/schemagate/llm/base.py +89 -0
  28. schemadriftgate-0.1.0/src/schemagate/llm/fake.py +29 -0
  29. schemadriftgate-0.1.0/src/schemagate/llm/heuristic.py +135 -0
  30. schemadriftgate-0.1.0/src/schemagate/llm/openai_provider.py +36 -0
  31. schemadriftgate-0.1.0/src/schemagate/llm/prompts/map_columns_v1.txt +26 -0
  32. schemadriftgate-0.1.0/src/schemagate/llm/prompts.py +35 -0
  33. schemadriftgate-0.1.0/src/schemagate/llm/validation.py +95 -0
  34. schemadriftgate-0.1.0/src/schemagate/materialize/__init__.py +24 -0
  35. schemadriftgate-0.1.0/src/schemagate/materialize/dialects.py +343 -0
  36. schemadriftgate-0.1.0/src/schemagate/materialize/executor.py +98 -0
  37. schemadriftgate-0.1.0/src/schemagate/materialize/plan.py +98 -0
  38. schemadriftgate-0.1.0/src/schemagate/models.py +67 -0
  39. schemadriftgate-0.1.0/src/schemagate/project.py +152 -0
  40. schemadriftgate-0.1.0/src/schemagate/resolve/__init__.py +22 -0
  41. schemadriftgate-0.1.0/src/schemagate/resolve/base.py +42 -0
  42. schemadriftgate-0.1.0/src/schemagate/resolve/collisions.py +63 -0
  43. schemadriftgate-0.1.0/src/schemagate/resolve/compat.py +53 -0
  44. schemadriftgate-0.1.0/src/schemagate/resolve/deterministic.py +136 -0
  45. schemadriftgate-0.1.0/src/schemagate/resolve/model.py +65 -0
  46. schemadriftgate-0.1.0/src/schemagate/resolve/pipeline.py +105 -0
  47. schemadriftgate-0.1.0/src/schemagate/schema/__init__.py +20 -0
  48. schemadriftgate-0.1.0/src/schemagate/schema/data/synonyms.yaml +66 -0
  49. schemadriftgate-0.1.0/src/schemagate/schema/normalize.py +85 -0
  50. schemadriftgate-0.1.0/src/schemagate/schema/target.py +106 -0
  51. schemadriftgate-0.1.0/src/schemagate/schema/vocabulary.py +48 -0
  52. schemadriftgate-0.1.0/src/schemagate/store/__init__.py +5 -0
  53. schemadriftgate-0.1.0/src/schemagate/store/audit.py +110 -0
  54. schemadriftgate-0.1.0/src/schemagate/store/db.py +221 -0
  55. schemadriftgate-0.1.0/src/schemagate/store/service.py +472 -0
  56. schemadriftgate-0.1.0/tests/test_audit.py +78 -0
  57. schemadriftgate-0.1.0/tests/test_benchmark.py +65 -0
  58. schemadriftgate-0.1.0/tests/test_cli.py +96 -0
  59. schemadriftgate-0.1.0/tests/test_collisions.py +39 -0
  60. schemadriftgate-0.1.0/tests/test_confidence_cap.py +99 -0
  61. schemadriftgate-0.1.0/tests/test_drift.py +136 -0
  62. schemadriftgate-0.1.0/tests/test_governance.py +165 -0
  63. schemadriftgate-0.1.0/tests/test_hardening.py +95 -0
  64. schemadriftgate-0.1.0/tests/test_ingest.py +158 -0
  65. schemadriftgate-0.1.0/tests/test_llm_validation.py +155 -0
  66. schemadriftgate-0.1.0/tests/test_materialize.py +145 -0
  67. schemadriftgate-0.1.0/tests/test_normalize.py +48 -0
  68. 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).