# Vendored from thoth_sqldb2 (thoth-dbmanager 0.7.4) — lsh/core.py # Copyright 2025 Marco Pancotti — Apache License 2.0 # Modifiche locali documentate in VENDORED.md. """Core LSH (MinHash) per la ricerca di valori simili nei campi del database.""" import logging from typing import Dict, List, Tuple from datasketch import MinHash, MinHashLSH from tqdm import tqdm def create_minhash(signature_size: int, string: str, n_gram: int) -> MinHash: m = MinHash(num_perm=signature_size) for d in [string[i : i + n_gram] for i in range(len(string) - n_gram + 1)]: m.update(d.encode("utf8")) return m # Token che, se presenti nel nome di una colonna, la marcano come "name-like": # nomi propri di persone/enti, i cui valori sono utili al value-matching e quindi # mai da escludere dall'indice LSH. Bilingue EN/IT (modifica locale, vedi VENDORED.md). # Per substring coprono le forme flesse: "nome" -> cognome/soprannome, # "name" -> surname/username, "denominazion" -> denominazione, ecc. NAME_LIKE_TOKENS: tuple[str, ...] = ( "name", "nome", "nominativ", "denominazion", "ragione_sociale", ) def skip_column( column_name: str, column_values: List[str], max_total_chars: int = 50000, max_avg_length: int = 20, name_tokens: tuple[str, ...] = NAME_LIKE_TOKENS, ) -> bool: lowered = column_name.lower() if any(token in lowered for token in name_tokens): return False sum_of_lengths = sum(len(value) for value in column_values) average_length = sum_of_lengths / len(column_values) return (sum_of_lengths > max_total_chars) and (average_length > max_avg_length) def jaccard_similarity(m1: MinHash, m2: MinHash) -> float: return m1.jaccard(m2) def create_lsh_index( unique_values: Dict[str, Dict[str, List[str]]], signature_size: int, n_gram: int, threshold: float, verbose: bool = True, ) -> Tuple[MinHashLSH, Dict[str, Tuple[MinHash, str, str, str]]]: lsh = MinHashLSH(threshold=threshold, num_perm=signature_size) minhashes: Dict[str, Tuple[MinHash, str, str, str]] = {} total = sum( len(column_values) for table_values in unique_values.values() for column_values in table_values.values() ) logging.info("Total unique values: %s", total) progress_bar = tqdm(total=total, desc="Creating LSH") if verbose else None for table_name, table_values in unique_values.items(): for column_name, column_values in table_values.items(): for idx, value in enumerate(column_values): minhash = create_minhash(signature_size, value, n_gram) minhash_key = f"{table_name}_{column_name}_{idx}" minhashes[minhash_key] = (minhash, table_name, column_name, value) lsh.insert(minhash_key, minhash) if progress_bar: progress_bar.update(1) if progress_bar: progress_bar.close() return lsh, minhashes