MadsLorentzen/ai-job-search · error · ValueError
not numeric
Error message
not numeric
What it means
Raised by parse_numeric_cell when the value passed is neither a number nor a string (e.g. None, datetime, bool-handled elsewhere), so no numeric conversion is possible. The function only accepts int/float directly or str after localization normalization. Any other cell type is rejected as non-numeric.
Source
Thrown at tools/convert_salary_excel.py:62
INDEX_PATTERNS = {"indeks", "index", "idx", "salary", "løn", "median", "average", "gennemsnit"}
# "Compound" tokens: pattern words allowed to match as a substring of a larger
# header token, for languages that glue words together (e.g. Danish "lønindeks"
# -> løn + indeks). Languages that write headers as separate words need none.
# Ships populated for this repo's Danish demonstration data; a fork targeting
# another locale edits this constant.
COMPOUND_PATTERNS = {"antal", "indeks", "løn", "gennemsnit", "medarbejdere"}
# Identifier columns (employee id, Danish "personnummer", etc.) are never salary
# data. They are dropped at classification so they are not mistaken for a salary
# category. Matched as whole tokens only, like other pattern sets.
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:
# The separator that appears last is the decimal separator: European
# "1.234,56" and US "1,234.56" are both unambiguous here, unlike the
# single-separator cases below.
if text.rfind(",") > text.rfind("."):
text = text.replace(".", "").replace(",", ".")
else:
text = text.replace(",", "")
elif "," in text:
if re.fullmatch(r"[+-]?\d+,\d{3}", text):
raise ValueError("ambiguous comma separator")
text = text.replace(",", ".")
elif "." in text:
if re.fullmatch(r"[+-]?\d+\.\d{3}", text):View on GitHub (pinned to 79cd383e58)
Solutions
- Skip or default empty/None cells before calling parse_numeric_cell (e.g. `if value is None or value == '': continue`)
- Convert datetimes to numbers or filter them before parsing
- If reading via openpyxl, ensure you pass cell.value, not the Cell object
- Wrap the call in try/except ValueError and report the offending row
Example fix
// before
num = parse_numeric_cell(row['salary'])
// after
if row['salary'] is None:
continue
num = parse_numeric_cell(row['salary']) Defensive patterns
Strategy: validation
Validate before calling
raw = row['salary']
if raw is None or isinstance(raw, (str, int, float)) is False:
continue # or: raw = raw.value if hasattr(raw, 'value') else None
if isinstance(raw, str) and not raw.strip():
continue Type guard
def is_parseable_cell(v) -> bool:
return v is None or isinstance(v, (int, float, str)) Try / catch
try:
num = parse_numeric_cell(value)
except ValueError as e:
logger.warning('skipping non-numeric cell %r: %s', value, e)
continue Prevention
- Always unwrap openpyxl cells to .value before parsing
- Skip None cells in the sheet loop rather than letting them reach the parser
- Log raw values on failure so bad rows are traceable
When it happens
Trigger: Calling parse_numeric_cell with a None (empty Excel cell), a datetime.datetime object, a bool, or a cell object from openpyxl/xlrd that wasn't unwrapped to a raw value first. Happens via parse_sheet when a spreadsheet column contains blanks or dates.
Common situations: Empty cells in a salary column read with openpyxl (which yields None for blanks), date-formatted cells, or forgetting to use .value on cell objects. Also pandas NaN leaking through if the sheet was loaded with na values kept.
Related errors
AI-assisted analysis of MadsLorentzen/ai-job-search@79cd383e58 (2026-08-27).
Data as JSON: /api/errors/32d4dc84d1bd315e.
Report an issue: GitHub.