fix(salary): collapse line breaks in wrapped workbook headers (#643)

* fix(salary): collapse line breaks in wrapped workbook headers

A wrapped header cell ("Median\nsalary", "Løn-\nindeks") kept its line
break, so the converter derived a category name containing a newline:
the column no longer paired with its count column, and salary_lookup
printed the category label across two lines.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(changelog): record fix for #643

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* changelog: place the #643 entry under [Unreleased]

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Mads Lorentzen <madslorentzen_17@hotmail.com>
This commit is contained in:
authored and GitHub committed 2026-10-19 19:50:53 +02:00
1 parent 51c7dfdbd7
commit 1ec64d243c
3 files changed
+60 -2

No files matched your search

+6
View File
@@ -96,6 +96,12 @@ per-file diff commands.
"newly expired" by the sweep, then re-ranked by `apply` from its fresh deadline, so the same job appeared "newly expired" by the sweep, then re-ranked by `apply` from its fresh deadline, so the same job appeared
as both expired and shortlisted. Keys are now stripped before matching. as both expired and shortlisted. Keys are now stripped before matching.
- **Salary workbook conversion handles wrapped header cells** (#643, `tools/convert_salary_excel.py`,
`tests/test_salary_wrapped_header_cli.py`) - a header cell with a line break inside it
("Median⏎salary", "Løn-⏎indeks") kept the break in the derived category name, so the column no
longer paired with its count column and `salary_lookup.py` printed the label across two lines.
Header whitespace runs now collapse to one space, so a wrapped header converts like its one-line spelling.
## [1.7.3] - 2026-10-19 ## [1.7.3] - 2026-10-19
### Added ### Added
+50
View File
@@ -0,0 +1,50 @@
"""Wrapped workbook header cells must convert like their one-line spelling."""
import json
from pathlib import Path
import subprocess
import sys
from tempfile import TemporaryDirectory
import unittest
try:
import openpyxl
except ImportError:
openpyxl = None
TOOL = Path(__file__).resolve().parent.parent / "tools" / "convert_salary_excel.py"
@unittest.skipUnless(openpyxl, "requires optional openpyxl (installed in CI)")
class WrappedHeaderCLI(unittest.TestCase):
def convert(self, header):
with TemporaryDirectory() as directory:
root = Path(directory)
workbook, output = root / "salary.xlsx", root / "salary_data.json"
book = openpyxl.Workbook()
try:
book.active.append(header)
book.active.append(["Acme", "Aarhus", 12, 104.5])
book.save(workbook)
finally:
book.close()
result = subprocess.run(
[sys.executable, str(TOOL), str(workbook), "--output", str(output)],
cwd=root, capture_output=True, text=True,
)
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
return json.loads(output.read_text(encoding="utf-8"))["companies"][0]["categories"]
def test_wrapped_headers_pair_like_their_single_line_spelling(self):
expected = {"all_employees": {"count": 12, "index": 104.5}}
self.assertEqual(self.convert(["Company", "City", "Employees", "Median salary"]), expected)
self.assertEqual(self.convert(["Company", "City", "Employees", "Median\nsalary"]), expected)
self.assertEqual(self.convert(["Firma", "By", "Antal\nmedarbejdere", "Løn-\nindeks"]), expected)
def test_wrapped_category_name_has_no_line_break(self):
categories = self.convert(["Company", "City", "Count\nengineers", "Index\nengineers"])
self.assertEqual(categories, {"engineers": {"count": 12, "index": 104.5}})
if __name__ == "__main__":
unittest.main()
+4 -2
View File
@@ -186,10 +186,12 @@ def parse_sheet(ws, sheet_label=None):
print(f"Warning: Could not find header row in sheet '{ws.title}'. Skipping.", file=sys.stderr) print(f"Warning: Could not find header row in sheet '{ws.title}'. Skipping.", file=sys.stderr)
return [] return []
# Read headers # Read headers. Wrapped header cells carry a line break ("Median\nsalary"),
# which would survive into the category name and keep the column from
# pairing with its count, so collapse every whitespace run to one space.
headers = [] headers = []
for cell in ws[header_row]: for cell in ws[header_row]:
headers.append(str(cell.value).strip() if cell.value else "") headers.append(" ".join(str(cell.value).split()) if cell.value else "")
# Find company and city columns # Find company and city columns
company_col = None company_col = None