Files
rar-autopass/tools/mapare-llm/f7_ensemble.py
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

87 lines
4.5 KiB
Python

import json, urllib.request, urllib.error, time, os, csv, glob, random, re
from collections import Counter
KEY=os.environ["GROQ_KEY"]; N=150; BATCH=30
MODELS=["llama-3.3-70b-versatile","openai/gpt-oss-120b","qwen/qwen3-32b"]
OUT="/tmp/claude-1000/-workspace-autopass/4177677c-7995-4fab-bbd5-16735cb335e3/scratchpad/f7_result.json"
random.seed(123)
CODURI=("OE-1=REPARATIE, OE-2=INTRETINERE, OE-3=REVIZIE PERIODICA, OE-4=REGLARE FUNCTIONALA, "
"OE-5=MODIFICARE CONSTRUCTIVA, OE-6=RECONSTRUCTIE, OE-7=ACTUALIZARE SOFTWARE, "
"OE-8=INLOCUIRE SEZONIERA ANVELOPE, OE-D=AVARIE GRAVA DIRECTIE, OE-F=AVARIE GRAVA FRANARE, "
"OE-C=AVARIE GRAVA CAROSERIE, OE-S=AVARIE GRAVA SASIU, OE-R=AVARIE GRAVA RETINERE/AIRBAG, "
"OE-A=AVARIE GRAVA ADAS, OE-I=ISTORIC ODOMETRU, AITLV=ATELIER TAHOGRAFE, "
"R-ODO=REPARATIE ODOMETRU, I-ODO=INLOCUIRE ODOMETRU, NUL=NU e operatie de service")
SYS=("Esti expert RAR AUTOPASS. Clasifici fiecare operatie de service-auto in EXACT unul din coduri:\n"+CODURI+
"\nReguli: AVARIILE GRAVE (OE-D/F/C/S/R/A) DOAR pentru daune in urma unui accident, NU reparatii curente. "
"Vopsire/revopsire/retus = REPARATIE (OE-1). Inlocuire/D-R/reparare piese = REPARATIE (OE-1). "
"Schimb ulei motor + filtre = REVIZIE (OE-3). Aerisit/gresat/completat nivele = INTRETINERE (OE-2). "
"Text care nu e operatie efectiva (ITP, plata, discount, manopera generica, nr inmatriculare, doar nume piesa) -> NUL. "
"Raspunde DOAR JSON {\"rez\":[{\"i\":<numar>,\"cod\":\"...\"}]}.")
# F3: scrub PII (nr inmatriculare) inainte de trimitere
PLATE=re.compile(r'\b[A-Z]{1,2}\s?\d{2,3}\s?[A-Z]{3}\b')
VIN=re.compile(r'\b[A-HJ-NPR-Z0-9]{17}\b')
def scrub(s): return VIN.sub('[VIN]',PLATE.sub('[NR]',s))
def call(model,batch):
msgs=[{"role":"system","content":SYS},
{"role":"user","content":"\n".join(f"{i+1}. {scrub(o)}" for i,o in enumerate(batch))}]
body={"model":model,"messages":msgs,"temperature":0,"response_format":{"type":"json_object"}}
data=json.dumps(body).encode()
for attempt in range(8):
req=urllib.request.Request("https://api.groq.com/openai/v1/chat/completions",
data=data,headers={"Authorization":f"Bearer {KEY}","Content-Type":"application/json","User-Agent":"Mozilla/5.0"})
try:
with urllib.request.urlopen(req,timeout=180) as r: d=json.load(r)
out=json.loads(d["choices"][0]["message"]["content"])["rez"]
m={x["i"]:x["cod"] for x in out}
return [m.get(i+1,"?") for i in range(len(batch))]
except urllib.error.HTTPError as e:
if e.code in (429,500,502,503):
wait=float(e.headers.get("retry-after",0)) or min(2**attempt,30); time.sleep(wait); continue
raise
except Exception:
time.sleep(min(2**attempt,20)); continue
return ["?"]*len(batch)
# corpus + esantion random
ops=set()
for f in sorted(glob.glob("/workspace/autopass/docs/operatii-service/*.csv")):
for r in list(csv.reader(open(f,encoding="utf-8",errors="replace"),delimiter=";"))[1:]:
if len(r)>1 and r[1].strip(): ops.add(r[1].strip())
sample=random.sample(sorted(ops),N)
print(f"esantion random {N} din {len(ops)} distincte",flush=True)
votes={m:[] for m in MODELS}
t0=time.time()
for m in MODELS:
res=[]
nb=(N+BATCH-1)//BATCH
for bi,k in enumerate(range(0,N,BATCH)):
res+=call(m,sample[k:k+BATCH])
print(f" {m} batch {bi+1}/{nb} ({time.time()-t0:.0f}s)",flush=True)
time.sleep(6) # pacing sub TPM 12000
votes[m]=res
print(f" {m}: GATA ({time.time()-t0:.0f}s)",flush=True)
rows=[]
for i,op in enumerate(sample):
vs=[votes[m][i] for m in MODELS]
c=Counter(vs); top,cnt=c.most_common(1)[0]
level=3 if cnt==3 else (2 if cnt==2 else 1)
rows.append({"op":op,"votes":vs,"cod":top,"agree":level})
json.dump(rows,open(OUT,"w"),ensure_ascii=False,indent=1)
a3=[r for r in rows if r["agree"]==3]; a2=[r for r in rows if r["agree"]==2]; a1=[r for r in rows if r["agree"]==1]
print(f"\n=== F7 ENSEMBLE ({N} ops, {time.time()-t0:.0f}s) ===")
print(f"ACORD 3/3 (candidat auto-send): {len(a3)} ({100*len(a3)//N}%)")
print(f"ACORD 2/3: {len(a2)} ({100*len(a2)//N}%)")
print(f"DEZACORD total (1+1+1): {len(a1)} ({100*len(a1)//N}%)")
n3=sum(1 for r in a3 if r['cod']=='NUL')
print(f" din 3/3: {n3} sunt NUL (gunoi), {len(a3)-n3} coduri reale")
print(f"\nrezultat complet salvat: {OUT}")
print("\n--- ACORD 2/3 si DEZACORD (astea ar merge la needs_mapping) ---")
for r in a2+a1:
print(f" {r['op']:<42} {r['cod']:<6} voturi={r['votes']}")