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>
This commit is contained in:
224
docs/prd/prd-5.14-mapare-llm-distilata.md
Normal file
224
docs/prd/prd-5.14-mapare-llm-distilata.md
Normal file
@@ -0,0 +1,224 @@
|
||||
<!-- 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)
|
||||
Reference in New Issue
Block a user