fix(campaign): suppress overlapping aggregate cells
This commit is contained in:
@@ -268,15 +268,24 @@ def _build_aggregate_campaign_report(
|
||||
outcome_values,
|
||||
threshold=policy.small_cell_threshold,
|
||||
)
|
||||
outcome_suppression_applied = denominator.suppressed or any(
|
||||
item.suppressed for item in outcomes.values()
|
||||
)
|
||||
inactive_entries = _inactive_entry_count(version)
|
||||
standalone_counts = {
|
||||
"inactive_source_entries": _suppress_standalone_count(
|
||||
inactive_entries,
|
||||
threshold=policy.small_cell_threshold,
|
||||
),
|
||||
"excluded_or_blocked_jobs": _suppress_standalone_count(
|
||||
# This population count overlaps the outcome partition, so exposing it
|
||||
# can make a suppressed outcome recoverable through subtraction.
|
||||
"excluded_or_blocked_jobs": (
|
||||
AggregateCount(value=None, suppressed=True)
|
||||
if outcome_suppression_applied
|
||||
else _suppress_standalone_count(
|
||||
facts.excluded_or_blocked,
|
||||
threshold=policy.small_cell_threshold,
|
||||
)
|
||||
),
|
||||
}
|
||||
suppression_applied = denominator.suppressed or any(
|
||||
@@ -322,7 +331,8 @@ def _build_aggregate_campaign_report(
|
||||
suppression_applied=suppression_applied,
|
||||
rule=(
|
||||
"Positive counts below the threshold are hidden. At least one additional "
|
||||
"count or the denominator is hidden when needed to prevent subtraction."
|
||||
"count or the denominator is hidden when needed to prevent subtraction. "
|
||||
"Overlapping population counts are hidden whenever outcome suppression applies."
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -141,6 +141,39 @@ def test_small_cells_use_primary_and_complementary_suppression() -> None:
|
||||
assert report.privacy.suppression_applied is True
|
||||
|
||||
|
||||
def test_outcome_suppression_also_hides_overlapping_population_count() -> None:
|
||||
jobs = [
|
||||
*[
|
||||
_job(
|
||||
index,
|
||||
"smtp_accepted",
|
||||
validation_status="blocked" if index < 4 else "ready",
|
||||
)
|
||||
for index in range(8)
|
||||
],
|
||||
_job(8, "skipped", validation_status="excluded"),
|
||||
]
|
||||
|
||||
report = build_aggregate_campaign_report(
|
||||
campaign=_campaign(), # type: ignore[arg-type]
|
||||
version=_version(inactive_count=5), # type: ignore[arg-type]
|
||||
jobs=jobs, # type: ignore[arg-type]
|
||||
policy=_policy(5),
|
||||
)
|
||||
|
||||
assert report.outcomes.excluded.suppressed is True
|
||||
assert report.population.excluded_or_blocked_jobs.model_dump() == {
|
||||
"value": None,
|
||||
"suppressed": True,
|
||||
}
|
||||
# Source entries without jobs are outside the outcome denominator and do
|
||||
# not overlap the suppressed partition, so their threshold-safe count stays visible.
|
||||
assert report.population.inactive_source_entries.model_dump() == {
|
||||
"value": 5,
|
||||
"suppressed": False,
|
||||
}
|
||||
|
||||
|
||||
def test_all_small_cells_also_suppress_the_denominator_and_state() -> None:
|
||||
jobs = [_job(0, "smtp_accepted"), _job(1, "failed_permanent")]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user