File size: 4,600 Bytes
7e2d97f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
"""
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}")