"""Pure normalization rules: company-name spellings and invoice-number formats.""" import re def company_key(name): """Grouping key for company-name spellings: case/punctuation/whitespace-insensitive, the German legal-form spellings unified (GmbH == Ges.m.b.H. == GesmbH, 'und' == '&'). Different legal forms (GmbH vs AG) stay different — near-misses beyond this are only SUGGESTED for manual merge.""" n = (name or "").strip().lower() n = n.replace(" und ", " & ").replace(" u. ", " & ") n = re.sub(r"ges\.?\s*m\.?\s*b\.?\s*h\.?", "gmbh", n) n = re.sub(r"[^a-z0-9äöüß&]+", "", n) return n.replace("gbmh", "gmbh") # 'GbmH' is a transposition typo, not a different legal form def invoice_seq(number, year): """Canonical integer sequence of an invoice number for the given 4-digit year, whatever the format: '202262' -> 62, '2022-62' -> 62, '22-62' -> 62, '62-2015' -> 62 (SEQ-YEAR), '2026-01' -> 1, '62' -> 62. Combined / concatenated numbers ('41/46', '5556') return None so they never pass for a single invoice.""" toks = [t for t in re.split(r"\D+", str(number or "")) if t] if not toks: return None if len(toks) == 1: # bare '25' / glued 'YYYYNN' / 'YYNN' s = toks[0] if s == year: return None # a bare 4-digit year is not a sequence if year and s.startswith(year) and len(s) > len(year): s = s[len(year):] # 'YYYYNN' elif year and s.startswith(year[2:]) and len(s) > 2: s = s[2:] # 'YYNN' # else keep s verbatim: a bare sequence ('25','62') even when it equals the 2-digit year (invoice #25 in 2025) else: # PREFIX-SEQ / SEQ-YEAR: drop ONE year token non_year = list(toks) for yt in (year, year[2:]): if yt in non_year: non_year.remove(yt) break if len(non_year) != 1: return None # combined ('41','46') -> no single sequence s = non_year[0] s = s.lstrip("0") or s return int(s) if s.isdigit() and len(s) <= 3 else None def invoice_year(number): """The 4-digit year an invoice number encodes: '2025-46'->'2025', '25-46'/'26-01'->20YY, else None. A New-Year invoice dated in January can still carry the old year's number; this is that year.""" s = str(number or "") m = re.search(r"(?:19|20)\d\d", s) if m: return m.group(0) m = re.match(r"\s*(\d{2})\D", s) # 'YY-NN' 2-digit prefix return "20" + m.group(1) if m else None