Tabular re-identification risk report¶
openmed.risk.tabular_risk_report() produces a deterministic, local-only summary for a structured export. It is intended for release manifests and review handoffs where a detailed row-level risk report would disclose more than the recipient needs.
The report computes exact-match equivalence classes over declared quasi-identifiers and retains only aggregate evidence:
- source, analyzed, and caller-declared suppressed row counts;
- schema column names, safe scalar kinds, missing counts, and distinct counts;
- class count, minimum
k, singleton rate, and class-size distribution; - maximum, mean, and P95 exact-match risk indicators;
- caller-declared generalization coverage;
- suppression rate, configured thresholds, pass/review outcome, and digests.
Source cells are used only during the local computation. Class keys are fingerprinted in memory and are not included in the report. Row identifiers, class membership, suppression offsets, raw generalized values, and generalization-level labels are not retained or serialized. The module makes no network calls and has no telemetry path.
Example¶
from openmed.risk import tabular_risk_report
synthetic_rows = [
{"age_band": "30-39", "region_band": "north", "outcome": "synthetic-a"},
{"age_band": "30-39", "region_band": "north", "outcome": "synthetic-b"},
]
report = tabular_risk_report(
synthetic_rows,
quasi_identifiers=["age_band", "region_band"],
generalization={"age_band": "ten-year", "region_band": "district"},
thresholds={
"minimum_k": 2,
"max_singleton_rate": 0.0,
"max_reidentification_risk": 0.5,
},
)
json_text = report.to_json()
markdown_text = report.to_markdown()
TabularRiskReport is immutable, including values returned from nested lookups. Use to_dict() when a mutable independent copy is required and to_json() rather than passing the report object directly to json.dumps().
Input boundaries¶
The local computation accepts at most 10,000 rows, 512 columns, 1,000,000 cells, and 65,536 characters in one string or decimal scalar. Integer values must fit in a signed 64-bit range. Column identifiers use a closed ASCII identifier grammar, and schema, quasi-identifier, generalization, threshold, and suppression declarations reject duplicate aliases or unknown fields. These boundaries apply before fingerprinting so hostile adapters cannot create unbounded work or retain source values through an error path.
The JSON and Markdown renderers project only the documented aggregate fields. They recompute equivalence-class metrics and every threshold outcome from the bounded class-size distribution rather than trusting caller-supplied status or risk fields. Report mappings with inconsistent row counts, class aggregates, or schema digests fail closed.
Generalization metadata is a caller declaration. This report does not prove that a transformation was applied, nor does it infer a safe threshold for a particular population. Suppression counts likewise describe the export workflow; the report does not retain the suppressed row offsets.
Interpretation¶
The default threshold requires minimum_k >= 2 and a zero singleton rate. The default suppression and generalization thresholds are permissive (100% and 0%) because those policy choices depend on the release context. Supply explicit thresholds for a release gate. A review outcome is a signal for qualified privacy review, not an automatic clinical or compliance decision.
The exact-match risk indicators are local sample indicators: a class of size k contributes 1/k risk for each row. They do not estimate population risk, attacker auxiliary-data risk, or a legal safe harbor. Use a qualified expert and the relevant release policy before sharing a structured export.