mirror of
https://github.com/MadsLorentzen/ai-job-search.git
synced 2026-09-17 00:26:26 +00:00
refactor(salary): optimize search match scoring and normalize Excel category keys (#101)
This commit improves the performance and consistency of the salary tools: - Redundant query normalization and word extraction are eliminated in salary_lookup.py by pre-calculating representations once before the search loop. - A match_score_optimized helper is introduced to perform the comparison using the pre-calculated query data, preserving full backward compatibility for match_score. - Normalization in tools/convert_salary_excel.py is unified: paired column headers now consistently substitute spaces and dashes with underscores (e.g. 'software_engineering') to match the single-column formatting. - Unit test coverage is significantly expanded in tests/test_salary_lookup.py and tests/test_convert_salary_excel.py to cover normalization, anglicization, search filtering, and matching behaviors.
This commit is contained in:
@@ -77,6 +77,16 @@ class DetectColumnTypeTests(unittest.TestCase):
|
||||
|
||||
self.assertEqual(companies[0]["categories"]["accounting"], {"count": 12, "index": 105.5})
|
||||
|
||||
def test_parse_sheet_normalizes_paired_category_name_with_underscores(self):
|
||||
ws = FakeWorksheet([
|
||||
("Company", "Software Engineering Count", "Software Engineering Index"),
|
||||
("Example Corp", 8, 110.0),
|
||||
])
|
||||
|
||||
companies = parse_sheet(ws)
|
||||
|
||||
self.assertEqual(companies[0]["categories"]["software_engineering"], {"count": 8, "index": 110.0})
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -2,7 +2,14 @@
|
||||
|
||||
import unittest
|
||||
|
||||
from salary_lookup import format_entry, match_score, search_company
|
||||
from salary_lookup import (
|
||||
format_entry,
|
||||
normalize,
|
||||
anglicize,
|
||||
extract_core_words,
|
||||
match_score,
|
||||
search_company,
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -158,6 +165,68 @@ class SearchCompanyTests(unittest.TestCase):
|
||||
self.assertEqual(results, [])
|
||||
|
||||
|
||||
class UtilityTests(unittest.TestCase):
|
||||
def test_normalize_strips_suffix_and_noise(self):
|
||||
self.assertEqual(normalize("Novo Nordisk A/S"), "novonordisk")
|
||||
self.assertEqual(normalize("Ørsted (VG) Holding"), "ørsted")
|
||||
self.assertEqual(normalize("Chr. Hansen, Denmark Division"), "chrhansen")
|
||||
self.assertEqual(normalize("Simple Corp ApS"), "simplecorp")
|
||||
|
||||
def test_anglicize_replaces_danish_chars(self):
|
||||
self.assertEqual(anglicize("ørsted"), "orsted")
|
||||
self.assertEqual(anglicize("mærsk"), "maersk")
|
||||
self.assertEqual(anglicize("ålborg"), "aalborg")
|
||||
|
||||
def test_extract_core_words(self):
|
||||
self.assertEqual(extract_core_words("Novo Nordisk A/S"), ["novo", "nordisk"])
|
||||
self.assertEqual(extract_core_words("A/S"), [])
|
||||
self.assertEqual(extract_core_words("Test Company (Sub-entity)"), ["test", "company"])
|
||||
|
||||
|
||||
class MatchScoreTests(unittest.TestCase):
|
||||
def test_exact_match_score(self):
|
||||
self.assertEqual(match_score("Novo Nordisk", "Novo Nordisk"), 100)
|
||||
self.assertEqual(match_score("novo nordisk", "Novo Nordisk A/S"), 100)
|
||||
|
||||
def test_partial_match_score(self):
|
||||
self.assertGreater(match_score("Novo", "Novo Nordisk A/S"), 80)
|
||||
self.assertEqual(match_score("Novo Nordisk", "Novo"), 75)
|
||||
|
||||
def test_anglicized_match_score(self):
|
||||
self.assertEqual(match_score("Orsted", "Ørsted A/S"), 85)
|
||||
|
||||
def test_overlap_match_score(self):
|
||||
# Overlap of multiple words
|
||||
self.assertGreater(match_score("Novo Tech", "Novo Nordisk Tech A/S"), 30)
|
||||
|
||||
def test_no_match_score(self):
|
||||
self.assertEqual(match_score("Google", "Microsoft"), 0)
|
||||
|
||||
|
||||
class SearchCompanyRefactoredTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.data = {
|
||||
"companies": [
|
||||
{"company": "Novo Nordisk A/S", "city": "Bagsværd"},
|
||||
{"company": "Ørsted", "city": "Fredericia"},
|
||||
{"company": "Vestas Wind Systems", "city": "Aarhus"},
|
||||
]
|
||||
}
|
||||
|
||||
def test_search_by_name(self):
|
||||
results = search_company(self.data, "Novo")
|
||||
self.assertEqual(len(results), 1)
|
||||
self.assertEqual(results[0]["company"], "Novo Nordisk A/S")
|
||||
|
||||
def test_search_with_city_filter(self):
|
||||
results = search_company(self.data, "Ørsted", city="Fredericia")
|
||||
self.assertEqual(len(results), 1)
|
||||
|
||||
# Mismatching city
|
||||
results_wrong_city = search_company(self.data, "Ørsted", city="Bagsværd")
|
||||
self.assertEqual(len(results_wrong_city), 0)
|
||||
|
||||
|
||||
class TestSearchCompanyBasicMatch(unittest.TestCase):
|
||||
def test_exact_name_returns_match(self):
|
||||
data = _make_data(_entry("Novo Nordisk", "Bagsværd"))
|
||||
|
||||
Reference in New Issue
Block a user