pii-en-65m / reproduction /scripts /regex_baseline.py
smoh's picture
Stage DataFog PII v0.1.0: epoch-2 checkpoint, frozen MCP bundle, provenance and licenses (part 2)
4b51830 verified
Raw History Blame
4.15 kB
#!/usr/bin/env python3
"""Pinned official DataFog regex baseline on shared email/phone scope."""
import argparse
import hashlib
import importlib.metadata
import json
import sys
import time
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from pii.evaluation import evaluate, read_jsonl, write_json
VERSION = '4.8.1'
MAPPING = {'EMAIL': 'email', 'PHONE': 'phone_number'}
def scoped_gold(records):
return [{**r, 'spans':[s for s in r['spans'] if s['label'] in MAPPING.values()]} for r in records]
def predict(records):
import datafog
if importlib.metadata.version('datafog') != VERSION:
raise RuntimeError(f'Baseline requires datafog=={VERSION}')
for r in records:
result = datafog.scan(r['text'], engine='regex', entity_types=list(MAPPING))
findings = []
byte_offsets = [0]
for ch in r['text']:
byte_offsets.append(byte_offsets[-1] + len(ch.encode('utf-8')))
for e in result.entities:
if r['text'][e.start:e.end] != e.text:
raise ValueError('Upstream span/source mismatch')
findings.append(dict(label=MAPPING[e.type], start=byte_offsets[e.start], end=byte_offsets[e.end], raw_score=1.0, confidence=None))
yield dict(id=r['id'], text=r['text'], findings=findings, contains_pii=bool(findings), score_semantics='Constant match indicator from DataFog regex, not calibrated confidence')
def write_jsonl(path, records):
with open(path, 'w') as f:
for record in records:
f.write(json.dumps(record, ensure_ascii=False)+'\n')
if __name__ == '__main__':
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--gold', required=True)
parser.add_argument('--output-dir', required=True)
parser.add_argument('--model-predictions')
parser.add_argument('--model-calibration')
args = parser.parse_args()
output = Path(args.output_dir)
output.mkdir(parents=True, exist_ok=True)
gold = scoped_gold(read_jsonl(args.gold))
started = time.perf_counter()
predictions = list(predict(gold))
elapsed = time.perf_counter()-started
write_jsonl(output/'gold-email-phone.jsonl', gold)
write_jsonl(output/'regex-predictions.jsonl', predictions)
metrics = evaluate(gold, predictions)
# A regex match flag is not a probability; do not imply a calibration comparison.
for key in ('raw_reliability','confidence_reliability','reliability_population','confidence_semantics'):
metrics.pop(key, None)
metrics['score_semantics'] = 'Regex match indicator, no probability calibration evaluated'
metrics['comparison_scope'] = ['email','phone_number']
metrics['unsupported_labels'] = ['first_name','last_name','street_address','customer_id','account_number']
write_json(output/'regex-metrics.json', metrics)
provenance = dict(package='datafog', version=VERSION, official_repository='https://github.com/DataFog/datafog-python',
release='https://pypi.org/project/datafog/4.8.1/', input_sha256=hashlib.sha256(Path(args.gold).read_bytes()).hexdigest(),
documents=len(gold), elapsed_seconds=elapsed, API="datafog.scan(text, engine='regex', entity_types=['EMAIL','PHONE'])",
offset_conversion='Python character offsets converted to original UTF-8 byte offsets', dependencies={d.metadata['Name']:d.version for d in importlib.metadata.distributions()})
write_json(output/'regex-provenance.json', provenance)
if args.model_predictions:
model = read_jsonl(args.model_predictions)
model = [{**r, 'findings':[s for s in r['findings'] if s['label'] in MAPPING.values()]} for r in model]
calibration = json.loads(Path(args.model_calibration).read_text()) if args.model_calibration else None
model_metrics = evaluate(gold, model, calibration=calibration)
model_metrics['comparison_scope'] = ['email','phone_number']
write_json(output/'model-email-phone-metrics.json', model_metrics)
print(json.dumps({'documents':len(gold), 'seconds':elapsed, 'output_dir':str(output)}))