mirror of
https://github.com/MadsLorentzen/ai-job-search.git
synced 2026-09-17 00:26:26 +00:00
fix(salary): parse localized numeric strings (#272)
This commit is contained in:
@@ -189,6 +189,46 @@ class DetectColumnTypeTests(unittest.TestCase):
|
||||
self.assertIn("salary_index", companies[0]["categories"])
|
||||
self.assertEqual(companies[0]["categories"]["salary_index"], {"index": 105.5})
|
||||
|
||||
def test_parse_sheet_accepts_comma_decimal_string_values(self):
|
||||
# Locale-formatted Excel exports can carry numeric cells as strings.
|
||||
# Danish decimal commas must not be silently dropped by float().
|
||||
ws = FakeWorksheet([
|
||||
("Company", "Engineering Count", "Engineering Index"),
|
||||
("Example Corp", "12,0", "108,5"),
|
||||
])
|
||||
|
||||
companies = parse_sheet(ws)
|
||||
|
||||
self.assertEqual(
|
||||
companies[0]["categories"]["engineering"],
|
||||
{"count": 12, "index": 108.5},
|
||||
)
|
||||
|
||||
def test_parse_sheet_accepts_danish_thousands_and_decimal_string(self):
|
||||
ws = FakeWorksheet([
|
||||
("Company", "Salary Index"),
|
||||
("Example Corp", "1.234,5"),
|
||||
])
|
||||
|
||||
companies = parse_sheet(ws)
|
||||
|
||||
self.assertEqual(
|
||||
companies[0]["categories"]["salary_index"],
|
||||
{"index": 1234.5},
|
||||
)
|
||||
|
||||
def test_parse_sheet_skips_ambiguous_single_comma_thousands_string(self):
|
||||
# In an English-locale export, "1,234" is probably 1234, but in a
|
||||
# decimal-comma locale it could be 1.234. Preserve the old safe-skip
|
||||
# behavior instead of guessing and writing a 1000x-wrong salary value.
|
||||
ws = FakeWorksheet([
|
||||
("Company", "Salary Index"),
|
||||
("Example Corp", "1,234"),
|
||||
])
|
||||
|
||||
companies = parse_sheet(ws)
|
||||
|
||||
self.assertEqual(companies[0]["categories"], {})
|
||||
|
||||
def test_parse_sheet_pairs_interleaved_count_index_columns_by_name(self):
|
||||
ws = FakeWorksheet([
|
||||
|
||||
@@ -54,6 +54,25 @@ COMPOUND_PATTERNS = {"antal", "indeks", "løn", "gennemsnit", "medarbejdere"}
|
||||
ID_PATTERNS = {"id", "personnummer"}
|
||||
|
||||
|
||||
def parse_numeric_cell(value):
|
||||
"""Parse numeric Excel values, including localized string cells."""
|
||||
if isinstance(value, (int, float)):
|
||||
return float(value)
|
||||
if not isinstance(value, str):
|
||||
raise ValueError("not numeric")
|
||||
|
||||
text = value.strip().replace("\u00a0", " ").replace(" ", "")
|
||||
if not text:
|
||||
raise ValueError("not numeric")
|
||||
if "," in text and "." in text:
|
||||
text = text.replace(".", "").replace(",", ".")
|
||||
elif "," in text:
|
||||
if re.fullmatch(r"[+-]?\d+,\d{3}", text):
|
||||
raise ValueError("ambiguous comma separator")
|
||||
text = text.replace(",", ".")
|
||||
return float(text)
|
||||
|
||||
|
||||
def header_matches(header, patterns):
|
||||
"""Return True when a header contains a meaningful pattern match.
|
||||
|
||||
@@ -211,12 +230,12 @@ def parse_sheet(ws, sheet_label=None):
|
||||
index_val = None
|
||||
if cat["count_col"] < len(row) and row[cat["count_col"]] is not None:
|
||||
try:
|
||||
count_val = int(row[cat["count_col"]])
|
||||
count_val = int(parse_numeric_cell(row[cat["count_col"]]))
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
if cat["index_col"] < len(row) and row[cat["index_col"]] is not None:
|
||||
try:
|
||||
index_val = float(row[cat["index_col"]])
|
||||
index_val = parse_numeric_cell(row[cat["index_col"]])
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
# A count/index pair that is entirely empty for this row carries
|
||||
@@ -228,7 +247,7 @@ def parse_sheet(ws, sheet_label=None):
|
||||
if cat["value_col"] < len(row) and row[cat["value_col"]] is not None:
|
||||
val = row[cat["value_col"]]
|
||||
try:
|
||||
val = float(val)
|
||||
val = parse_numeric_cell(val)
|
||||
except (ValueError, TypeError):
|
||||
# Non-numeric standalone value (e.g. a free-text "Notes"
|
||||
# column) is not salary data; skip it for this row.
|
||||
|
||||
Reference in New Issue
Block a user