fix(campaign): suppress overlapping aggregate cells
This commit is contained in:
@@ -268,15 +268,24 @@ def _build_aggregate_campaign_report(
|
|||||||
outcome_values,
|
outcome_values,
|
||||||
threshold=policy.small_cell_threshold,
|
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)
|
inactive_entries = _inactive_entry_count(version)
|
||||||
standalone_counts = {
|
standalone_counts = {
|
||||||
"inactive_source_entries": _suppress_standalone_count(
|
"inactive_source_entries": _suppress_standalone_count(
|
||||||
inactive_entries,
|
inactive_entries,
|
||||||
threshold=policy.small_cell_threshold,
|
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,
|
facts.excluded_or_blocked,
|
||||||
threshold=policy.small_cell_threshold,
|
threshold=policy.small_cell_threshold,
|
||||||
|
)
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
suppression_applied = denominator.suppressed or any(
|
suppression_applied = denominator.suppressed or any(
|
||||||
@@ -322,7 +331,8 @@ def _build_aggregate_campaign_report(
|
|||||||
suppression_applied=suppression_applied,
|
suppression_applied=suppression_applied,
|
||||||
rule=(
|
rule=(
|
||||||
"Positive counts below the threshold are hidden. At least one additional "
|
"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
|
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:
|
def test_all_small_cells_also_suppress_the_denominator_and_state() -> None:
|
||||||
jobs = [_job(0, "smtp_accepted"), _job(1, "failed_permanent")]
|
jobs = [_job(0, "smtp_accepted"), _job(1, "failed_permanent")]
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user