"""Stratified error-analysis sample over the name-join confusion matrices. differ: pairs (a, b, name) where both resolve to different keys, 8 per distro pair. unres: (distro, name) unresolved while a same-named package elsewhere resolves, 20 for openSUSE and 10 for each other distro. Prints stratum sizes and, per item, every distro's row and evidence for that name. The labels for the 2026-09-28 sample are in errors-2026-09-28.tsv. Usage: python3 eval/sample.py differ|unres [packages.sqlite] """ import itertools, random, sqlite3, sys DB = sys.argv[2] if len(sys.argv) > 2 else "data/packages.sqlite" SEED = 20260928 DISTROS = ["alpine", "arch", "debian", "opensuse"] db = sqlite3.connect(DB) def rows(name): out = {} for d, suite, key, basis, home in db.execute( "select distro, suite, coalesce(upstream_key,''), coalesce(basis,''), coalesce(homepage,'') " "from packages where package=? order by distro, suite", (name,)): ev = db.execute( "select source, value, coalesce(key,'') from evidence where distro=? and suite=? and package=? " "order by weight desc", (d, suite, name)).fetchall() out.setdefault(d, (suite, key, basis, home, ev)) return out def dump(tag, header, name): print(f"[{tag}] {header} name={name}") for d, (suite, key, basis, home, ev) in rows(name).items(): print(f" {d:8} {key or '-'} ({basis or 'unresolved'}) home={home}") seen, n = set(), 0 for src, val, k in ev: if val in seen: continue seen.add(val) n += 1 if n > 6: print(f" ... {len(set(v for _, v, _ in ev)) - 6} more") break print(f" {src}: {val[:110]}" + (f" -> {k}" if k else "")) print() rng = random.Random(SEED) resolved = {} for d, name, key in db.execute("select distro, package, coalesce(upstream_key,'') from packages"): if key or name not in resolved.get(d, {}): resolved.setdefault(d, {})[name] = key mode = sys.argv[1] if mode == "differ": for a, b in itertools.combinations(DISTROS, 2): pop = sorted(n for n in resolved[a].keys() & resolved[b].keys() if resolved[a][n] and resolved[b][n] and resolved[a][n] != resolved[b][n]) pick = rng.sample(pop, min(8, len(pop))) print(f"### stratum {a} vs {b}: {len(pop)} differing names, sampled {len(pick)}\n") for i, n in enumerate(sorted(pick)): dump(f"D-{a[:2]}{b[:2]}-{i}", f"{a} vs {b}", n) elif mode == "unres": want = {"opensuse": 20, "debian": 10, "arch": 10, "alpine": 10} for d in DISTROS: pop = sorted(n for n, k in resolved[d].items() if not k and any( resolved[o].get(n) for o in DISTROS if o != d)) pick = rng.sample(pop, want[d]) print(f"### stratum {d} unresolved, a counterpart resolved: {len(pop)} names, sampled {len(pick)}\n") for i, n in enumerate(sorted(pick)): dump(f"U-{d[:2]}-{i}", f"{d} unresolved", n)