Dosarul document_store din Drive are 3 surse .xml pe care depozitul le ignora
complet, fiindca store.py accepta doar .txt/.md. La d406_saft_knowledge exista
ambele formate, iar .xml e cu trei luni mai nou (2026-01-28 vs 2025-10-15) si cu
50% mai mare (64 KB vs 41 KB) — deci indexam varianta mai saraca.
- store.py devine sursa unica pentru extensii (DOC_EXTENSIONS = .txt/.md/.xml).
Cand acelasi nume de baza exista in mai multe formate, la indexare intra unul
singur, cel mai bogat (.xml > .md > .txt); celalalt ramane pe disc, marcat
`shadowed_by`. Fara asta, acelasi raspuns ar aparea de doua ori in rezultate.
`list_documents()` arata tot (dashboard), `documents_for_index()` doar
castigatorii (indexer).
- indexer.py taie XML-ul altfel: un chunk per element de nivel 1, adica o
problema = un chunk, cu <mesaj_eroare> si <rezolvare> impreuna. Taierea pe
linii goale le-ar separa si cautarea ar returna eroarea fara raspuns.
Etichetele raman prefixe lizibile ("mesaj eroare: ..."), fara paranteze
unghiulare care doar dilueaza embedding-ul. XML invalid nu opreste indexarea:
cade pe taierea obisnuita, cu o linie in log. Elementele peste 4000 de
caractere se taie mai departe pe granite de cuvant — `chunk_text` imparte doar
pe linii goale, deci un element scris ca un paragraf lung ar fi ramas intreg
(prins de test).
- sync.py: amprenta si `rclone --include` derivate din DOC_EXTENSIONS.
- dashboard: acelasi filtru si aceeasi preferinta (copie, fiindca nu poate
importa `store` — coliziune de nume pe `config`), plus marcajul "umbrit de X"
in tabelul de documente si numarul de documente chiar indexate.
- README: sectiunea Drive rescrisa pe `rclone authorize` (autorizezi pe o masina
cu browser, muti tokenul) in loc de cont de serviciu — mai putini pasi, fara
consola Google Cloud. Documentat si ca `sync` sterge local ce nu mai e in Drive.
tests/ nou (20 de teste, fara retea si fara Ollama): preferinta de format,
vizibilitatea in dashboard, taierea XML, entitati, comentarii, XML invalid,
elemente uriase. Suita puntii Discord: 426 pass, neafectata.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4uzvgm7AyJch5WH8QHRhY
115 lines
3.8 KiB
Python
115 lines
3.8 KiB
Python
"""Ce intra in index (preferinta de format) si cum se taie XML-ul in chunk-uri."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
import indexer
|
|
import store
|
|
|
|
XML = """<?xml version="1.0" encoding="UTF-8"?>
|
|
<erori_rag versiune="1.0">
|
|
<!-- comentariu -->
|
|
<eroare_stocuri_negative>
|
|
<keywords>stocuri negative NIR</keywords>
|
|
<mesaj_eroare>Documentul nu poate fi sters: stocuri negative pentru BANDA HARTIE.</mesaj_eroare>
|
|
<rezolvare>Optiuni firma > VERIFICARESTOCNEGATIV = 0.</rezolvare>
|
|
</eroare_stocuri_negative>
|
|
<eroare_a_doua>
|
|
<mesaj_eroare>Alta eroare.</mesaj_eroare>
|
|
<rezolvare>Alta rezolvare.</rezolvare>
|
|
</eroare_a_doua>
|
|
</erori_rag>"""
|
|
|
|
|
|
# ------------------------------------------------------- preferinta de format
|
|
def test_xml_umbreste_md_cu_acelasi_nume(write):
|
|
write("d406.xml", "<r><a>x</a></r>")
|
|
write("d406.md", "versiune veche")
|
|
umbrite = {d["name"]: d["shadowed_by"] for d in store.list_documents()}
|
|
assert umbrite["d406.md"] == "d406.xml"
|
|
assert umbrite["d406.xml"] is None
|
|
assert [d["name"] for d in store.documents_for_index()] == ["d406.xml"]
|
|
|
|
|
|
def test_md_umbreste_txt(write):
|
|
write("x.md", "md")
|
|
write("x.txt", "txt")
|
|
assert [d["name"] for d in store.documents_for_index()] == ["x.md"]
|
|
|
|
|
|
def test_fisierele_fara_pereche_raman_toate(write):
|
|
write("bilant.md", "a")
|
|
write("roagest.xml", "<r><a>b</a></r>")
|
|
write("note.txt", "c")
|
|
assert len(store.documents_for_index()) == 3
|
|
|
|
|
|
def test_depozitul_e_vizibil_intreg_chiar_daca_nu_tot_se_indexeaza(write):
|
|
write("d406.xml", "<r><a>x</a></r>")
|
|
write("d406.md", "vechi")
|
|
assert len(store.list_documents()) == 2 # dashboard le arata pe ambele
|
|
assert len(store.documents_for_index()) == 1 # indexul ia doar una
|
|
|
|
|
|
def test_extensiile_necunoscute_sunt_ignorate(write):
|
|
write("chatflow.json", "{}")
|
|
write("script.ps1", "echo")
|
|
assert store.list_documents() == []
|
|
|
|
|
|
@pytest.mark.parametrize("name", ["a.xml", "a.md", "a.txt"])
|
|
def test_numele_acceptate(name):
|
|
assert store.validate_name(name) == name
|
|
|
|
|
|
@pytest.mark.parametrize("name", ["a.json", "a.pdf", "../a.md", "a b.md"])
|
|
def test_numele_respinse(name):
|
|
with pytest.raises(ValueError):
|
|
store.validate_name(name)
|
|
|
|
|
|
# ----------------------------------------------------------- chunking XML
|
|
def test_o_problema_ramane_un_singur_chunk():
|
|
"""Miezul: mesajul de eroare si rezolvarea NU trebuie separate."""
|
|
chunks = indexer.chunk_document("x.xml", XML)
|
|
assert len(chunks) == 2
|
|
assert "BANDA HARTIE" in chunks[0] and "VERIFICARESTOCNEGATIV" in chunks[0]
|
|
assert "Alta eroare" in chunks[1] and "Alta rezolvare" in chunks[1]
|
|
|
|
|
|
def test_etichetele_raman_ca_etichete_fara_paranteze():
|
|
chunk = indexer.chunk_document("x.xml", XML)[0]
|
|
assert "mesaj eroare:" in chunk and "rezolvare:" in chunk
|
|
assert "<mesaj_eroare>" not in chunk
|
|
|
|
|
|
def test_entitatile_sunt_decodate():
|
|
assert ">" not in indexer.chunk_document("x.xml", XML)[0]
|
|
|
|
|
|
def test_comentariile_nu_devin_chunk():
|
|
assert not any("comentariu" in c for c in indexer.chunk_document("x.xml", XML))
|
|
|
|
|
|
def test_xml_invalid_cade_pe_taierea_obisnuita():
|
|
chunks = indexer.chunk_document("stricat.xml", "<root><neinchis>")
|
|
assert chunks == ["<root><neinchis>"]
|
|
|
|
|
|
def test_xml_fara_copii_e_tratat_ca_text():
|
|
assert indexer.chunk_document("x.xml", "<root>doar text</root>") == ["doar text"]
|
|
|
|
|
|
def test_element_urias_e_taiat_mai_departe():
|
|
mare = "<r><a>" + ("propozitie lunga. " * 500) + "</a></r>"
|
|
chunks = indexer.chunk_document("x.xml", mare)
|
|
assert len(chunks) > 1
|
|
assert all(len(c) < indexer.MAX_XML_CHUNK * 2 for c in chunks)
|
|
|
|
|
|
def test_md_foloseste_taierea_pe_paragrafe():
|
|
text = "primul paragraf\n\n" + "al doilea paragraf " * 20
|
|
assert len(indexer.chunk_document("x.md", text)) >= 1
|
|
assert indexer.chunk_document("x.md", text) == indexer.chunk_text(text)
|