Seed-ul Haiku eticheta sistematic "INLOCUIT ANVELOPE ..." cu OE-1 (223 vs 17
OE-8), desi RAR are cod dedicat OE-8 (inlocuire sezoniera anvelope), asa ca
votul k-NN propunea OE-1 pentru orice inlocuire de anvelope.
- tools/mapare-llm/fix_anvelope_oe8.py: re-etichetare deterministica (187
randuri DB + seed JSON -> OE-8; regula exclude singular cu pozitie, D/R,
janta, vulcanizare) + 13 intrari canonice curate manual pentru formularile
sezoniere absente din corpus ("MONTAT CAUCIUCURI VARA" etc.); idempotent
- enrich_suggestions: ponderea votului = EMB_VOTE_DECAY^rang (0.7) in loc de
similaritatea bruta — anizotropia modelului lasa zgomotul de coada (ex.
INLOCUIT BECURII la 0.934 de INLOCUIRE ANVELOPE) sa invinga vecinii corecti
de rang 1-2; LOO: precizie egala (93.1%), cod-gresit 4.67% -> 4.63%
- test_fix_anvelope_oe8: gardii pe regula + seed-ul din repo (regresie la
regenerare); auditul k-NN al dezacordurilor nu a gasit alte erori
sistematice (curatat=OE-2, reglat=OE-4, bujii/ulei=OE-3 raman apararabile)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
166 lines
5.7 KiB
Python
166 lines
5.7 KiB
Python
"""Corectie etichete SILVER: inlocuirea de anvelope -> OE-8.
|
|
|
|
Seed-ul Haiku a etichetat sistematic "INLOCUIT ANVELOPE ..." cu OE-1 (REPARATIE),
|
|
desi nomenclatorul RAR are un cod dedicat: OE-8 (INLOCUIRE SEZONIERA A ANVELOPELOR).
|
|
Eroarea domina vecinatatea k-NN (223 OE-1 vs 17 OE-8), asa ca votul sugera OE-1
|
|
pentru orice "Inlocuire anvelope".
|
|
|
|
Regula (deterministica, aceeasi in DB si in seed):
|
|
text NORMALIZAT care contine un verb de inlocuire/montare
|
|
(INLOCUIT/INLOCUIRE/INL/SCHIMB*/MONTAT/MONTARE) SI anvelope la plural
|
|
(ANVELOPE/CAUCIUCURI), FARA context de reparatie (JANTA, D/R, D/M, REPARAT,
|
|
VULCANIZ, PRESIUNE, DEPRESAT) -> OE-8.
|
|
Singularul cu pozitie ("INLOCUIT ANVELOPA DR FATA") ramane OE-1: e tipic
|
|
inlocuire dupa pana/uzura in cadrul unei reparatii, nu schimb sezonier.
|
|
|
|
In plus, adauga intrari canonice OE-8 curate manual (`CURATE_OE8`) pentru
|
|
formularile uzuale de schimb sezonier absente din corpus ("MONTAT CAUCIUCURI
|
|
VARA" etc.) — fara ele, k-NN nu are niciun vecin relevant si votul cade pe
|
|
zgomot de coada.
|
|
|
|
Aplica idempotent pe:
|
|
- tabela `mapping_suggestions` (sursa corpusului k-NN la runtime)
|
|
- `app/data/operatii-etichetate.json` (seed-ul pentru instalari noi)
|
|
`source` devine "haiku_seed+fix_oe8" pe randurile corectate, "curat_manual"
|
|
pe cele adaugate (audit).
|
|
|
|
Rulare:
|
|
python3 tools/mapare-llm/fix_anvelope_oe8.py --db data/autopass.db [--dry-run]
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import os
|
|
import re
|
|
import sqlite3
|
|
|
|
_HERE = os.path.dirname(os.path.abspath(__file__))
|
|
_ROOT = os.path.abspath(os.path.join(_HERE, "..", ".."))
|
|
SEED_PATH = os.path.join(_ROOT, "app", "data", "operatii-etichetate.json")
|
|
|
|
VERB = re.compile(r"\b(INLOCUIT|INLOCUIRE|INL|SCHIMB|SCHIMBAT|SCHIMBARE|MONTAT|MONTARE)\b")
|
|
PLURAL = re.compile(r"ANVELOPE|CAUCIUCURI")
|
|
EXCLUDERE = re.compile(r"JANTA|D/R|D/M|REPARAT|VULCANIZ|PRESIUNE|DEPRESAT")
|
|
|
|
# Formulari canonice de schimb sezonier, absente din corpusul Haiku.
|
|
# Texte deja in forma normalizata (majuscule ASCII, fara diacritice).
|
|
CURATE_OE8 = [
|
|
"INLOCUIRE ANVELOPE",
|
|
"SCHIMBARE ANVELOPE",
|
|
"SCHIMB ANVELOPE",
|
|
"SCHIMBAT ANVELOPE IARNA",
|
|
"SCHIMBAT ANVELOPE VARA",
|
|
"MONTAT ANVELOPE IARNA",
|
|
"MONTAT ANVELOPE VARA",
|
|
"MONTAT CAUCIUCURI IARNA",
|
|
"MONTAT CAUCIUCURI VARA",
|
|
"SCHIMB CAUCIUCURI",
|
|
"SCHIMBAT ROTI IARNA",
|
|
"SCHIMBAT ROTI VARA",
|
|
"INLOCUIRE SEZONIERA ANVELOPE",
|
|
]
|
|
|
|
|
|
def este_inlocuire_anvelope(text_normalizat: str) -> bool:
|
|
"""True daca textul descrie o inlocuire/montare de anvelope (plural)."""
|
|
t = text_normalizat or ""
|
|
return bool(VERB.search(t) and PLURAL.search(t) and not EXCLUDERE.search(t))
|
|
|
|
|
|
def fix_db(conn: sqlite3.Connection, dry_run: bool) -> int:
|
|
rows = conn.execute(
|
|
"SELECT id, denumire_normalizata FROM mapping_suggestions "
|
|
"WHERE is_nul=0 AND cod_prestatie != 'OE-8' "
|
|
"AND (denumire_normalizata LIKE '%ANVELOP%' OR denumire_normalizata LIKE '%CAUCIUC%')"
|
|
).fetchall()
|
|
ids = [r[0] for r in rows if este_inlocuire_anvelope(r[1])]
|
|
if not dry_run and ids:
|
|
conn.executemany(
|
|
"UPDATE mapping_suggestions SET cod_prestatie='OE-8', "
|
|
"source='haiku_seed+fix_oe8', updated_at=datetime('now') WHERE id=?",
|
|
[(i,) for i in ids],
|
|
)
|
|
conn.commit()
|
|
return len(ids)
|
|
|
|
|
|
def adauga_curate_db(conn: sqlite3.Connection, dry_run: bool) -> int:
|
|
n = 0
|
|
for t in CURATE_OE8:
|
|
exists = conn.execute(
|
|
"SELECT 1 FROM mapping_suggestions WHERE denumire_normalizata=?", (t,)
|
|
).fetchone()
|
|
if exists:
|
|
continue
|
|
n += 1
|
|
if not dry_run:
|
|
conn.execute(
|
|
"INSERT INTO mapping_suggestions "
|
|
"(denumire_normalizata, cod_prestatie, is_nul, source, confidence) "
|
|
"VALUES (?, 'OE-8', 0, 'curat_manual', 1.0)",
|
|
(t,),
|
|
)
|
|
if not dry_run:
|
|
conn.commit()
|
|
return n
|
|
|
|
|
|
def adauga_curate_seed(items: list[dict], dry_run: bool) -> int:
|
|
existente = {it.get("denumire_normalizata") for it in items}
|
|
n = 0
|
|
for t in CURATE_OE8:
|
|
if t in existente:
|
|
continue
|
|
n += 1
|
|
if not dry_run:
|
|
items.append({
|
|
"denumire": t,
|
|
"denumire_normalizata": t,
|
|
"cod": "OE-8",
|
|
"is_nul": False,
|
|
"source": "curat_manual",
|
|
"confidence": 1.0,
|
|
})
|
|
return n
|
|
|
|
|
|
def fix_seed(dry_run: bool) -> tuple[int, int]:
|
|
with open(SEED_PATH, encoding="utf-8") as f:
|
|
items = json.load(f)
|
|
n = 0
|
|
for it in items:
|
|
if it.get("is_nul") or it.get("cod") == "OE-8":
|
|
continue
|
|
if este_inlocuire_anvelope(it.get("denumire_normalizata") or ""):
|
|
it["cod"] = "OE-8"
|
|
it["source"] = "haiku_seed+fix_oe8"
|
|
n += 1
|
|
n_curate = adauga_curate_seed(items, dry_run)
|
|
if not dry_run and (n or n_curate):
|
|
with open(SEED_PATH, "w", encoding="utf-8") as f:
|
|
# acelasi format ca genereaza_seed.py (diff git minimal la corectii)
|
|
json.dump(items, f, ensure_ascii=False, indent=2)
|
|
return n, n_curate
|
|
|
|
|
|
def main() -> None:
|
|
ap = argparse.ArgumentParser(description=__doc__)
|
|
ap.add_argument("--db", default=os.path.join(_ROOT, "data", "autopass.db"))
|
|
ap.add_argument("--dry-run", action="store_true")
|
|
args = ap.parse_args()
|
|
|
|
conn = sqlite3.connect(args.db)
|
|
n_db = fix_db(conn, args.dry_run)
|
|
n_db_curate = adauga_curate_db(conn, args.dry_run)
|
|
n_seed, n_seed_curate = fix_seed(args.dry_run)
|
|
eticheta = "(dry-run) " if args.dry_run else ""
|
|
print(
|
|
f"{eticheta}DB: {n_db} relabel + {n_db_curate} curate; "
|
|
f"seed JSON: {n_seed} relabel + {n_seed_curate} curate"
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|