"""THE DECISIVE TEST: does GLOBAL name uniqueness rescue the empty-address pairs? WHY. My earlier rescue test scored each pair in isolation: 'exact normalised name match' on empty-address candidates gave 442 TP / 3,074 FP = 0.14:1 against a ~3.76:1 break-even, and I concluded the pairs were unrecoverable. That test never asked whether any OTHER S1 entity was competing for the record - which is global information, and is what `new plan.md` Step 1/Step 4 and LIFTOVER R3 both propose (reverse search, competing-owner features). Measured on the raw data: of the empty-address pool records whose core name matches at least one S1, 55% (train) / 59% (test) match EXACTLY ONE S1. For those the assignment is forced: there is no competitor. That is a completely different question from 'is this pair similar', and it is the most plausible explanation for a 0.99066 leaderboard score. The competitor count is computed over ALL 2.2M train S1 entities, not just the 20k val slice, because a competitor can be anywhere in the data. That is the whole point. """ import collections import json import os import re import sys import unicodedata import joblib import numpy as np import pandas as pd sys.path.insert(0, "src") import config as C import decide as DC from evaluate import macro_f05 RAW = os.environ.get("RAW_DIR", "/scratch/user4/dai/dataset_unstop/dataset/train") _WS = re.compile(r"\s+") _PUN = re.compile(r"[^\w\s]", re.UNICODE) LEGAL = set("inc incorporated llc ltd limited corp corporation co company lp llp pllc pc pa plc " "trust holdings pvt private pte sa sarl sas sasu sci eurl snc".split()) def core(s): s = "".join(c for c in unicodedata.normalize("NFKD", str(s)) if not unicodedata.combining(c)) s = _WS.sub(" ", _PUN.sub(" ", s.casefold())).strip() return " ".join(t for t in s.split() if t not in LEGAL) b = joblib.load("work/results_A/model_bundle.joblib") MM, THR = b["miss_mass"], b["param"] CACHE = "work/val_scored_20000.parquet" cand = pd.read_parquet(CACHE) truths = {int(k): set(v) for k, v in json.load(open(CACHE + ".truth.json")).items()} ids = sorted(truths) shipped = DC.predict_sets(cand, "q", "c", "p", b["method"], THR, MM, True) m0, _ = macro_f05(shipped, truths, ids) print(f"baseline F0.5 {m0['f05']:.5f} P {m0['precision']:.4f} R {m0['recall']:.4f}") p = C.PATHS["train"] s1p = pd.read_parquet(p["source1"], columns=["entity_id"]) s1_ids = s1p["entity_id"].values pool_ids = np.concatenate([ pd.read_parquet(p["source2"], columns=["entity_id"])["entity_id"].values, pd.read_parquet(p["source3"], columns=["entity_id"])["entity_id"].values]) s1raw = pd.read_csv(os.path.join(RAW, "train_source1.tsv"), sep="\t", dtype=str, keep_default_na=False, na_filter=False) print(f"all S1 entities (the competitor universe): {len(s1raw):,}") s1_core = {} name_count = collections.Counter() for e, n, c in zip(s1raw["entity_id"], s1raw["business_name"], s1raw["country"]): k = core(n) + "\x00" + c s1_core[e] = k name_count[k] += 1 praw = {} for src in (2, 3): d = pd.read_csv(os.path.join(RAW, f"train_source{src}.tsv"), sep="\t", dtype=str, keep_default_na=False, na_filter=False) for e, n, a, c in zip(d["entity_id"], d["business_name"], d["business_address"], d["country"]): praw[e] = (core(n) + "\x00" + c, not a.strip()) qv = cand["q"].to_numpy() cv = cand["c"].to_numpy() pv = cand["p"].to_numpy() y = np.array([1 if c in truths.get(q, ()) else 0 for q, c in zip(qv, cv)], np.int8) selp = {(q, c) for q, cs in shipped.items() for c in cs} sel = np.array([1 if (q, c) in selp else 0 for q, c in zip(qv, cv)], np.int8) rows = [] for i in range(len(cand)): ck, ea = praw[pool_ids[cv[i]]] qk = s1_core[s1_ids[qv[i]]] rows.append((ea, ck == qk and ck.split("\x00")[0] != "", name_count.get(ck, 0))) F = pd.DataFrame(rows, columns=["empty_addr", "name_eq", "n_competitors"]) F["y"] = y F["sel"] = sel F["p"] = pv F["q"] = qv F["c"] = cv ea = F[F.empty_addr] print(f"\nempty-address candidate pairs in val: {len(ea):,}") un = ea[ea.sel == 0] print(f"currently REJECTED: {len(un):,} ({int((un.y==1).sum()):,} true, " f"{int((un.y==0).sum()):,} false)") print("\n" + "=" * 78) print("EXACT NAME MATCH, split by HOW MANY S1 ENTITIES COMPETE for that record") print("=" * 78) m = un[un.name_eq] print(f" rejected empty-address pairs with an exact core-name match: {len(m):,}") print(f"{'n_competitors':>14}{'TP':>8}{'FP':>9}{'ratio':>9} (needs > 3.76)") for lo, hi, lab in [(1, 1, "1 (forced)"), (2, 2, "2"), (3, 5, "3-5"), (6, 10, "6-10"), (11, 10 ** 9, "11+")]: s = m[(m.n_competitors >= lo) & (m.n_competitors <= hi)] tp, fp = int((s.y == 1).sum()), int((s.y == 0).sum()) if tp + fp == 0: continue r = tp / fp if fp else float("inf") flag = " <== CLEARS" if r > 3.76 else "" print(f"{lab:>14}{tp:>8,}{fp:>9,}{r:>9.2f}{flag}") print("\n" + "=" * 78) print("RULES combining uniqueness with the pairwise score") print("=" * 78) print(f"{'rule':<52}{'TP':>7}{'FP':>8}{'ratio':>8}") rules = [ ("name_eq AND n_competitors==1", (un.name_eq) & (un.n_competitors == 1)), ("name_eq AND n_comp==1 AND p>=0.05", (un.name_eq) & (un.n_competitors == 1) & (un.p >= 0.05)), ("name_eq AND n_comp==1 AND p>=0.20", (un.name_eq) & (un.n_competitors == 1) & (un.p >= 0.20)), ("name_eq AND n_comp==1 AND p>=0.40", (un.name_eq) & (un.n_competitors == 1) & (un.p >= 0.40)), ("name_eq AND n_comp<=2", (un.name_eq) & (un.n_competitors <= 2)), ("name_eq AND n_comp<=2 AND p>=0.20", (un.name_eq) & (un.n_competitors <= 2) & (un.p >= 0.20)), ] best = None for name, mask in rules: tp, fp = int((mask & (un.y == 1)).sum()), int((mask & (un.y == 0)).sum()) if tp + fp == 0: print(f" {name:<50}{0:>7}{0:>8}{'-':>8}") continue r = tp / fp if fp else float("inf") flag = " <== CLEARS" if r > 3.76 else "" print(f" {name:<50}{tp:>7,}{fp:>8,}{r:>8.2f}{flag}") if r > 3.76 and (best is None or tp > best[1]): best = (name, tp, fp, mask) print("\n" + "=" * 78) print("END TO END, held out on half the entities") print("=" * 78) h = np.array([hash(("half", int(q))) % 2 for q in ids]) HA = set(np.array(ids)[h == 0].tolist()) HAl = sorted(HA) HB = [q for q in ids if q not in HA] def ev(pr, idl): return macro_f05({q: pr.get(q, []) for q in idl}, truths, idl)[0]["f05"] bA, bB = ev(shipped, HAl), ev(shipped, HB) print(f"baseline half A {bA:.5f} half B {bB:.5f}") if best is None: print("\nNothing clears the break-even even with global uniqueness. Then the empty-address") print("pairs really are unrecoverable and 0.99066 must come from somewhere else entirely.") else: name, tp, fp, mask = best add = un[mask] newpred = {q: set(v) for q, v in shipped.items()} for q, c in zip(add["q"], add["c"]): newpred.setdefault(int(q), set()).add(int(c)) pr = {q: list(v) for q, v in newpred.items()} a, bb = ev(pr, HAl), ev(pr, HB) print(f"\nbest: {name} (+{len(add):,} pairs, {tp} TP / {fp} FP)") print(f" half A {a:.5f} ({a-bA:+.5f})") print(f" half B {bb:.5f} ({bb-bB:+.5f}) <-- HELD OUT, the honest number") dup = collections.Counter() for q, cs in newpred.items(): for c in cs: dup[c] += 1 print(f" one-owner violations: {sum(1 for v in dup.values() if v > 1):,}") print("\n" + "=" * 78) print("SCALE-UP: how many such pairs exist across ALL of val, not just rejected ones?") print("=" * 78) allm = ea[(ea.name_eq) & (ea.n_competitors == 1)] print(f" empty-address + exact name + unique competitor: {len(allm):,} pairs") print(f" already selected : {int((allm.sel == 1).sum()):,}") print(f" true : {int((allm.y == 1).sum()):,}") print(f" -> per entity that is {len(allm)/len(ids):.4f}; scaled to 1.73M test entities " f"about {len(allm)/len(ids)*1732544:,.0f} pairs")