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

14 KiB
Raw Blame History

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.

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)