patient-record-entity-resolution — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited patient-record-entity-resolution (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
The same patient, provider, or facility appears many times across claims, eligibility, and clinical systems under slightly different names, addresses, and IDs. This skill links those records into a single resolved entity and produces a golden record. It implements the full entity-resolution pipeline blocking, comparison, probabilistic scoring, clustering, and survivorship using production-grade open-source tooling.
The core is the Fellegi–Sunter probabilistic model: for each candidate pair, combine field-level agreement/disagreement into a match weight using learned m and u probabilities, then threshold into match / possible-match / non-match.
usaddress, dates, phone, gender);normalize provider IDs and run medical-ontology-code-mapping for any coded fields.
(e.g., soundex(last_name) + dob_year, or zip + first_initial). Critical at scale.
field; handle nulls explicitly.
m/u via EM, so nolabeled data is strictly required).
review the borderline band manually or with active learning.
or source-priority rules) and keep full lineage back to source rows.
# Probabilistic linkage with Splink (DuckDB backend) scales to millions of rows
from splink.duckdb.linker import DuckDBLinker
import splink.duckdb.comparison_library as cl
settings = {
"link_type": "dedupe_only",
"blocking_rules_to_generate_predictions": [
"l.dob = r.dob and substr(l.last_name,1,1) = substr(r.last_name,1,1)",
"l.zip = r.zip",
],
"comparisons": [
cl.jaro_winkler_at_thresholds("first_name", [0.9, 0.7]),
cl.jaro_winkler_at_thresholds("last_name", [0.9, 0.7]),
cl.exact_match("dob"),
cl.levenshtein_at_thresholds("address", 2),
],
}
linker = DuckDBLinker(df, settings)
linker.estimate_u_using_random_sampling(max_pairs=1e6)
linker.estimate_parameters_using_expectation_maximisation(
"l.dob = r.dob and l.last_name = r.last_name")
pairs = linker.predict(threshold_match_probability=0.95)
clusters = linker.cluster_pairwise_predictions_at_threshold(pairs, 0.95)# Lightweight alternative for small data: recordlinkage
import recordlinkage as rl
idx = rl.Index(); idx.block("dob_year")
cand = idx.index(df)
cmp = rl.Compare()
cmp.string("last_name","last_name", method="jarowinkler", threshold=0.85, label="ln")
cmp.exact("dob","dob", label="dob")
features = cmp.compute(cand, df)
matches = features[features.sum(axis=1) >= 2]Report precision, recall, and F1 against a labeled gold set when available; otherwise use clerical review of a sampled borderline band. Track the match-rate and duplicate-collapse rate, and always expose a tunable threshold so reviewers can trade precision vs. recall (false-merge of two real patients is a safety event bias toward precision and route ambiguous pairs to review).
entity_clusters.parquet source row → resolved entity_id.golden_records.parquet one survivorship record per entity with lineage.match_pairs.parquet scored pairs with per-field contributions (explainable).linkage_report.md blocking stats, score distribution, threshold, P/R/F1.Optimized for patient/member and provider/NPI matching, including the hard cases: nicknames, maiden/married names, twins sharing an address, transposed DOBs, and group-vs- individual NPIs. Upstream: healthcare-data-quality-profiling. Pairs with spark-healthcare-data-pipeline for billion-row deployment.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.