deidkit 0.1.0__tar.gz → 0.1.2__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.
- {deidkit-0.1.0/src/deidkit.egg-info → deidkit-0.1.2}/PKG-INFO +123 -117
- {deidkit-0.1.0 → deidkit-0.1.2}/README.md +119 -113
- {deidkit-0.1.0 → deidkit-0.1.2}/pyproject.toml +4 -5
- {deidkit-0.1.0 → deidkit-0.1.2}/src/deidkit/api.py +21 -1
- {deidkit-0.1.0 → deidkit-0.1.2}/src/deidkit/detect/pipeline.py +13 -6
- {deidkit-0.1.0 → deidkit-0.1.2}/src/deidkit/io.py +14 -3
- {deidkit-0.1.0 → deidkit-0.1.2}/src/deidkit/version.py +1 -1
- {deidkit-0.1.0 → deidkit-0.1.2/src/deidkit.egg-info}/PKG-INFO +123 -117
- {deidkit-0.1.0 → deidkit-0.1.2}/src/deidkit.egg-info/SOURCES.txt +1 -0
- deidkit-0.1.2/tests/test_qa_hardening.py +89 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/LICENSE +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/MANIFEST.in +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/examples/benchmark.py +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/examples/make_sample.py +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/examples/policy.example.yaml +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/examples/policy.meridian.yaml +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/examples/quickstart.md +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/setup.cfg +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/src/deidkit/__init__.py +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/src/deidkit/__main__.py +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/src/deidkit/cli.py +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/src/deidkit/config.py +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/src/deidkit/data/NOTICES.md +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/src/deidkit/data/context_triggers_en.txt +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/src/deidkit/data/context_triggers_es.txt +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/src/deidkit/data/en_given_names.txt +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/src/deidkit/data/en_surnames.txt +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/src/deidkit/data/es_given_names.txt +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/src/deidkit/data/es_surnames.txt +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/src/deidkit/data/honorifics_en.txt +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/src/deidkit/data/honorifics_es.txt +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/src/deidkit/data/medical_stoplist_en.txt +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/src/deidkit/data/medical_stoplist_es.txt +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/src/deidkit/data/medical_vocab.txt +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/src/deidkit/dates.py +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/src/deidkit/detect/__init__.py +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/src/deidkit/detect/checksums.py +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/src/deidkit/detect/context.py +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/src/deidkit/detect/gazetteer.py +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/src/deidkit/detect/patterns.py +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/src/deidkit/detect/spacy_ner.py +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/src/deidkit/detect/types.py +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/src/deidkit/engine.py +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/src/deidkit/generators/__init__.py +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/src/deidkit/generators/names.py +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/src/deidkit/generators/surrogates.py +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/src/deidkit/learn.py +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/src/deidkit/mapping.py +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/src/deidkit/report.py +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/src/deidkit/resources.py +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/src/deidkit/schema.py +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/src/deidkit/secret.py +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/src/deidkit/textnorm.py +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/src/deidkit.egg-info/dependency_links.txt +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/src/deidkit.egg-info/entry_points.txt +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/src/deidkit.egg-info/requires.txt +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/src/deidkit.egg-info/top_level.txt +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/tests/test_api.py +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/tests/test_checksums.py +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/tests/test_dates.py +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/tests/test_db.py +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/tests/test_detect.py +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/tests/test_engine.py +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/tests/test_hardening.py +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/tests/test_known_phi.py +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/tests/test_learn.py +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/tests/test_medical_vocab.py +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/tests/test_names.py +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/tests/test_qa_fixes.py +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/tests/test_qa_freetext_leaks.py +0 -0
- {deidkit-0.1.0 → deidkit-0.1.2}/tests/test_schema_lang.py +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: deidkit
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: Schema-driven pseudonymization for clinical/tabular datasets: synthetic names, interval-preserving date shifting, multi-stage free-text PII detection, and before/after audit exports.
|
|
5
5
|
Author: Meridian Data
|
|
6
6
|
License: MIT
|
|
7
|
-
Project-URL: Homepage, https://github.com/
|
|
8
|
-
Project-URL: Documentation, https://github.com/
|
|
9
|
-
Project-URL: Issues, https://github.com/
|
|
7
|
+
Project-URL: Homepage, https://github.com/Meridian-Int/meridian-deid-engine
|
|
8
|
+
Project-URL: Documentation, https://github.com/Meridian-Int/meridian-deid-engine#readme
|
|
9
|
+
Project-URL: Issues, https://github.com/Meridian-Int/meridian-deid-engine/issues
|
|
10
10
|
Keywords: de-identification,pseudonymization,hipaa,phi,ner,clinical,healthcare,privacy
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.9
|
|
@@ -79,18 +79,6 @@ re-runnable.
|
|
|
79
79
|
pip install deidkit
|
|
80
80
|
```
|
|
81
81
|
|
|
82
|
-
One command, no square brackets, nothing to pick. The database drivers needed to
|
|
83
|
-
read your Azure SQL / Fabric source (SQLAlchemy + pyodbc) are bundled in, so a
|
|
84
|
-
plain `pip install deidkit` is ready to connect.
|
|
85
|
-
|
|
86
|
-
> **Heads-up — not on PyPI yet.** Until the package is published (see
|
|
87
|
-
> [Publishing](#publishing-to-pypi)), install it straight from the repository:
|
|
88
|
-
> ```bash
|
|
89
|
-
> pip install git+https://github.com/<your-org>/deidkit.git
|
|
90
|
-
> # or, from a local clone: pip install .
|
|
91
|
-
> ```
|
|
92
|
-
> Both give you the identical `deidkit` command and library.
|
|
93
|
-
|
|
94
82
|
One system-level thing is required **once**, on the machine that will connect:
|
|
95
83
|
the Microsoft **ODBC Driver 18 for SQL Server** — the actual database driver that
|
|
96
84
|
`pyodbc` talks to:
|
|
@@ -113,38 +101,46 @@ deidkit --version
|
|
|
113
101
|
|
|
114
102
|
## 2. Quick start
|
|
115
103
|
|
|
116
|
-
**
|
|
104
|
+
Your data is the **Azure SQL Database mirrored into Microsoft Fabric**, so the
|
|
105
|
+
run connects to that endpoint, de-identifies the six tables together, and writes
|
|
106
|
+
a clean copy out. That's the whole thing:
|
|
117
107
|
|
|
118
108
|
```bash
|
|
119
|
-
deidkit
|
|
109
|
+
deidkit init-secret --out secret.key # do this once; keep the file private
|
|
120
110
|
```
|
|
121
111
|
|
|
122
|
-
Reads every table in `data/`, de-identifies it, and writes two folders:
|
|
123
|
-
`data_deid/` (ship this) and `data_deid-PRIVATE/` (keep this — see §5).
|
|
124
|
-
|
|
125
|
-
**Python — one call:**
|
|
126
|
-
|
|
127
112
|
```python
|
|
128
113
|
import deidkit as dk
|
|
114
|
+
from deidkit.io import read_sql_table
|
|
129
115
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
116
|
+
# connection string built from the values in section 10 (nothing writes back)
|
|
117
|
+
CONN = (
|
|
118
|
+
"mssql+pyodbc://@<sql-endpoint>.datawarehouse.fabric.microsoft.com/<database>"
|
|
119
|
+
"?driver=ODBC+Driver+18+for+SQL+Server&authentication=ActiveDirectoryInteractive"
|
|
120
|
+
)
|
|
121
|
+
TABLES = ["tbl_patient", "tbl_encounter", "tbl_notes",
|
|
122
|
+
"tbl_lab", "tbl_imaging", "tbl_medication"]
|
|
136
123
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
124
|
+
tables = {t: read_sql_table(CONN, t) for t in TABLES} # read the DB (read-only)
|
|
125
|
+
result = dk.deidentify(
|
|
126
|
+
tables, out="deid_out", # de-identify all six together
|
|
127
|
+
secret_file="secret.key",
|
|
128
|
+
lang="es",
|
|
129
|
+
entity_key="patient_id",
|
|
130
|
+
known_id_columns=["patient_id"],
|
|
131
|
+
)
|
|
132
|
+
print(result.summary)
|
|
133
|
+
# -> deid_out/ (ship this) + deid_out-PRIVATE/ (keep private — see section 5)
|
|
142
134
|
```
|
|
143
135
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
136
|
+
That reads the tables from Fabric, never writes back, and produces the two output
|
|
137
|
+
folders. Section 9 walks through the connection (plus a database-to-database
|
|
138
|
+
variant), and section 10 lists the exact connection values to put in `CONN`.
|
|
139
|
+
|
|
140
|
+
> **Handed a file export instead of database access?** If you receive the tables
|
|
141
|
+
> as files (CSV etc.) rather than a live endpoint, the same run is one CLI line —
|
|
142
|
+
> `deidkit run data/` — writing `data_deid/` and `data_deid-PRIVATE/`. See
|
|
143
|
+
> section 3.
|
|
148
144
|
|
|
149
145
|
> **CLI or code — same engine.** Everything below is shown for **both**
|
|
150
146
|
> interfaces. The rule of thumb: every CLI `--flag` is a field on the Python
|
|
@@ -163,10 +159,10 @@ mirrored into Microsoft Fabric. deidkit connects to the Fabric **SQL analytics
|
|
|
163
159
|
endpoint** (read-only), reads the six tables directly, de-identifies them, and
|
|
164
160
|
writes the clean copy out. It **never writes back** to your database.
|
|
165
161
|
|
|
166
|
-
|
|
167
|
-
SQL endpoint, the database name, and how to
|
|
168
|
-
|
|
169
|
-
|
|
162
|
+
deidkit connects on its own — it only needs the connection details for that
|
|
163
|
+
endpoint (the SQL analytics endpoint host, the database name, and how to
|
|
164
|
+
authenticate). Those go into one connection string; section 9 shows how a run
|
|
165
|
+
works and section 10 lists the exact values. Start there.
|
|
170
166
|
|
|
171
167
|
### Or: a data export as files
|
|
172
168
|
|
|
@@ -189,7 +185,7 @@ deidkit run data/ # a folder of table files
|
|
|
189
185
|
- **Column names drive the plan.** deidkit recognises clinical column names in
|
|
190
186
|
**English and Spanish** (e.g. `note_full`, `result_interpretation`,
|
|
191
187
|
`report_text`, `clinical_justification_pbs`) and classifies each one
|
|
192
|
-
automatically. You can override any decision (
|
|
188
|
+
automatically. You can override any decision (section 12). An optional
|
|
193
189
|
**data-dictionary JSON** (`--schema dict.json`) makes the classification
|
|
194
190
|
authoritative instead of heuristic.
|
|
195
191
|
|
|
@@ -212,7 +208,7 @@ force any of them with a rule.
|
|
|
212
208
|
| `redact` | blank the value | |
|
|
213
209
|
| `drop` | remove the column entirely | |
|
|
214
210
|
|
|
215
|
-
Preview the full plan before running anything with `deidkit plan` (
|
|
211
|
+
Preview the full plan before running anything with `deidkit plan` (section 6).
|
|
216
212
|
|
|
217
213
|
---
|
|
218
214
|
|
|
@@ -233,9 +229,9 @@ deid_out/ ← SHIP THIS — de-identified data only
|
|
|
233
229
|
deid_out-PRIVATE/ ← NEVER SHARE — the re-identification key
|
|
234
230
|
deidkit-secret.key ← the secret (only if it was auto-generated)
|
|
235
231
|
mapping.private.json ← value → surrogate map (reverses/extends the run)
|
|
236
|
-
deid_audit.PRIVATE.xlsx ← before/after audit workbook (see
|
|
232
|
+
deid_audit.PRIVATE.xlsx ← before/after audit workbook (see section 6)
|
|
237
233
|
deid_audit.PRIVATE_freetext_changes.csv
|
|
238
|
-
review.csv ← uncertain detections for a human (see
|
|
234
|
+
review.csv ← uncertain detections for a human (see section 7)
|
|
239
235
|
```
|
|
240
236
|
|
|
241
237
|
- **`deid_out/`** — the pseudonymized tables, same structure as the input. This
|
|
@@ -245,7 +241,7 @@ deid_out-PRIVATE/ ← NEVER SHARE — the re-identification key
|
|
|
245
241
|
|
|
246
242
|
**Output format** defaults to CSV; use `--format parquet|tsv|json` (CLI) or
|
|
247
243
|
`fmt=` (Python) to change it. Reading from a DB and want the result written back
|
|
248
|
-
to a DB? See `run-db` in
|
|
244
|
+
to a DB? See `run-db` in section 9.
|
|
249
245
|
|
|
250
246
|
---
|
|
251
247
|
|
|
@@ -316,32 +312,70 @@ print(los.describe()) # identical to the source — only absolute dates moved
|
|
|
316
312
|
|
|
317
313
|
## 7. Training it — the review → learn loop
|
|
318
314
|
|
|
319
|
-
deidkit
|
|
320
|
-
|
|
321
|
-
|
|
315
|
+
deidkit **never guesses** on an ambiguous name. Anything it detects but isn't sure
|
|
316
|
+
about (for example a lone surname that is also a common word) is left untouched
|
|
317
|
+
and listed in **`review.csv`** for a person to rule on. Confirm or reject those,
|
|
318
|
+
and the next run does better — deterministically, with no model.
|
|
319
|
+
|
|
320
|
+
`review.csv` holds only that **uncertain tail**; the confident detections are
|
|
321
|
+
already scrubbed in the output and never appear here. It is written to the
|
|
322
|
+
**private** folder on every run (`deid_out-PRIVATE/review.csv`) and contains raw
|
|
323
|
+
candidate text, so treat it as sensitive — never ship it.
|
|
324
|
+
|
|
325
|
+
### What the reviewer does
|
|
326
|
+
|
|
327
|
+
Open `deid_out-PRIVATE/review.csv` (Excel, Google Sheets, any editor). Each row is
|
|
328
|
+
one uncertain candidate, with a `context` snippet so you can judge it. Fill in the
|
|
329
|
+
**first column only** — `decision(y/n)` — and save:
|
|
330
|
+
|
|
331
|
+
| decision(y/n) | entity_type | candidate | context | table | column | row_id | confidence | detectors |
|
|
332
|
+
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
|
333
|
+
| `y` | PERSON | `Bello` | `…paciente Bello refiere dolor…` | tbl_notes | note_full | 87 | 0.6 | context |
|
|
334
|
+
| `n` | PERSON | `Cruz` | `…la Cruz Roja atendió…` | tbl_notes | note_full | 42 | 0.55 | gazetteer |
|
|
335
|
+
| *(blank)* | PERSON | `Sur` | `…zona Sur del hospital…` | tbl_notes | note_full | 91 | 0.5 | gazetteer |
|
|
336
|
+
|
|
337
|
+
- **`y`** — yes, this **is** a real person's name (it should be redacted).
|
|
338
|
+
- **`n`** — no, it's a **false positive** — not a name (e.g. "Cruz" in "Cruz Roja").
|
|
339
|
+
- **blank** — undecided; that row is skipped and simply stays for a later pass.
|
|
340
|
+
|
|
341
|
+
That is the whole reviewer job: read the `context`, type `y` or `n`, save the
|
|
342
|
+
file. (Lenient spellings work too: `yes` / `1` / `true` = yes; `no` / `0` /
|
|
343
|
+
`false` / `fp` = no.)
|
|
344
|
+
|
|
345
|
+
### The four steps
|
|
322
346
|
|
|
323
347
|
```bash
|
|
324
|
-
# 1. Run
|
|
325
|
-
deidkit run
|
|
348
|
+
# 1. Run. Uncertain detections are written to deid_out-PRIVATE/review.csv.
|
|
349
|
+
deidkit run <input> deid_out
|
|
326
350
|
|
|
327
|
-
# 2. A
|
|
328
|
-
# y = real PII (learn it as a name → higher recall next time)
|
|
329
|
-
# n = false positive (learn it as a stopword → higher precision next time)
|
|
351
|
+
# 2. A reviewer opens deid_out-PRIVATE/review.csv and marks each row y / n (above).
|
|
330
352
|
|
|
331
|
-
# 3. Fold those decisions into
|
|
353
|
+
# 3. Fold those decisions into a "learn" directory.
|
|
332
354
|
deidkit learn --review deid_out-PRIVATE/review.csv --learn-dir learned/
|
|
333
355
|
|
|
334
|
-
# 4. Re-run
|
|
335
|
-
|
|
356
|
+
# 4. Re-run pointing at what it learned: confirmed names are now caught
|
|
357
|
+
# automatically, and rejected candidates are suppressed.
|
|
358
|
+
deidkit run <input> deid_out --learn-dir learned/
|
|
336
359
|
```
|
|
337
360
|
|
|
338
|
-
|
|
339
|
-
|
|
361
|
+
Step 3 appends to two plain-text files that `learned/` **accumulates** across
|
|
362
|
+
cycles (so it keeps improving):
|
|
363
|
+
|
|
364
|
+
- `learned_names.txt` — words from every `y` → added to the name list (**higher recall**),
|
|
365
|
+
- `learned_stoplist.txt` — words from every `n` → added to the stoplist (**higher precision**).
|
|
340
366
|
|
|
341
|
-
|
|
367
|
+
Both are one token per line and human-editable. *In code*, load them with
|
|
342
368
|
`Policy(extra_name_files=["learned/learned_names.txt"],
|
|
343
369
|
extra_stoplist_files=["learned/learned_stoplist.txt"])`.
|
|
344
370
|
|
|
371
|
+
**Good to know:**
|
|
372
|
+
- `review.csv` is produced on **every** run — `--learn-dir` is only needed to
|
|
373
|
+
*load* what was learned (steps 3–4), not to create the queue.
|
|
374
|
+
- Learning is **token-level**: marking "María Gómez" as `y` teaches both `maría`
|
|
375
|
+
and `gómez` (lowercased, accent-folded), so they are caught elsewhere too.
|
|
376
|
+
- The loop tunes the **borderline** cases. A name the detector missed entirely
|
|
377
|
+
will not appear in `review.csv` — for those, use the known-PHI columns below.
|
|
378
|
+
|
|
345
379
|
### Two stronger levers (deterministic, near-100% recall)
|
|
346
380
|
|
|
347
381
|
- **Known-PHI columns** — if you already know a row's real name or ID (from a
|
|
@@ -397,27 +431,27 @@ dk.Policy(mode="balanced", spacy_model="es_core_news_lg",
|
|
|
397
431
|
|
|
398
432
|
## 9. Reading from a database (Fabric / Azure SQL)
|
|
399
433
|
|
|
400
|
-
### How
|
|
434
|
+
### How the connection works
|
|
401
435
|
|
|
402
|
-
|
|
403
|
-
|
|
436
|
+
deidkit talks only to the Fabric **SQL analytics endpoint** — it never logs into
|
|
437
|
+
the Azure portal or the production database. The setup is the standard Fabric
|
|
438
|
+
mirror:
|
|
404
439
|
|
|
405
|
-
1.
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
3.
|
|
412
|
-
|
|
413
|
-
deidkit **never writes back** to your database — it only reads.
|
|
440
|
+
1. The Azure SQL Database is mirrored into a Microsoft Fabric workspace, and the
|
|
441
|
+
mirrored database is shared **read-only** — *Read* + *Read all SQL analytics
|
|
442
|
+
endpoint data*, nothing else. No write or admin access is ever required.
|
|
443
|
+
2. deidkit is pointed at that endpoint with a connection string built from four
|
|
444
|
+
values (see section 10): the SQL analytics endpoint, the database name, the
|
|
445
|
+
authentication method, and the workspace URL.
|
|
446
|
+
3. It connects to the read-only endpoint, reads the six tables, de-identifies
|
|
447
|
+
them, and writes out the clean copy. It **never writes back** — it only reads.
|
|
414
448
|
|
|
415
|
-
|
|
416
|
-
de-identification runs entirely on our side against the shared endpoint.
|
|
449
|
+
All that's needed is read-only access to the shared endpoint plus those values.
|
|
417
450
|
|
|
418
451
|
### Then it runs — two patterns
|
|
419
452
|
|
|
420
|
-
The database drivers ship with deidkit (
|
|
453
|
+
The database drivers ship with deidkit (section 1). Pick based on where you want
|
|
454
|
+
the output.
|
|
421
455
|
|
|
422
456
|
### Pattern 1 — Database → files (recommended, simplest)
|
|
423
457
|
|
|
@@ -490,9 +524,9 @@ dk.deidentify_database(
|
|
|
490
524
|
|
|
491
525
|
## 10. Database connection parameters — exactly what to fill in
|
|
492
526
|
|
|
493
|
-
###
|
|
527
|
+
### The four connection values
|
|
494
528
|
|
|
495
|
-
|
|
529
|
+
Once the mirrored database is shared read-only, gather these **four values** (they
|
|
496
530
|
are exactly what the Fabric "share" step produces):
|
|
497
531
|
|
|
498
532
|
| # | Item | Example | Used for |
|
|
@@ -502,10 +536,11 @@ are exactly what the Fabric "share" step produces):
|
|
|
502
536
|
| 3 | **Authentication method** — Microsoft Entra ID *or* SQL Login | `Microsoft Entra ID` | connecting |
|
|
503
537
|
| 4 | **Fabric Workspace URL** | `https://app.fabric.microsoft.com/groups/...` | locating & confirming the shared database |
|
|
504
538
|
|
|
505
|
-
Items 1–3 go into the connection string; item 4
|
|
506
|
-
shared
|
|
539
|
+
Items 1–3 go into the connection string; item 4 (the workspace URL) only locates
|
|
540
|
+
and confirms the shared database in Fabric — it is not part of the connection
|
|
541
|
+
string.
|
|
507
542
|
|
|
508
|
-
###
|
|
543
|
+
### The connection string
|
|
509
544
|
|
|
510
545
|
Connections are **SQLAlchemy URLs**. For the Fabric source, the shape is:
|
|
511
546
|
|
|
@@ -515,10 +550,10 @@ mssql+pyodbc://@<HOST>/<DATABASE>?driver=<DRIVER>&authentication=<AUTH>
|
|
|
515
550
|
|
|
516
551
|
| Placeholder | Filled from | Example |
|
|
517
552
|
| --- | --- | --- |
|
|
518
|
-
| `<HOST>` | item 1 — the SQL analytics endpoint
|
|
519
|
-
| `<DATABASE>` | item 2 — the database name
|
|
520
|
-
| `<AUTH>` | item 3 —
|
|
521
|
-
| `<DRIVER>` |
|
|
553
|
+
| `<HOST>` | item 1 — the SQL analytics endpoint | `abc123....datawarehouse.fabric.microsoft.com` |
|
|
554
|
+
| `<DATABASE>` | item 2 — the database name | `ProductionMirror` |
|
|
555
|
+
| `<AUTH>` | item 3 — the authentication method | `ActiveDirectoryInteractive` |
|
|
556
|
+
| `<DRIVER>` | installed locally (section 1), not a Fabric value | `ODBC+Driver+18+for+SQL+Server` |
|
|
522
557
|
|
|
523
558
|
> Note the `@` before `<HOST>`: `...pyodbc://@abc123...`. With Entra ID auth there
|
|
524
559
|
> is no username/password in the URL — the `@` with an empty user is intentional.
|
|
@@ -724,42 +759,13 @@ print(deid.summary())
|
|
|
724
759
|
| Symptom | Fix |
|
|
725
760
|
| --- | --- |
|
|
726
761
|
| `ModuleNotFoundError: pyodbc` / SQLAlchemy | Reinstall: `pip install --force-reinstall deidkit` (the DB drivers ship with it) |
|
|
727
|
-
| `Data source name not found` / driver error | Install **ODBC Driver 18 for SQL Server** and make the `driver=` value match exactly (
|
|
762
|
+
| `Data source name not found` / driver error | Install **ODBC Driver 18 for SQL Server** and make the `driver=` value match exactly (section 10) |
|
|
728
763
|
| Browser never opens for `ActiveDirectoryInteractive` | Run from an interactive session; for servers use `ActiveDirectoryServicePrincipal` |
|
|
729
764
|
| `cannot reach the output database` (run-db) | Create the empty output DB first and pass a reachable `--out-db` URL |
|
|
730
765
|
| A column was changed that shouldn’t be | Add it to `--ignore`, or add a `FieldRule(..., "passthrough")`; confirm with `deidkit plan` |
|
|
731
|
-
| A free-text name slipped through | Try `--mode balanced`, enable `--spacy-model`, or add the name via the review→learn loop (
|
|
766
|
+
| A free-text name slipped through | Try `--mode balanced`, enable `--spacy-model`, or add the name via the review→learn loop (section 7) |
|
|
732
767
|
| Non-UTF-8 CSV export | Handled automatically (deidkit falls back to latin-1/cp1252) |
|
|
733
|
-
| Different output each run | You’re not pinning the secret — pass the same `--secret-file` every time (
|
|
734
|
-
|
|
735
|
-
---
|
|
736
|
-
|
|
737
|
-
## Publishing to PyPI
|
|
738
|
-
|
|
739
|
-
deidkit is **not on PyPI yet**, so `pip install deidkit` will only work once you
|
|
740
|
-
publish it under your own PyPI account. The package already builds cleanly into a
|
|
741
|
-
wheel (verified). To publish:
|
|
742
|
-
|
|
743
|
-
```bash
|
|
744
|
-
# 0. one-time: make a PyPI account at https://pypi.org and create an API token.
|
|
745
|
-
# Also edit the [project.urls] in pyproject.toml (they still say "your-org").
|
|
746
|
-
|
|
747
|
-
pip install build twine # the standard build + upload tools
|
|
748
|
-
|
|
749
|
-
python -m build # produces dist/deidkit-0.1.0-py3-none-any.whl (+ .tar.gz)
|
|
750
|
-
python -m twine check dist/* # validates the package metadata
|
|
751
|
-
|
|
752
|
-
# optional dry run on the test index first:
|
|
753
|
-
python -m twine upload --repository testpypi dist/*
|
|
754
|
-
|
|
755
|
-
# the real publish (asks for your PyPI token):
|
|
756
|
-
python -m twine upload dist/*
|
|
757
|
-
```
|
|
758
|
-
|
|
759
|
-
After that first upload, `pip install deidkit` works for everyone, and the name
|
|
760
|
-
`deidkit` is yours (it is currently unclaimed on PyPI). Bump `version` in
|
|
761
|
-
`pyproject.toml` for each subsequent release — PyPI refuses to overwrite an
|
|
762
|
-
existing version.
|
|
768
|
+
| Different output each run | You’re not pinning the secret — pass the same `--secret-file` every time (section 2) |
|
|
763
769
|
|
|
764
770
|
---
|
|
765
771
|
|