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

  1. Skip or default empty/None cells before calling parse_numeric_cell (e.g. `if value is None or value == '': continue`)
  2. Convert datetimes to numbers or filter them before parsing
  3. If reading via openpyxl, ensure you pass cell.value, not the Cell object
  4. 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

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.