Files
rar-autopass/docs/prd/prd-5.14-mapare-llm-distilata.md
Claude Agent 9031f81908 feat(mapare-llm): pivot PRD 5.14 + tooling etichetare OpenRouter
PRD 5.14 rescris cu pivotul arhitectural: LLM doar etichetator OFFLINE,
runtime = clasificator local fara API (fuzzy + embeddings), baza de
cunostinte GOLD partajata cross-account (validarea unui service ajuta
toate). Decizia 8 (corpus per-cont) SUPERSEDED.

Tooling nou OpenRouter (free, familia NVIDIA Nemotron): or_common.py
(client + corpus pe frecventa, cheie din .env) + or_modeltest.py
(comparatie modele, acord ensemble vs Groq). Masurat: super-120b +
nano-9b fiabile, 3/3 unanim pe 87% volum; ultra-550b aruncat.

Corpus real (4 CSV service, coloana NR=frecventa) + etichete Groq
bootstrap incluse ca date de masurare.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 14:10:10 +00:00

225 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!-- plan sub /autoplan -->
# PRD 5.14 — Mapare automata operatii service prin distilare LLM
## Problema
La ingestie (canal API si import web), o prestatie poate veni cu `cod_op_service`
+ `denumire` in loc de `cod_prestatie` RAR. Daca nu exista mapare, submission-ul
intra in `needs_mapping` si asteapta confirmare umana. Service-urile reale au
**volume mari de denumiri particulare** (masurat: 17.435 denumiri DISTINCTE in 4
CSV-uri de clienti reali — `automotive` 13.170, `sigma` 3.743, `clever` 1.668,
`south` 875). Maparea manuala a acestora, prin editorul `needs_mapping`, e
prohibitiva: zeci de mii de operatii × confirmare umana.
Nomenclatorul RAR are doar **18 coduri** foarte largi (REPARATIE, INTRETINERE,
REVIZIE PERIODICA, etc. — `nomenclator_seed.py`). Deci problema nu e potrivire de
sinonime, ci **clasificare** a mii de operatii granulare in 18 categorii abstracte
+ detectare de „gunoi" (linii care nu sunt operatii: `ITP CT 12 ABC`, `DISCOUNT
MATERIALE 5%`, `MANOPERA`, nr. inmatriculare).
## Viziune (pivot 2026-06-28)
LLM-ul **NU ruleaza la runtime**. Rol unic: **etichetator offline** care
construieste un set de date (denumire -> cod). La runtime ruleaza un **clasificator
local mic, fara API** (similaritate / fuzzy / embeddings), „distilat" din etichetele
LLM + maparile validate de oameni. Trei straturi:
1. **Etichetare offline (LLM, periodic):** acopera denumirile cu cele mai multe
aparitii (frecventa) si grupeaza denumirile asemanatoare ca sa eticheteze ieftin.
2. **Clasificator runtime (fara AI):** exact -> fuzzy/substring -> similaritate
semantica (embeddings) peste baza de cunostinte. Zero cost per cerere, ruleaza pe
LXC.
3. **Baza de cunostinte PARTAJATA:** maparile validate de oameni din TOATE conturile
de service contribuie la clasificare (strat „gold" comun), peste etichetele LLM
(„silver" bootstrap). Munca de validare a unui service ajuta toate service-urile.
Viitor (nu acum): un LLM generativ local pe LXC. Pasul curent foloseste un model de
**embedding** (nu generativ): mic, CPU, milisecunde/text.
## Premise
1. **Volumul de denumiri distincte e finit si se schimba lent.** Odata etichetate,
90%+ din traficul viitor sunt repetari ale acelorasi denumiri (service-ul
refoloseste propriul vocabular). Lege Zipf: top 100 denumiri = 43.6% volum,
top 500 = 67.7%, top 1000 = 76.2% (din 155.195 operatii totale).
2. **RAR accepta NUMAI coduri din nomenclator.** Un cod necunoscut -> HTTP 500
(`ORA-12899`) + record PARTIAL FINALIZATA (terminal). Deci orice cod propus de
un sistem automat TREBUIE validat fata de nomenclator inainte de enqueue
(invariant existent in `resolve_prestatii(..., valid_codes)`).
3. **Maparea gresita are cost asimetric:** un cod gresit trimis = FINALIZATA
ireversibil la RAR. Deci pragul de auto-trimitere ramane conservator; incertul
ramane `needs_mapping` cu om in bucla. Etichetele LLM NEVALIDATE = sugestie, nu
auto-trimitere (vezi scara de incredere).
4. **Hardware LLM local generativ e prea lent acum** (masurat: Ollama LXC 104
generativ 180-320s/op). Embeddings locale insa sunt rapide pe CPU si suficiente
pentru similaritate la runtime.
5. **Datele nu sunt sensibile** (confirmat utilizator): denumirile de operatii pot
merge la API-uri cloud pentru etichetare. PII incidental (nr. inmatriculare/VIN)
se face scrub inainte de trimitere (F3).
## Masuratori
### Bootstrap (anterior, Groq)
- Groq `llama-3.3-70b`: 28ms/op, acord 94% cu heuristica pe cazuri clare, detectare
gunoi excelenta (`NUL`). Abandonat ca furnizor: cap zilnic free atins + cheie expusa.
### OpenRouter free — NVIDIA Nemotron (masurat 2026-06-28)
Furnizor nou pentru etichetare: cheie utilizator, modele GRATUITE, date ne-sensibile.
- **Capcane de cont (rezolvate):** modelele free dau initial `404 No allowed
providers` din cauza unui allowlist de provideri pe cont (venice/together/fireworks/
atlas-cloud) — `open-inference`/`google-ai-studio`/`nvidia` erau excluse. Fix:
eliminat restrictia in Settings -> Preferences + activat toggle-ul de privacy
„free endpoints may publish/train". WAF: User-Agent `Mozilla/5.0` obligatoriu.
- **Set fiabil = familia NVIDIA Nemotron.** Restul modelelor sunt 429 (rate-limited
upstream, partajat global: llama-3.3-70b, qwen3-next, gemma, hermes, dolphin) sau
404 (gpt-oss). Cap free tier ~50 cereri/zi fara credit.
- **Test ensemble pe top 120 dupa frecventa (46.4% din volum), 2026-06-28:**
| Model | ms/op | parse-fail | acord vs Groq (overlap) |
|---|---|---|---|
| nemotron-3-super-120b | 1463 | 0 | 100% |
| nemotron-nano-9b-v2 | 1248 | 0 | 100% |
| nemotron-3-ultra-550b | 6450 | 0 | 100% |
Acord ensemble ponderat pe volum: **3/3 unanim = 87% volum**, 2/3 = 13%, dezacord
total = **0%**. Din unanim: 7 NUL (gunoi), 100 coduri reale.
- **Decizie model:** pastram `super-120b` + `nano-9b`; **aruncam `ultra-550b`**
(4-5x mai lent, zero castig de acuratete). Caveat: ensemble din aceeasi familie
NVIDIA -> acordul supraestimeaza increderea fata de un ensemble cross-family.
- **Dezacordurile (13%) sunt cazuri de granita taxonomica reala**, nu zgomot:
`REGLAT DIRECTIE/FARURI` (OE-2 intretinere vs OE-4 reglare), `MANOPERA
TINICHIGERIE` (NUL vs OE-1), `DEZECHIPAT usa/bara` (pas de demontare), `INLOCUIT
FILTRU AER` (OE-1 vs OE-3). Astea trebuie sa cada in `needs_mapping`.
## Solutia
### Stratul 1 — Etichetare offline (LLM, fara cod runtime)
Tool CLI (`tools/mapare-llm/`, stil `tools/apikey`). Etichetatorul OpenRouter
(`or_common.py` + `or_label.py`) clasifica denumirile in cele 18 coduri RAR + `NUL`:
1. **Prioritizare pe FRECVENTA (NR), nu alfabetic.** Etichetam intai denumirile cu
cele mai multe aparitii (acopera cel mai mult volum per apel).
2. **Grupare pe similaritate inainte de etichetare.** Denumirile aproape identice
(`REGLAT DIRECTIE` / `REGLAT DIRECTIA` / `REGLARE DIRECTIE`) se grupeaza; LLM
eticheteaza doar **reprezentantul grupului**, codul se propaga la tot grupul.
Maximizeaza acoperirea per apel LLM (critic pe cap free de ~50 cereri/zi).
3. **Ensemble NVIDIA** (`super-120b` + `nano-9b`): acord -> incredere mai mare;
dezacord -> ramane pentru `needs_mapping`. Vot pe coduri, nu self-confidence.
4. **Scrub PII** (regex nr. inmatriculare/VIN) inainte de trimitere (F3, exista).
5. Output: dataset etichetat cu `denumire, cod, sursa, confidence` (provenienta).
`NUL` marcat separat (ancore negative + supresie), NU se promoveaza la cod RAR.
Prompt cu reguli explicite (avarii grave DOAR la accident; vopsire = reparatie;
ulei+filtru = revizie; gunoi -> NUL). Batch mare (cap free tier), retry/backoff pe
429, respecta `Retry-After`.
### Stratul 2 — Clasificator runtime (FARA AI, fara API)
Pentru o denumire din prezentare (canal API sau import), in `app/mapping.py`:
1. **Exact** in baza de cunostinte (`operations_mapping` + strat partajat) -> cod direct.
2. **Fuzzy/substring** (`operation_text_rules`, `rapidfuzz`) — exista deja.
3. **Similaritate semantica (embeddings)** — NOU: model multilingv mic (ex.
`intfloat/multilingual-e5-small` sau `paraphrase-multilingual-MiniLM`), CPU.
Vectorizam baza etichetata o data; la runtime vectorizam denumirea noua si luam
cel mai apropiat vecin (sau top-k cu vot). Optional: clasificator `scikit-learn`
(regresie logistica / kNN) antrenat pe (embedding -> cod) pentru generalizare
dincolo de vecinul exact. „Antrenarea pe datele de test" = acest pas, secunde,
ruleaza oriunde.
4. Cod propus -> validat OBLIGATORIU `valid_codes` (garda ORA-12899). Peste pragul
de incredere -> conform scarii; altfel `needs_mapping`.
Decizie de gazduire runtime: ramane deschisa pentru reviziile plan (in-proces in
gateway vs microserviciu pe LXC/Flowise). Default propus: in-proces (cel mai simplu).
### Stratul 3 — Baza de cunostinte PARTAJATA cross-account
**Schimbare fata de versiunea anterioara** (care izola corpusul per cont):
- **Strat GOLD partajat:** maparile **validate de oameni** (din `needs_mapping`, in
ORICE cont) intra intr-un store partajat `denumire_normalizata -> cod`. Astfel
validarea facuta de un service ridica increderea pentru toate. Cheia = denumire
normalizata (scrub PII, lower, strip), nu textul brut.
- **Strat SILVER:** etichetele LLM (bootstrap) — sugestii, NU auto-trimitere.
- **Override per-cont:** daca un cont mapeaza explicit o denumire la alt cod decat
cel partajat (conflict legitim de vocabular), override-ul contului castiga pentru
acel cont. Conflictele inter-cont se rezolva cu provenienta + (optional) majoritate.
Confirmarile umane curg organic prin folosirea normala a editorului `needs_mapping`
— FARA sesiune separata de adjudecare manuala (cerinta utilizator).
### Scara de incredere (runtime, per operatie din prezentare)
| Treapta | Sursa | Actiune | Frictiune |
|---|---|---|---|
| Certa | exact in stratul GOLD (validat de om, orice cont) sau override cont | auto-trimite | zero |
| Inalta | embedding NN cu similaritate FOARTE inalta la o mapare GOLD + ensemble LLM unanim | auto-trimite (prag calibrat) | zero |
| Medie | LLM silver / similaritate medie | `needs_mapping` cu sugestie pre-completata -> 1 click | minima |
| Joasa | similaritate slaba / coduri apropiate | `needs_mapping` manual | normala |
| NUL | non-operatie (ITP, discount, nr. inmatriculare) | marcat „nu e operatie", suprimat | — |
**Invariant F1 (pastrat):** o eticheta pur-LLM NEVALIDATA nu auto-trimite singura;
auto-send cere ori GOLD (validat de om), ori treapta „inalta" calibrata. Tensiunea
centrala (utilizatorul se bazeaza pe LLM, dar FINALIZATA e ireversibil) = intrebarea
cheie pentru reviziile plan: unde fix se aseaza bara treptei „inalta".
## Integrare
- Stratul 1: tool CLI offline `tools/mapare-llm/` (exista: `or_common.py`,
`or_modeltest.py`; de adaugat `or_label.py` cu grupare + propagare).
- Stratul 2: `suggest_from_corpus` + similaritate embeddings in `app/mapping.py`,
apelata in `pending_unmapped` pentru sugestia din editor. Model embedding incarcat
la pornire / serviciu, vectori pre-calculati pe baza.
- Stratul 3: store partajat (tabela noua `shared_mappings` sau coloana de scope pe
`operations_mapping`), seed la confirmare umana; override per-cont.
- Validare `valid_codes` pe tot lantul (exista).
## Non-obiective
- Nu inlocuim confirmarea umana pentru cazuri incerte.
- Nu trimitem automat coduri sub prag / etichete LLM nevalidate.
- Nu adaugam dependenta cloud la RUNTIME (LLM doar offline pentru etichetare).
- Nu antrenam un LLM generativ local acum (viitor).
## Riscuri
- Etichete LLM gresite tratate ca adevar daca scapa garda F1 (seed direct in GOLD).
- Ensemble aceeasi familie (NVIDIA) -> acord corelat-gresit; supraestimare incredere.
- Strat partajat cross-account: o denumire poate insemna lucruri diferite la
service-uri diferite -> conflict; mitigat prin override per-cont + provenienta.
- Drift: denumiri noi neacoperite; embeddings ajuta dar nu elimina.
- Free tier OpenRouter flaky (429/404, cap 50/zi) -> etichetarea bulk e lenta;
e offline, deci tolerabil, dar nu pe calea critica de productie.
- Model embedding ales: calitate pe limba romana de verificat empiric.
<!-- AUTONOMOUS DECISION LOG -->
## Decision Audit Trail
| # | Faza | Decizie | Clasificare | Principiu | Rationament | Respins |
|---|------|---------|-------------|-----------|-------------|---------|
| 1 | Eng | Seed-ul NU intra direct in stratul auto-send; etichetele LLM = strat SILVER (sugestii). Auto-send cere GOLD (validat de om) sau treapta inalta calibrata | TASTE (critic) | P1, P5 | `resolve_prestatii`->`queued` direct => seed auto = AUTO-TRIMITERE ghiciri la FINALIZATA ireversibil (Premisa 3) | seed direct in auto-send |
| 2 | Eng | Seeder = `INSERT OR IGNORE` / refuza overwrite pe randuri validate de om | MECHANICAL | P1 | re-rularea ar clobber-ui maparile umane cu ghiciri LLM | ON CONFLICT UPDATE |
| 3 | Eng | Scrub regex (nr. inmatriculare/VIN) inainte de trimitere la LLM | TASTE | P1 | gunoiul contine `ITP CT 12 ABC` = nr. inmatriculare = PII | trimitere text brut |
| 4 | Eng | NUL = ancore negative in corpus + lista supresie | MECHANICAL | P1 | altfel gunoiul recurent reintra mereu in needs_mapping si fuzzy ii da cod gresit | exclude NUL |
| 5 | Eng | Coloana `source`/`confidence` (provenienta) pe baza de cunostinte | MECHANICAL | P1 | audit + rollback batch model prost + safe re-seed | fara provenienta |
| 6 | Eng | Runtime = embeddings + clasificator mic (sklearn), NU LLM generativ | TASTE | P3, P5 | LLM generativ local prea lent (Premisa 4); embeddings CPU suficiente + rapide | LLM la runtime |
| 8 | Eng | **SUPERSEDED:** corpus partajat cross-account (strat GOLD comun), NU per-cont izolat; override per-cont pe conflict | TASTE | P1, P2 | cerinta utilizator: validarea unui service ajuta toate; muncă compusa. Conflictul de vocabular rezolvat prin override + provenienta | (vechi: corpus strict per-cont) |
| 9 | Eng | Furnizor etichetare = OpenRouter free, ensemble NVIDIA (super-120b + nano-9b); aruncat ultra-550b | MECHANICAL | P3 | masurat 2026-06-28: doar NVIDIA routeaza fiabil; ultra 4-5x lent fara castig | Groq (cap atins) / ultra |
| 10 | Eng | Etichetare prioritizata pe frecventa + grupare pe similaritate (eticheteaza reprezentant, propaga) | MECHANICAL | P2 | acopera mult mai mult volum per apel; critic pe cap free ~50/zi | etichetare alfabetica |
## Istoric review (pre-pivot)
Versiunea anterioara a trecut prin `/autoplan` (mod SELECTIVE EXPANSION, subagent-only,
Codex indisponibil). Constatari portante atunci: **F1 CRITIC** (seed=auto-send),
F2/F3/F4 HIGH (idempotenta seed, scrub PII, ancore NUL), F5/F6/F7/F8 MEDIUM. Acele
decizii sunt incorporate in Decision Audit Trail de mai sus. Pivotul 2026-06-28
(LLM offline-only + runtime embeddings + strat partajat cross-account) NECESITA o
noua rulare de review (CEO / Eng / Design) — de aceea sectiunea GSTACK REVIEW REPORT
e goala momentan si se completeaza la urmatoarea rulare.
## GSTACK REVIEW REPORT
(de completat la rularea reviziilor pe versiunea pivotata — plan-ceo / plan-eng / plan-design)