Spaces:
Sleeping
Sleeping
ajout libellé COICOP sortie
Browse files- quick_deploy_agent.py +54 -14
quick_deploy_agent.py
CHANGED
|
@@ -17,6 +17,7 @@ FALLBACK_MODELS = [
|
|
| 17 |
]
|
| 18 |
|
| 19 |
|
|
|
|
| 20 |
# ---- Mini référentiel COICOP (démo) ----
|
| 21 |
COICOP_ITEMS = [
|
| 22 |
{"code": "01.1.4.5.1", "label": "Laits caillés, fromage blanc, petites crèmes fromagères"},
|
|
@@ -29,6 +30,11 @@ COICOP_ITEMS = [
|
|
| 29 |
{"code": "01.1.1.3", "label": "Pâtes, couscous et produits similaires"},
|
| 30 |
]
|
| 31 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
def normalize_txt(s: str) -> str:
|
| 33 |
if not s: return ""
|
| 34 |
s = s.upper()
|
|
@@ -564,11 +570,14 @@ class Resolve(Tool):
|
|
| 564 |
output_type = "object"
|
| 565 |
|
| 566 |
def _fallback_min3(self):
|
| 567 |
-
# ordre neutre et scores modestes
|
| 568 |
base = [
|
| 569 |
-
{"code":"01.1.4.5.2","
|
| 570 |
-
|
| 571 |
-
{"code":"01.1.4.5.
|
|
|
|
|
|
|
|
|
|
| 572 |
]
|
| 573 |
return base
|
| 574 |
|
|
@@ -586,21 +595,36 @@ class Resolve(Tool):
|
|
| 586 |
if isinstance(s, str):
|
| 587 |
try: data = json.loads(s)
|
| 588 |
except Exception: data = {}
|
| 589 |
-
if not isinstance(data, dict):
|
|
|
|
| 590 |
for c in data.get("candidates", []):
|
| 591 |
-
code = c.get("code")
|
| 592 |
-
if not code:
|
|
|
|
| 593 |
score = float(c.get("score", c.get("score_final", 0.0)))
|
| 594 |
why = c.get("why", "") or c.get("label", "")
|
|
|
|
|
|
|
|
|
|
| 595 |
if code not in bucket:
|
| 596 |
-
bucket[code] = {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 597 |
else:
|
| 598 |
bucket[code]["score"] = max(bucket[code]["score"], score)
|
| 599 |
bucket[code]["votes"] += 1
|
| 600 |
-
if why:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 601 |
|
| 602 |
if not bucket:
|
| 603 |
-
# 🔁 Fallback global si VRAIMENT rien n'a pu être agrégé
|
| 604 |
ranked = self._fallback_min3()
|
| 605 |
final = ranked[0]
|
| 606 |
alts = ranked[1:]
|
|
@@ -615,23 +639,39 @@ class Resolve(Tool):
|
|
| 615 |
# Top fusionné : au moins 3
|
| 616 |
min_top = max(3, topn if isinstance(topn, int) and topn>0 else 3)
|
| 617 |
if len(ranked) < min_top:
|
| 618 |
-
# compléter avec un petit fallback sans dupliquer
|
| 619 |
already = {r["code"] for r in ranked}
|
| 620 |
for fb in self._fallback_min3():
|
| 621 |
-
if len(ranked) >= min_top:
|
| 622 |
-
|
|
|
|
|
|
|
| 623 |
ranked.append(fb)
|
| 624 |
|
|
|
|
| 625 |
final = ranked[0]
|
| 626 |
alts = ranked[1:1+min_top-1]
|
| 627 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 628 |
ev = final.get("evidences", [])
|
| 629 |
exp = (
|
| 630 |
f"Choix {final['code']} (score {final['score_final']:.2f}) – votes={final.get('votes',0)} – raisons: {', '.join(sorted(set(ev)))}"
|
| 631 |
if ev else
|
| 632 |
f"Choix {final['code']} (score {final['score_final']:.2f}) – fallback partiel."
|
| 633 |
)
|
| 634 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 635 |
|
| 636 |
|
| 637 |
|
|
|
|
| 17 |
]
|
| 18 |
|
| 19 |
|
| 20 |
+
# ---- Mini référentiel COICOP (démo) ----
|
| 21 |
# ---- Mini référentiel COICOP (démo) ----
|
| 22 |
COICOP_ITEMS = [
|
| 23 |
{"code": "01.1.4.5.1", "label": "Laits caillés, fromage blanc, petites crèmes fromagères"},
|
|
|
|
| 30 |
{"code": "01.1.1.3", "label": "Pâtes, couscous et produits similaires"},
|
| 31 |
]
|
| 32 |
|
| 33 |
+
# ✅ Map code -> libellé (avec un libellé pour le code générique)
|
| 34 |
+
CODE_TO_LABEL = {it["code"]: it["label"] for it in COICOP_ITEMS}
|
| 35 |
+
CODE_TO_LABEL.setdefault("01.1.4.5", "Fromages (générique)")
|
| 36 |
+
|
| 37 |
+
|
| 38 |
def normalize_txt(s: str) -> str:
|
| 39 |
if not s: return ""
|
| 40 |
s = s.upper()
|
|
|
|
| 570 |
output_type = "object"
|
| 571 |
|
| 572 |
def _fallback_min3(self):
|
| 573 |
+
# ordre neutre et scores modestes (avec libellés)
|
| 574 |
base = [
|
| 575 |
+
{"code":"01.1.4.5.2","label": CODE_TO_LABEL.get("01.1.4.5.2",""),
|
| 576 |
+
"score_final":0.50,"votes":0,"evidences":["fallback (aucune évidence)"]},
|
| 577 |
+
{"code":"01.1.4.5.3","label": CODE_TO_LABEL.get("01.1.4.5.3",""),
|
| 578 |
+
"score_final":0.49,"votes":0,"evidences":["fallback (aucune évidence)"]},
|
| 579 |
+
{"code":"01.1.4.5.5","label": CODE_TO_LABEL.get("01.1.4.5.5",""),
|
| 580 |
+
"score_final":0.48,"votes":0,"evidences":["fallback (aucune évidence)"]},
|
| 581 |
]
|
| 582 |
return base
|
| 583 |
|
|
|
|
| 595 |
if isinstance(s, str):
|
| 596 |
try: data = json.loads(s)
|
| 597 |
except Exception: data = {}
|
| 598 |
+
if not isinstance(data, dict):
|
| 599 |
+
continue
|
| 600 |
for c in data.get("candidates", []):
|
| 601 |
+
code = c.get("code")
|
| 602 |
+
if not code:
|
| 603 |
+
continue
|
| 604 |
score = float(c.get("score", c.get("score_final", 0.0)))
|
| 605 |
why = c.get("why", "") or c.get("label", "")
|
| 606 |
+
# ✅ libellé via le mapping (fallback sur un éventuel label déjà présent)
|
| 607 |
+
label = CODE_TO_LABEL.get(code, c.get("label", ""))
|
| 608 |
+
|
| 609 |
if code not in bucket:
|
| 610 |
+
bucket[code] = {
|
| 611 |
+
"code": code,
|
| 612 |
+
"label": label, # <-- ajouté
|
| 613 |
+
"score": score,
|
| 614 |
+
"votes": 1,
|
| 615 |
+
"evidences": [why] if why else []
|
| 616 |
+
}
|
| 617 |
else:
|
| 618 |
bucket[code]["score"] = max(bucket[code]["score"], score)
|
| 619 |
bucket[code]["votes"] += 1
|
| 620 |
+
if why:
|
| 621 |
+
bucket[code]["evidences"].append(why)
|
| 622 |
+
# garde un label si absent
|
| 623 |
+
if not bucket[code].get("label"):
|
| 624 |
+
bucket[code]["label"] = label
|
| 625 |
|
| 626 |
if not bucket:
|
| 627 |
+
# 🔁 Fallback global si VRAIMENT rien n'a pu être agrégé (avec labels)
|
| 628 |
ranked = self._fallback_min3()
|
| 629 |
final = ranked[0]
|
| 630 |
alts = ranked[1:]
|
|
|
|
| 639 |
# Top fusionné : au moins 3
|
| 640 |
min_top = max(3, topn if isinstance(topn, int) and topn>0 else 3)
|
| 641 |
if len(ranked) < min_top:
|
| 642 |
+
# compléter avec un petit fallback sans dupliquer (avec labels)
|
| 643 |
already = {r["code"] for r in ranked}
|
| 644 |
for fb in self._fallback_min3():
|
| 645 |
+
if len(ranked) >= min_top:
|
| 646 |
+
break
|
| 647 |
+
if fb["code"] in already:
|
| 648 |
+
continue
|
| 649 |
ranked.append(fb)
|
| 650 |
|
| 651 |
+
# Sélection finale
|
| 652 |
final = ranked[0]
|
| 653 |
alts = ranked[1:1+min_top-1]
|
| 654 |
|
| 655 |
+
# Sécurise le label si jamais manquant (ne change rien au scoring)
|
| 656 |
+
final.setdefault("label", CODE_TO_LABEL.get(final["code"], ""))
|
| 657 |
+
for a in alts:
|
| 658 |
+
a.setdefault("label", CODE_TO_LABEL.get(a["code"], ""))
|
| 659 |
+
|
| 660 |
ev = final.get("evidences", [])
|
| 661 |
exp = (
|
| 662 |
f"Choix {final['code']} (score {final['score_final']:.2f}) – votes={final.get('votes',0)} – raisons: {', '.join(sorted(set(ev)))}"
|
| 663 |
if ev else
|
| 664 |
f"Choix {final['code']} (score {final['score_final']:.2f}) – fallback partiel."
|
| 665 |
)
|
| 666 |
+
|
| 667 |
+
# candidates_top avec labels assurés
|
| 668 |
+
candidates_top = []
|
| 669 |
+
for r in ranked[:min_top]:
|
| 670 |
+
r.setdefault("label", CODE_TO_LABEL.get(r["code"], ""))
|
| 671 |
+
candidates_top.append(r)
|
| 672 |
+
|
| 673 |
+
return {"final": final, "alternatives": alts, "candidates_top": candidates_top, "explanation": exp}
|
| 674 |
+
|
| 675 |
|
| 676 |
|
| 677 |
|