Mapping State Small-Cell Rules Into a Policy File
This guide, part of Compliance Mapping Frameworks for Spatial Epidemiology Pipelines, turns the small-cell suppression rules in a state data-release policy into a machine-readable file that a surveillance pipeline enforces directly — so the rule applied is provably the rule approved.
Problem Context & Constraints
Data-release policies are written in prose for people. A typical one says that counts from 1 to 10 shall not be released for geographies below county level, that rates shall not be published where the numerator is below 20, that complementary suppression shall be applied to prevent recovery, and that certain conditions are exempt or stricter. Every one of those sentences is an interpretation problem when it becomes code.
Does “1 to 10” include zero? Most policies treat zero as releasable, some do not. Does “below county level” include a city that is coterminous with a county? Is the rate threshold of 20 applied before or after suppression of the count? When two rules conflict — a count of 12 that passes the count rule in a tract with a population of 90 — which wins? Analysts answer these questions one at a time, in code, and different analysts answer them differently. The result is that two products from the same agency apply subtly different rules, and neither matches the policy exactly.
Three constraints shape the fix. The policy must be data, not code — a structured file the pipeline reads, so the rule is stated once and every product applies the same one. Every ambiguity must be resolved explicitly in the file, with the resolution approved by whoever owns the policy, rather than resolved implicitly by whoever writes the code. And the file must be versioned and hashed, so each release can cite the exact policy version it was produced under.
Prerequisites
python3.11pydantic2.7.4,pyyaml6.0.1,pandas2.2.2- The prose policy document, and access to the person or body who owns its interpretation
- A list of every product the agency releases, with the geographies and measures each contains
- Version control for the policy file, separate from the pipeline code
Step-by-Step Solution
Write the policy as a structured file with every ambiguity resolved, validate it against a schema, and give the pipeline a single function that applies it and returns the reason for each decision.
# disclosure_policy.yaml — approved by the Data Release Committee, 2026-08-14
policy_id: state-dph-small-cell
version: 3
effective_from: 2026-09-01
count_rule:
suppress_min: 1 # counts 1..10 suppressed
suppress_max: 10
zero_is_releasable: true # RESOLVED: zero is not a small cell (committee minute 14.3)
rate_rule:
min_numerator: 20 # rates with fewer events are not published
applies_after_count_rule: true # RESOLVED: a suppressed count has no rate
geography:
sub_county_levels: [tract, block_group, zcta]
coterminous_city_is_county: true # RESOLVED: consolidated city-county treated as county
complementary:
required_when_primary_in_row: true
row_definitions: [geography_hierarchy, demographic_margin]
exemptions:
- condition_group: vaccination_coverage
count_rule: {suppress_min: 1, suppress_max: 4} # stricter-than-default rules listed too
stricter:
- condition_group: hiv
count_rule: {suppress_min: 1, suppress_max: 19}
# Load, validate and apply a machine-readable small-cell policy.
# Pinned: pydantic==2.7.4, pyyaml==6.0.1, pandas==2.2.2
import hashlib
import logging
import pandas as pd
import yaml
from pydantic import BaseModel, Field
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
log = logging.getLogger("policy")
class CountRule(BaseModel):
suppress_min: int = Field(ge=0)
suppress_max: int = Field(ge=0)
zero_is_releasable: bool = True
class Policy(BaseModel):
policy_id: str
version: int
effective_from: str
count_rule: CountRule
rate_rule: dict
geography: dict
complementary: dict
exemptions: list = []
stricter: list = []
def load_policy(path: str) -> tuple[Policy, str]:
raw = open(path, "rb").read()
digest = hashlib.sha256(raw).hexdigest()
pol = Policy(**yaml.safe_load(raw))
log.info("policy %s v%d loaded, effective %s, sha256=%s",
pol.policy_id, pol.version, pol.effective_from, digest[:16])
return pol, digest
def rule_for(pol: Policy, condition_group: str) -> CountRule:
for entry in pol.stricter + pol.exemptions:
if entry["condition_group"] == condition_group:
return CountRule(**{**pol.count_rule.model_dump(), **entry["count_rule"]})
return pol.count_rule
def apply_primary(cells: pd.DataFrame, pol: Policy) -> pd.DataFrame:
"""Primary suppression with a reason per cell. Complementary is a later step."""
out = cells.copy()
reasons = []
for r in out.itertuples():
cr = rule_for(pol, r.condition_group)
sub_county = r.level in pol.geography["sub_county_levels"]
if r.count == 0 and cr.zero_is_releasable:
reasons.append("release:zero")
elif sub_county and cr.suppress_min <= r.count <= cr.suppress_max:
reasons.append(f"suppress:count {cr.suppress_min}-{cr.suppress_max}")
elif r.count < pol.rate_rule["min_numerator"]:
reasons.append("release_count_only:rate below numerator floor")
else:
reasons.append("release")
out["decision"] = reasons
out["suppressed"] = out["decision"].str.startswith("suppress")
out["rate_publishable"] = out["decision"] == "release"
log.info("primary suppression under v%d: %s", pol.version,
out["decision"].str.split(":").str[0].value_counts().to_dict())
return out
The comments marked RESOLVED are the point of the exercise. Each is an ambiguity in the prose policy that the policy owner decided, with a reference to where the decision was made. When an analyst later asks whether zero is suppressed, the answer is in the file with its provenance, rather than in someone’s memory or in whichever product happened to be written first.
Returning a reason per cell, rather than a bare boolean, is what lets the audit trail explain each decision. A reviewer asking why a particular tract count was released can see that it was a zero under an explicit zero-is-releasable rule, which is a very different conversation from finding that the code simply did not suppress it.
Validation & Edge Cases
Ambiguities discovered after deployment. New products expose new questions — does the rule apply to a count of zero cases among a denominator of zero? Add each resolution to the file as a new version with the approving reference, rather than handling it in the product that found it.
Conflicting rules for one cell. A cell may fall under both a stricter condition rule and a general exemption. The file must define precedence — stricter wins is the usual choice — and the loader should reject a file where precedence is undefined.
Policy changes mid-series. A version change on 1 September means weekly products before and after apply different thresholds, and a series spanning the change is inconsistent. Either reprocess the earlier weeks under the new version or mark the discontinuity in every product that crosses it.
Test coverage of the policy itself. Build a table of boundary cases — counts of 0, 1, 10, 11, 19, 20 at each geography level and condition group — with the expected decision for each, and assert the function reproduces them. That table doubles as the plain-language explanation of the policy for new staff.
Rules that belong to complementary suppression. Primary suppression cannot see what else is released. Keep the complementary rules in the same file but apply them in a separate step that considers the whole release, as in Complementary Suppression to Defeat Back-Calculation.
INFO policy policy state-dph-small-cell v3 loaded, effective 2026-09-01, sha256=5e21c08a7fd4b913
INFO policy primary suppression under v3: {'release': 1427, 'suppress': 318, 'release_count_only': 97}
Compliance Notes
Store the policy file in version control with its approval record: who approved each version, when, and the minute or decision reference for each resolved ambiguity. Record in every release’s provenance the policy identifier, version and content hash, so any published cell can be traced to the exact rule that governed it.
The policy file is the interface between the people who own disclosure risk and the people who write code. Keeping it readable by the former — plain keys, comments explaining each resolution — is what allows the committee to review what the pipeline actually enforces, rather than trusting that the code matches a document they approved. That review should happen whenever the file changes, and the change log is its agenda.
Related Topics
- Compliance Mapping Frameworks for Spatial Epidemiology Pipelines — the parent guide on turning regulations into pipeline controls.
- Small-Count Cell Suppression in Rate Maps — the suppression the policy file drives.
- Producing an Audit Trail for a Spatial Data Release — where the policy version and per-cell reasons end up.