feat(maria): depozit local, in afara oglinzii Drive

Dictionarul de erori Oracle pus in `documents/` ar fi disparut in 10 minute:
`rclone sync` sterge de acolo tot ce nu exista in Drive. Aceeasi problema o avea
si butonul de adaugare document din dashboard — documentul traia pana la
urmatorul tur de sincronizare, ceea ce README-ul mentiona ca pe o ciudatenie, nu
ca pe un bug.

Acum sunt doua directoare cu un singur spatiu de nume: `documents/` (oglinda
Drive) si `documents-local/` (ce nu vine din Drive). La acelasi nume castiga
Drive-ul — e sursa comuna a echipei, iar copia locala poate fi o versiune veche
uitata acolo. Dashboard-ul scrie in cel local si marcheaza documentele „local".

Recalibrat cu dictionarul indexat (169 chunk-uri): 19/19, cele patru intrebari
Oracle noi ies la 0,73-0,75. Reindexarea a durat cat cele 29 de chunk-uri noi,
nu cat toate 169 — refolosirea vectorilor isi face treaba (log: "29 embeddings
noi, 140 refolosite").

Verificat pe canalul viu: o escaladare cu captura chiar pleaca pe WhatsApp,
imagine + rezumat + referinta, si apare in dashboard.

73 pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4uzvgm7AyJch5WH8QHRhY
This commit is contained in:
Claude Agent
2026-08-31 22:13:09 +00:00
parent ca6b53e0ca
commit c514818ebe
8 changed files with 173 additions and 39 deletions

View File

@@ -17,6 +17,10 @@ _DEFAULT_DIR = pathlib.Path.home() / ".maria-bridge"
STATE_DIR: pathlib.Path = pathlib.Path(os.environ.get("MARIA_BRIDGE_DIR") or _DEFAULT_DIR)
DOCS_DIR: pathlib.Path = STATE_DIR / "documents"
# Documente care NU vin din Drive. `rclone sync` sterge din DOCS_DIR tot ce nu
# exista in Drive, deci orice fisier pus acolo de mana dispare la urmatorul tur.
# Ce sta aici e in afara oglinzii si supravietuieste.
DOCS_LOCAL_DIR: pathlib.Path = STATE_DIR / "documents-local"
INDEX_FILE: pathlib.Path = STATE_DIR / "rag_index.json"
LOCK_FILE: pathlib.Path = STATE_DIR / ".rag.lock"
LOG_DIR: pathlib.Path = STATE_DIR / "logs"
@@ -87,11 +91,12 @@ def parse_env(text: str) -> dict[str, str]:
def reload(base_dir: str | os.PathLike | None = None) -> dict[str, str]:
"""Recalculeaza caile si reciteste env-ul. Returneaza dictionarul incarcat."""
global STATE_DIR, DOCS_DIR, INDEX_FILE, LOCK_FILE, LOG_DIR, ENV_FILE, AUTH_DIR, _env
global STATE_DIR, DOCS_DIR, DOCS_LOCAL_DIR, INDEX_FILE, LOCK_FILE, LOG_DIR, ENV_FILE, AUTH_DIR, _env
if base_dir is None:
base_dir = os.environ.get("MARIA_BRIDGE_DIR") or _DEFAULT_DIR
STATE_DIR = pathlib.Path(base_dir)
DOCS_DIR = STATE_DIR / "documents"
DOCS_LOCAL_DIR = STATE_DIR / "documents-local"
INDEX_FILE = STATE_DIR / "rag_index.json"
LOCK_FILE = STATE_DIR / ".rag.lock"
LOG_DIR = STATE_DIR / "logs"

View File

@@ -4,6 +4,14 @@ Fisiere text (.txt/.md/.xml) sub STATE_DIR/documents/, un singur nivel (fara
subdirectoare), ca numele afisat in dashboard sa fie neambiguu si sa poata servi
direct ca parametru de request fara riscuri de traversare de cale.
DOUA DIRECTOARE, un singur spatiu de nume:
documents/ oglinda Google Drive. `rclone sync` STERGE de aici tot ce nu
exista in Drive — nu pune nimic de mana.
documents-local/ documente care nu vin din Drive (dictionare proprii, ce se
adauga din dashboard). Sincronizarea nu se atinge de ele.
Cand acelasi nume apare in ambele, castiga cel din Drive: e sursa comuna a
echipei, iar copia locala ar putea fi o versiune veche uitata acolo.
Cand acelasi document exista in mai multe formate (`d406_saft_knowledge.xml` si
`d406_saft_knowledge.md`), la indexare intra UNUL SINGUR, cel cu formatul cel mai
bogat: .xml > .md > .txt. Sursele .xml sunt structurate pe probleme
@@ -38,13 +46,32 @@ def validate_name(name: str) -> str:
return name
def _all_files() -> list[pathlib.Path]:
"""Fisierele din ambele directoare. La acelasi nume, cel din Drive castiga."""
vazute: dict[str, pathlib.Path] = {}
for d in (config.DOCS_LOCAL_DIR, config.DOCS_DIR): # Drive ultimul = suprascrie
if not d.is_dir():
continue
for f in sorted(d.glob("*")):
if f.is_file() and f.suffix in DOC_EXTENSIONS:
vazute[f.name] = f
return [vazute[n] for n in sorted(vazute)]
def path_for(name: str) -> pathlib.Path | None:
"""Unde e documentul, in oricare din cele doua directoare."""
name = validate_name(name)
for d in (config.DOCS_DIR, config.DOCS_LOCAL_DIR):
p = d / name
if p.is_file():
return p
return None
def list_documents() -> list[dict]:
"""Tot ce e in depozit. `shadowed_by` spune de ce un fisier nu ajunge in index."""
config.DOCS_DIR.mkdir(parents=True, exist_ok=True)
files = [
f for f in sorted(config.DOCS_DIR.glob("*"))
if f.is_file() and f.suffix in DOC_EXTENSIONS
]
files = _all_files()
# castigatorul per nume de baza: formatul cel mai bogat
winners: dict[str, str] = {}
for f in files:
@@ -60,6 +87,7 @@ def list_documents() -> list[dict]:
"name": f.name,
"size": st.st_size,
"mtime": st.st_mtime,
"local": f.parent == config.DOCS_LOCAL_DIR,
"shadowed_by": None if winner == f.name else winner,
})
return out
@@ -71,20 +99,27 @@ def documents_for_index() -> list[dict]:
def read_document(name: str) -> str:
name = validate_name(name)
return (config.DOCS_DIR / name).read_text(encoding="utf-8")
path = path_for(name)
if path is None:
raise FileNotFoundError(name)
return path.read_text(encoding="utf-8")
def write_document(name: str, content: str) -> None:
"""Scrie in documents-local/, NU in oglinda Drive.
Un document scris in `documents/` dispare la urmatorul `rclone sync` — asta
se intampla pana acum cu tot ce se adauga din dashboard.
"""
name = validate_name(name)
config.DOCS_DIR.mkdir(parents=True, exist_ok=True)
(config.DOCS_DIR / name).write_text(content, encoding="utf-8")
config.DOCS_LOCAL_DIR.mkdir(parents=True, exist_ok=True)
(config.DOCS_LOCAL_DIR / name).write_text(content, encoding="utf-8")
def delete_document(name: str) -> bool:
name = validate_name(name)
path = config.DOCS_DIR / name
if not path.exists():
"""Sterge documentul de unde se afla. Cel din Drive revine la sincronizare."""
path = path_for(name)
if path is None:
return False
path.unlink()
return True

View File

@@ -32,10 +32,11 @@ def _fingerprint() -> str:
doar cand s-a schimbat efectiv ceva, nu la fiecare tur de sincronizare."""
config.DOCS_DIR.mkdir(parents=True, exist_ok=True)
h = hashlib.sha256()
for f in sorted(config.DOCS_DIR.glob("*")):
if f.is_file() and f.suffix in store.DOC_EXTENSIONS:
st = f.stat()
h.update(f"{f.name}:{st.st_mtime_ns}:{st.st_size}\n".encode())
# amandoua directoarele: si un document local adaugat din dashboard trebuie sa
# declanseze reindexarea, nu doar ce vine din Drive
for f in store._all_files():
st = f.stat()
h.update(f"{f.name}:{st.st_mtime_ns}:{st.st_size}\n".encode())
return h.hexdigest()