Number_one / explore_store.py
EnriqueAlves's picture
Add ingestion/OCR/exploration tooling + improvements roadmap; ignore local artifacts
7e2d97f
Raw
History Blame Contribute Delete
4.6 kB
"""
Explore ChromaDB store
======================
Lecture seule (aucun appel Azure). Donne un état complet de la base :
- total de chunks, nombre de mémoires distincts
- répartition par catégorie
- chunks par mémoire (min / max / moyenne)
- DOUBLONS : (source, chunk_index) répétés = fichier ingéré plusieurs fois
- DOUBLONS de texte exact (chunks identiques)
- mémoires manquants vs train_data/
Usage : ./.venv/Scripts/python.exe explore_store.py
"""
import os
import hashlib
from pathlib import Path
from collections import Counter, defaultdict
os.environ.setdefault("ANONYMIZED_TELEMETRY", "False")
import chromadb
from chromadb.config import Settings
PROJECT_ROOT = Path(__file__).parent
DATA_DIR = Path("/data") if Path("/data").is_dir() else PROJECT_ROOT / "data"
TRAIN_DATA_DIR = Path("/train_data") if Path("/train_data").is_dir() else PROJECT_ROOT / "train_data"
COLLECTION_NAME = "rag_documents"
client = chromadb.PersistentClient(path=str(DATA_DIR / "chroma_db"), settings=Settings(anonymized_telemetry=False))
col = client.get_or_create_collection(name=COLLECTION_NAME, metadata={"hnsw:space": "cosine"})
total = col.count()
print("=" * 70)
print(f"TOTAL CHUNKS EN BASE : {total}")
print("=" * 70)
per_source = Counter() # chunks par mémoire
src_idx = Counter() # (source, chunk_index) -> nb d'occurrences
text_hashes = Counter() # hash texte -> nb d'occurrences
id_counter = Counter() # ids -> détecte doublons d'id
empty_texts = 0
short_texts = 0
hash_to_example = {}
off, step = 0, 2000
while off < total:
got = col.get(include=["metadatas", "documents"], limit=step, offset=off)
ids = got["ids"]
metas = got["metadatas"]
docs = got["documents"]
for _id, m, d in zip(ids, metas, docs):
id_counter[_id] += 1
src = m.get("source", "??")
idx = m.get("chunk_index", -1)
per_source[src] += 1
src_idx[(src, idx)] += 1
d = d or ""
if not d.strip():
empty_texts += 1
elif len(d.strip()) < 30:
short_texts += 1
h = hashlib.md5(d.encode("utf-8", errors="ignore")).hexdigest()
text_hashes[h] += 1
if h not in hash_to_example:
hash_to_example[h] = (src, d[:80].replace("\n", " "))
off += step
print(f" ...lu {min(off, total)}/{total}")
print()
print(f"Mémoires distincts (sources) : {len(per_source)}")
print(f"IDs en double : {sum(1 for v in id_counter.values() if v > 1)}")
print()
# --- DOUBLONS (source, chunk_index) -> fichier ingéré plusieurs fois ---
dup_pairs = {k: v for k, v in src_idx.items() if v > 1}
dup_sources = sorted({src for (src, _idx) in dup_pairs})
print("-" * 70)
print(f"DOUBLONS (source, chunk_index) répétés : {len(dup_pairs)} paires")
print(f" -> mémoires concernés (ingérés >1 fois) : {len(dup_sources)}")
for s in dup_sources[:30]:
occ = max(v for (src, _i), v in src_idx.items() if src == s)
print(f" x{occ} {s} ({per_source[s]} chunks au total)")
print()
# --- DOUBLONS de texte exact ---
dup_text = {h: v for h, v in text_hashes.items() if v > 1}
nb_extra = sum(v - 1 for v in dup_text.values())
print("-" * 70)
print(f"Textes EXACTEMENT identiques : {len(dup_text)} contenus dupliqués "
f"= {nb_extra} chunks redondants")
for h, v in sorted(dup_text.items(), key=lambda x: -x[1])[:10]:
src, ex = hash_to_example[h]
print(f" x{v} [{src}] «{ex}…»")
print()
print(f"Chunks vides : {empty_texts}")
print(f"Chunks < 30 car: {short_texts}")
print()
# --- Répartition par catégorie ---
cat = Counter()
cat_docs = defaultdict(set)
for src, n in per_source.items():
c = src.split("\\")[0].split("/")[0]
cat[c] += n
cat_docs[c].add(src)
print("-" * 70)
print("RÉPARTITION PAR CATÉGORIE :")
for c, n in cat.most_common():
print(f" {c:<22} {len(cat_docs[c]):>3} mémoires | {n:>7} chunks")
print()
# --- chunks par mémoire ---
counts = sorted(per_source.values())
if counts:
print("-" * 70)
print(f"Chunks par mémoire : min={counts[0]} | max={counts[-1]} | "
f"moyenne={sum(counts)//len(counts)}")
# --- mémoires manquants vs disque ---
if TRAIN_DATA_DIR.exists():
allfiles = set(str(p.relative_to(TRAIN_DATA_DIR)) for p in TRAIN_DATA_DIR.rglob("*.pdf"))
missing = sorted(allfiles - set(per_source.keys()))
print()
print("-" * 70)
print(f"PDF sur disque : {len(allfiles)} | en base : {len(per_source)} | MANQUANTS : {len(missing)}")
for m in missing[:50]:
print(f" - {m}")