#!/usr/bin/env python3 """ Convert salary data from Excel to JSON format. This script converts an Excel file containing company salary data into the JSON format expected by salary_lookup.py. Prerequisites: pip install openpyxl Usage: python tools/convert_salary_excel.py python tools/convert_salary_excel.py --source "My Union Stats 2025" python tools/convert_salary_excel.py --baseline 100 --baseline-desc "Index 100 = median salary" The output file (salary_data.json) will be written to the repository root. Expected Excel format: - A header row with column names - A "Company" or "Firma" column (required) - An optional "City" or "By" column - Any number of numeric data columns (salary index, count, etc.) The script auto-detects the header row and column layout. For Excel files with paired count/index columns per category, it groups them automatically. """ import json import sys import argparse import re from pathlib import Path try: import openpyxl except ImportError: openpyxl = None # Column name patterns for auto-detection COMPANY_PATTERNS = {"firma", "company", "virksomhed", "employer", "arbejdsgiver"} CITY_PATTERNS = {"by", "city", "kommune", "location", "lokation", "sted"} COUNT_PATTERNS = {"antal", "count", "number", "n", "employees", "medarbejdere"} 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"} def header_matches(header, patterns): """Return True when a header contains a meaningful pattern match. Patterns match whole tokens; any pattern also listed in ``COMPOUND_PATTERNS`` may additionally match as a substring, to handle languages that form compound words. """ h = header.lower().strip() tokens = set(re.findall(r"[a-zæøåöäü0-9]+", h)) for p in patterns: if p in tokens: return True if p in COMPOUND_PATTERNS and p in h: return True return False def strip_type_patterns(header, patterns): """Remove count/index words from a header to derive a category name.""" name = header.lower() for p in patterns: name = re.sub(rf"(?