diff --git a/CHANGELOG.md b/CHANGELOG.md index bfd2ece..5562ed8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,6 +62,13 @@ per-file diff commands. ### Fixed +- **`salary_lookup.py` prints the privacy footnote only when a row actually carries `N/A*`** + - the `* N/A = Too few employees to publish (privacy)` line was appended under every + category table, including one where every row has an index, so the output asserted a + suppression that never happened (the residual noted on #470). The footnote now follows a + flag set by the `N/A*` branch; a table with a suppressed row renders exactly as before. + Two `FormatEntryTests` cases pin both directions; the "omitted" one fails on master. + - **`convert_salary_excel.py` pairs a bare `Count`/`Index` column pair instead of splitting it, so `salary_lookup.py` no longer labels a published headcount as privacy-suppressed** - the pairing loop required a non-empty derived category name on diff --git a/salary_lookup.py b/salary_lookup.py index a49fa6e..3a9b7fa 100644 --- a/salary_lookup.py +++ b/salary_lookup.py @@ -319,6 +319,7 @@ def format_entry(entry, metadata): lines.append(f" {'Category':<22} {'Count':>6} {index_label:>8} {'vs Baseline':>10}") lines.append(f" {'-'*50}") + suppressed = False # did any row render its index as N/A*? for label, data in categories.items(): display_label = label.replace("_", " ").title() count = data.get("count") @@ -339,9 +340,14 @@ def format_entry(entry, metadata): else: index_str = "N/A*" diff_str = "" + suppressed = True lines.append(f" {display_label:<22} {count_str:>6} {index_str:>8} {diff_str:>10}") - lines.append(f"\n * N/A = Too few employees to publish (privacy)") + # The footnote explains the N/A* marker; printing it under a table with + # no such row asserts a privacy suppression that did not happen. + lines.append("") + if suppressed: + lines.append(" * N/A = Too few employees to publish (privacy)") if metadata.get("baseline_description"): lines.append(f" {metadata['baseline_description']}") else: diff --git a/tests/test_salary_lookup.py b/tests/test_salary_lookup.py index d5ac424..5c77af9 100644 --- a/tests/test_salary_lookup.py +++ b/tests/test_salary_lookup.py @@ -25,6 +25,39 @@ from salary_lookup import ( # --------------------------------------------------------------------------- class FormatEntryTests(unittest.TestCase): + PRIVACY_FOOTNOTE = "* N/A = Too few employees to publish (privacy)" + + def test_privacy_footnote_is_omitted_when_no_row_is_suppressed(self): + # The footnote explains the N/A* marker. Printed under a table where + # every row has an index, it asserts a privacy suppression that never + # happened (residual noted on #470). + entry = { + "company": "Example Corp", + "city": "", + "categories": {"all_employees": {"count": 500, "index": 108.5}}, + } + + rendered = format_entry(entry, {"index_baseline": 100, "index_label": "Index"}) + + self.assertNotIn("N/A", rendered) + self.assertNotIn(self.PRIVACY_FOOTNOTE, rendered) + self.assertRegex(rendered, r"All Employees\s+500\s+108\.5\s+\+8\.5%") + + def test_privacy_footnote_is_printed_when_a_row_is_suppressed(self): + entry = { + "company": "Example Corp", + "city": "", + "categories": { + "all_employees": {"count": 500, "index": 108.5}, + "small_team": {"count": 3, "index": None}, + }, + } + + rendered = format_entry(entry, {"index_baseline": 100, "index_label": "Index"}) + + self.assertRegex(rendered, r"Small Team\s+3\s+N/A\*") + self.assertIn(self.PRIVACY_FOOTNOTE, rendered) + def test_zero_count_is_displayed_as_zero(self): entry = { "company": "Example Corp",