model_tools / PMPF_duplicate_scanner.py
Naphula's picture
Upload PMPF_duplicate_scanner.py
6f2444f verified
Raw History Blame Contribute Delete
5.94 kB
import os
import glob
import json
import pandas as pd
import numpy as np
# ================= CONFIGURATION =================
DATASET_FOLDER = os.path.join(os.getcwd(), "dataset")
# =================================================
class Colors:
HEADER = '\033[95m'
OKGREEN = '\033[92m'
WARNING = '\033[93m'
FAIL = '\033[91m'
ENDC = '\033[0m'
BOLD = '\033[1m'
# Windows CMD color support fix
os.system('color')
def make_hashable(val):
"""Converts dialogue structures or lists into a stable string for hashing."""
if val is None:
return ""
if isinstance(val, (list, np.ndarray, dict)):
try:
return json.dumps(val, sort_keys=True)
except Exception:
return str(val)
return str(val).strip()
def load_file_to_df(filepath):
"""Loads various file formats and returns a unified DataFrame with row tracking."""
ext = os.path.splitext(filepath)[1].lower()
filename = os.path.basename(filepath)
df = pd.DataFrame()
try:
if ext in ['.txt', '.md']:
with open(filepath, 'r', encoding='utf-8', errors='ignore') as f:
lines = f.read().split("\n\n") # Split by double newline as rough sample block
df = pd.DataFrame({'text': lines})
df['row_index'] = df.index + 1
elif ext == '.parquet':
df = pd.read_parquet(filepath)
df['row_index'] = df.index
elif ext == '.csv':
df = pd.read_csv(filepath)
df['row_index'] = df.index + 2 # +2 accounts for header offset in CSV viewers
elif ext in ['.json', '.jsonl']:
try:
df = pd.read_json(filepath, lines=True)
except ValueError:
df = pd.read_json(filepath)
df['row_index'] = df.index
if not df.empty:
df['source_file'] = filename
except Exception as e:
print(f"{Colors.FAIL}Error reading {filename}: {e}{Colors.ENDC}")
return df
def main():
print(f"{Colors.HEADER}{'='*80}")
print(" CROSS-FILE DATASET DUPLICATE TRACE SCANNER")
print(f"{'='*80}{Colors.ENDC}")
if not os.path.exists(DATASET_FOLDER):
print(f"{Colors.FAIL}Error: Dataset folder '{DATASET_FOLDER}' not found.{Colors.ENDC}")
return
files = glob.glob(os.path.join(DATASET_FOLDER, "*.*"))
if not files:
print(f"{Colors.WARNING}No files found in '{DATASET_FOLDER}'.{Colors.ENDC}")
return
# 1. Load and aggregate all files
all_data = []
print("Loading and indexing raw dataset files...")
for f in files:
df = load_file_to_df(f)
if not df.empty:
print(f" -> Indexed: {os.path.basename(f)} ({len(df)} rows)")
all_data.append(df)
if not all_data:
print(f"{Colors.FAIL}No valid data could be parsed.{Colors.ENDC}")
return
# Combine everything into a global tracking DataFrame
master_df = pd.concat(all_data, ignore_index=True)
# Determine which column to audit
target_column = None
for col in ['conversations', 'messages', 'text']:
if col in master_df.columns:
target_column = col
break
if not target_column:
# Fallback to the first non-meta column
meta_cols = {'source_file', 'row_index'}
valid_cols = [c for c in master_df.columns if c not in meta_cols]
target_column = valid_cols[0] if valid_cols else master_df.columns[0]
print(f"\nAuditing duplicates using key column: '{Colors.BOLD}{target_column}{Colors.ENDC}'")
# Hash values for exact matching
master_df['_hash'] = master_df[target_column].apply(make_hashable)
# 2. Find duplicates (excluding empty rows)
master_df = master_df[master_df['_hash'] != ""]
# Group by the hash and find groups with size > 1
dup_groups = master_df.groupby('_hash').filter(lambda x: len(x) > 1)
if dup_groups.empty:
print(f"\n{Colors.OKGREEN}🎉 Scan complete! No duplicate records found across any of the files.{Colors.ENDC}")
return
# 3. Report detailed trace
unique_dupe_hashes = dup_groups['_hash'].unique()
total_dupes_count = len(dup_groups) - len(unique_dupe_hashes)
print(f"\n{Colors.WARNING}⚠️ Found {len(unique_dupe_hashes)} unique texts that have duplicated copies ({total_dupes_count} redundant entries total).{Colors.ENDC}")
print("="*80)
print("DETAILED DUPLICATE TRACE REPORT:")
print("="*80)
# Show up to 15 duplicate groups to keep console readable
max_groups_to_show = 15
for idx, h in enumerate(unique_dupe_hashes[:max_groups_to_show]):
group = dup_groups[dup_groups['_hash'] == h]
# Extract a clean text preview
raw_val = group.iloc[0][target_column]
if isinstance(raw_val, list): # ShareGPT format preview
preview = f"[ShareGPT Conversation with {len(raw_val)} turns]"
else:
preview = str(raw_val).replace('\n', ' ')[:80] + "..."
print(f"\n{Colors.BOLD}Group #{idx+1} | Duplicate Content Preview:{Colors.ENDC}")
print(f" \"{Colors.OKGREEN}{preview}{Colors.ENDC}\"")
print(f" {Colors.BOLD}Found in following locations:{Colors.ENDC}")
for _, row in group.iterrows():
print(f" - File: {Colors.WARNING}{row['source_file']}{Colors.ENDC} | Row/Index: {row['row_index']}")
if len(unique_dupe_hashes) > max_groups_to_show:
print(f"\n... and {len(unique_dupe_hashes) - max_groups_to_show} more duplicate groups.")
print(f"\n{Colors.HEADER}{'='*80}{Colors.ENDC}")
if __name__ == "__main__":
main()