Token Classification
Transformers
Safetensors
English
roberta
hipaa
phi
protected-health-information
safe-harbor
de-identification
healthcare
medical-records
pii-detection
ner
redaction
privacy
Synthetic
Instructions to use aitrainingdatabroker/hipaa-phi-detector with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use aitrainingdatabroker/hipaa-phi-detector with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("token-classification", model="aitrainingdatabroker/hipaa-phi-detector")# pip install -U transformers accelerate # Load model directly from transformers import AutoTokenizer, AutoModelForTokenClassification tokenizer = AutoTokenizer.from_pretrained("aitrainingdatabroker/hipaa-phi-detector") model = AutoModelForTokenClassification.from_pretrained("aitrainingdatabroker/hipaa-phi-detector", device_map="auto") - Notebooks
- Google Colab
- Kaggle
File size: 14,015 Bytes
0f6210e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 | """Build the training and test files for the five Hugging Face models (2026-10-29).
Every row is invented or comes from our own published datasets in ../datasets. Nothing is from a real person.
python models/build.py writes models/<name>/data/{train,test}.jsonl
Test rows for the generated sets come from templates the model never sees in training, so the scores are honest
about new sentence shapes, not just new names.
"""
import json
import random
from pathlib import Path
ROOT = Path(__file__).resolve().parent
DS = ROOT.parent / "datasets"
rng = random.Random(20261029)
def read(path):
with open(path, encoding="utf-8") as f:
return [json.loads(line) for line in f if line.strip()]
def write(name, split, rows):
out = ROOT / name / "data"
out.mkdir(parents=True, exist_ok=True)
with open(out / f"{split}.jsonl", "w", encoding="utf-8", newline="\n") as f:
for r in rows:
f.write(json.dumps(r, ensure_ascii=False) + "\n")
print(f"{name}/{split}: {len(rows)} rows")
# ---------- 1. business-pii-detector: our two PII datasets, one label set ----------
PAIR_MAP = {
"PERSON_CUSTOMER": "PERSON", "PERSON_STAFF": "PERSON", "ORG_CLIENT": "ORG", "EMAIL": "EMAIL", "PHONE": "PHONE",
"STREET_ADDRESS": "ADDRESS", "DATE": "DATE", "MONEY": "MONEY", "ORDER_ID": "ID_NUMBER",
}
def pii():
pairs = read(DS / "synthetic-deidentification-pairs/data/train.jsonl")
rows = [{"text": p["text"], "entities": [{"label": PAIR_MAP[e["type"]], "start": e["start"], "end": e["end"]}
for e in p["entities"]]} for p in pairs]
rng.shuffle(rows)
hard_train = read(DS / "pii-span-benchmark-hard/data/train.jsonl")
hard_test = read(DS / "pii-span-benchmark-hard/data/test.jsonl")
strip = lambda r: {"text": r["text"], "entities": [{"label": e["label"], "start": e["start"], "end": e["end"]}
for e in r["entities"]]}
write("business-pii-detector", "train", rows[:540] + [strip(r) for r in hard_train])
write("business-pii-detector", "test", rows[540:] + [strip(r) for r in hard_test])
# ---------- 2. hipaa-phi-detector: invented healthcare admin text, HIPAA Safe Harbor identifier types ----------
FIRST = ["Marisol", "Dennick", "Aurelio", "Tamsin", "Okonkwo", "Liesel", "Bram", "Yusra", "Corwin", "Ilse", "Nadim",
"Petra", "Ravindra", "Sunniva", "Teodor", "Wren", "Zaida", "Hollis", "Imogen", "Kasimir", "Delphine", "Ezio"]
LAST = ["Quarrington", "Velasquez-Holt", "Abernethy", "Okafor", "Lindqvist", "Marchetti", "Pemberton", "Szabo",
"Thistlewood", "Hakimi", "Brannigan", "Castellanos", "Duchesne", "Fairweather", "Gruber", "Ishikawa"]
STREETS = ["Larkspur Lane", "Copperfield Road", "Harbor View Drive", "Old Mill Court", "Juniper Street", "Ridgeway Ave"]
CITIES = ["Brookhaven", "Millbrook", "Fairhaven", "Ashford", "Kingsport", "Willow Creek", "Eastlake"]
MONTHS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October",
"November", "December"]
def name():
return f"{rng.choice(FIRST)} {rng.choice(LAST)}"
def date():
y, m, d = rng.randint(1931, 2026), rng.randint(1, 12), rng.randint(1, 28)
return rng.choice([f"{m:02d}/{d:02d}/{y}", f"{MONTHS[m - 1]} {d}, {y}", f"{y}-{m:02d}-{d:02d}", f"{d} {MONTHS[m - 1][:3]} {y}"])
def phone():
# 555-0100 to 555-0199 is the range reserved for fiction.
return rng.choice(["({0}) 555-01{1:02d}", "{0}-555-01{1:02d}", "{0}.555.01{1:02d}", "+1 {0} 555 01{1:02d}"]).format(
rng.randint(201, 989), rng.randint(0, 99))
def fill():
first, last = rng.choice(FIRST), rng.choice(LAST)
return {
"NAME": f"{first} {last}",
"NAME2": name(),
"DATE": date(),
"DATE2": date(),
"AGE_OVER_89": str(rng.randint(90, 104)),
"PHONE": phone(),
"FAX": phone(),
"EMAIL": rng.choice([f"{first.lower()}.{last.lower()}@example.com", f"{first[0].lower()}{last.lower()}@example.org"]),
"SSN": f"9{rng.randint(10, 99)}-{rng.randint(10, 99)}-{rng.randint(1000, 9999)}",
"MRN": rng.choice(["MRN ", "MR#", ""]) + str(rng.randint(10000000, 99999999)),
"HEALTH_PLAN_ID": rng.choice(["XQH", "BNW", "ZRT"]) + str(rng.randint(100000000, 999999999)),
"ACCOUNT_NUMBER": f"ACCT-{rng.randint(100000, 999999)}",
"LICENSE_NUMBER": rng.choice(["D", "S", "K"]) + str(rng.randint(1000000, 9999999)),
"VEHICLE_ID": rng.choice(["plate ", ""]) + f"{rng.choice('ABCDEFGHJKLMNPRSTUVWXYZ')}{rng.choice('ABCDEFGHJKLMNPRSTUVWXYZ')}{rng.randint(1000, 9999)}",
"DEVICE_ID": f"SN-{rng.randint(100000, 999999)}{rng.choice('ABCDEF')}",
"URL": rng.choice(["https://portal.example.com/p/", "https://example.org/chart/"]) + str(rng.randint(1000, 9999)),
"IP_ADDRESS": f"192.0.2.{rng.randint(1, 254)}",
"ADDRESS": f"{rng.randint(10, 9999)} {rng.choice(STREETS)}, {rng.choice(CITIES)}",
"ZIP": f"{rng.randint(10000, 99999)}",
}
# {SLOT} marks an identifier. Train and test templates are different sentences.
PHI_TRAIN = [
"Patient {NAME} (DOB {DATE}) called to reschedule her follow-up; best number is {PHONE}.",
"Prior authorization for {NAME}, member ID {HEALTH_PLAN_ID}, was denied on {DATE2}.",
"Please fax the discharge summary for {MRN} to {FAX} before noon.",
"Billing note: account {ACCOUNT_NUMBER} for {NAME} has a balance after the {DATE} visit.",
"{NAME} asked that statements go to {ADDRESS} {ZIP} instead of the old address.",
"Front desk verified identity with SSN {SSN} and date of birth {DATE}.",
"The insulin pump with serial {DEVICE_ID} was replaced for {NAME} under warranty.",
"Portal message from {EMAIL}: can I get my lab results before {DATE2}?",
"{NAME}, age {AGE_OVER_89}, needs transport; daughter {NAME2} will meet the van.",
"Login for the patient portal came from {IP_ADDRESS} at 2 a.m., flagged for review.",
"Visit summary link sent to {NAME}: {URL}",
"Parking pass issued to {NAME2}, vehicle {VEHICLE_ID}, for the infusion schedule.",
"Claim resubmitted for {NAME} with corrected member number {HEALTH_PLAN_ID}.",
"Driver's license {LICENSE_NUMBER} copied for the new patient file of {NAME}.",
"Referral from Dr. {NAME2} for {NAME}; chart {MRN}; call back at {PHONE}.",
"Records request signed by {NAME} on {DATE}; mail copies to {ADDRESS}.",
"Superbill for {ACCOUNT_NUMBER} shows two visits, {DATE} and {DATE2}.",
"Caregiver {NAME2} can be reached at {EMAIL} or {PHONE}.",
"Hearing aid {DEVICE_ID} returned by {NAME} for repair on {DATE2}.",
"Eligibility check failed for plan ID {HEALTH_PLAN_ID}; patient {NAME} will bring a new card.",
"Telehealth session for {MRN} dropped twice; patient IP {IP_ADDRESS}.",
"{NAME} turned {AGE_OVER_89} this year and qualifies for the home visit program.",
]
PHI_TEST = [
"Left a voicemail for {NAME} at {PHONE} about the {DATE} appointment.",
"Member {HEALTH_PLAN_ID} ({NAME}) disputes the copay on account {ACCOUNT_NUMBER}.",
"Chart {MRN} updated: new mailing address {ADDRESS} {ZIP}.",
"Pacemaker {DEVICE_ID} check scheduled for {NAME} on {DATE2}; confirm by email at {EMAIL}.",
"Spouse {NAME2}, plate {VEHICLE_ID}, is approved for valet at the clinic.",
"Identity mismatch: SSN {SSN} on file does not match the one on the claim for {NAME}.",
"{NAME} ({AGE_OVER_89}) prefers phone calls; fax records to {FAX}.",
"Suspicious portal sign-in from {IP_ADDRESS}; reset link sent: {URL}",
]
# Hard negatives: health words that are not identifiers.
PHI_NEG_TRAIN = [
"Flu shots are available every Tuesday morning in the main clinic.",
"Remind patients that the lab closes at 4 p.m. on holidays.",
"The billing team switched to the new claim form in the spring.",
"Type 2 diabetes education class moves to room 3 next month.",
"Metformin refill requests go to the pharmacy queue, not the front desk.",
]
PHI_NEG_TEST = [
"Blood pressure cuffs in exam room 2 need new batteries.",
"The prior authorization policy for MRI scans changed this quarter.",
]
def render(tpl):
vals, text, ents, i = fill(), "", [], 0
while i < len(tpl):
j = tpl.find("{", i)
if j < 0:
text += tpl[i:]
break
k = tpl.index("}", j)
text += tpl[i:j]
slot = tpl[j + 1:k]
value = vals[slot]
label = "NAME" if slot == "NAME2" else "DATE" if slot == "DATE2" else slot
if slot == "ZIP":
label = "ADDRESS"
ents.append({"label": label, "start": len(text), "end": len(text) + len(value)})
text += value
i = k + 1
return {"text": text, "entities": ents}
def phi():
train = [render(t) for t in PHI_TRAIN for _ in range(60)] + [render(t) for t in PHI_NEG_TRAIN for _ in range(12)]
test = [render(t) for t in PHI_TEST for _ in range(25)] + [render(t) for t in PHI_NEG_TEST for _ in range(10)]
rng.shuffle(train)
write("hipaa-phi-detector", "train", train)
write("hipaa-phi-detector", "test", test)
# ---------- 3. privacy-law-classifier: which rule a sentence about company records points to ----------
LAW_TRAIN = [
"Our export includes {kw} fields from the last six years.",
"Before we license the records, do we need to handle the {kw} data differently?",
"The legal team flagged {kw} in the sample set.",
"We found {kw} in free-text notes during the scan.",
"Which rules apply when the archive holds {kw}?",
"A buyer asked how we treat {kw} in the shared files.",
"The {kw} records sit in our old system and in email attachments.",
"We are cleaning anything tied to {kw} before the review.",
]
LAW_TEST = [
"Counsel wants a plan for the {kw} material before any sample goes out.",
"Some tickets mention {kw}, so we paused the export.",
"How should a company prepare {kw} for an AI data license?",
]
# Extra cue words per law, beyond the dataset's own keywords, so every class has enough variety.
LAW_EXTRA = {
"HIPAA": ["protected health information", "patient charts", "health plan member IDs", "clinic visit notes"],
"42 CFR Part 2": ["addiction treatment records", "substance use disorder notes", "detox program files"],
"CCPA and CPRA": ["California consumers' personal information", "a California opt-out of sale request"],
"Other US state privacy laws": ["Virginia and Colorado consumer data", "Texas residents' sensitive data"],
"GDPR": ["EU customers' personal data", "a data protection officer review in Germany", "consent under EU law"],
"UK GDPR": ["UK customers' personal data", "an ICO complaint", "data from our London office"],
"PIPEDA and Quebec Law 25": ["Canadian customers' personal information", "Quebec privacy officer approval"],
"GLBA and the FTC Safeguards Rule": ["bank customer account data", "loan applicant financial details"],
"FCRA": ["tenant screening reports", "employment background check results", "credit report pulls"],
"COPPA": ["accounts of kids under 13", "parent consent forms for a children's app"],
"PCI DSS": ["full credit card numbers", "cardholder data and CVV codes", "card expiry dates"],
"CAN-SPAM": ["marketing email unsubscribe lists", "newsletter opt-out records"],
"Fair Housing Act": ["rental applicant familial status", "tenant disability accommodation requests"],
"Driver's Privacy Protection Act": ["DMV records", "license plate lookups", "driver license scans"],
"ITAR and EAR": ["export-controlled technical data", "defense article drawings", "ECCN-classified designs"],
"CUI and DFARS": ["controlled unclassified information from a defense contract", "NIST 800-171 scoped files"],
"GINA": ["genetic test results", "employee family medical history", "hereditary condition screening"],
"21 CFR Part 11": ["electronic batch records with signatures", "GxP audit trails", "validated lab system logs"],
"Attorney-client privilege and professional conduct rules": ["privileged client emails", "attorney work product"],
"Contractual confidentiality and NDAs": ["files covered by a customer NDA", "MSA-restricted client deliverables"],
"Trade secret protection": ["our proprietary process recipes", "confidential product designs", "manufacturing know-how"],
}
def laws():
fws = read(DS / "data-compliance-frameworks/data/frameworks.jsonl")
train, test = [], []
for fw in fws:
label = fw["framework"]
cues = list(fw["keywords"]) + LAW_EXTRA[label]
for kw in cues:
train += [{"text": t.format(kw=kw), "label": label} for t in LAW_TRAIN]
test += [{"text": t.format(kw=kw), "label": label} for t in LAW_TEST]
rng.shuffle(train)
write("privacy-law-classifier", "train", train)
write("privacy-law-classifier", "test", test)
# ---------- 4. business-record-type-classifier: which kind of company record a text is ----------
def record_types():
pairs = read(DS / "synthetic-deidentification-pairs/data/train.jsonl")
rows = [{"text": p["text"], "label": p["kind"]} for p in pairs]
rng.shuffle(rows)
write("business-record-type-classifier", "train", rows[:480])
write("business-record-type-classifier", "test", rows[480:])
# ---------- 5. support-ticket-outcome-classifier: how a support conversation ended ----------
def tickets():
rows = read(DS / "synthetic-support-tickets-with-outcomes/data/train.jsonl")
out = []
for r in rows:
convo = "\n".join(f"{m['role']}: {m['text']}" for m in r["messages"])
out.append({"text": convo, "label": r["resolution"]["outcome"], "category": r["category"]})
rng.shuffle(out)
write("support-ticket-outcome-classifier", "train", out[:400])
write("support-ticket-outcome-classifier", "test", out[400:])
if __name__ == "__main__":
pii()
phi()
laws()
record_types()
tickets()
|