def evaluate_blocking(records, ground_truth_pairs, blocking_fn): candidates = blocking_fn(records) true_matches = set(ground_truth_pairs) found = candidates & true_matches missed = true_matches - candidates recall = len(found) / len(true_matches) if true_matches else 0.0 reduction_ratio = 1.0 - len(candidates) / (len(records) * (len(records) - 1) / 2) return { "recall": recall, "candidate_pairs": len(candidates), "reduction_ratio": reduction_ratio, "missed_true_matches": len(missed), } # Name-only blocking: recall 0.73, candidate_pairs 42_000, missed 12_500 # Composite blocking: recall 0.94, candidate_pairs 59_000, missed 2_800