pip install unspsc-frUNSPSC() cherche la base SQLite dans cet ordre :
UNSPSC(db_path=...)— chemin passé explicitement- variable d'environnement
UNSPSC_FR_DB - cache local (
~/.cache/unspsc-fr/<version>/unspsc-bilingue.sqlite), téléchargée depuis la release GitHub si absente (~88 Mo, une seule fois)
from unspsc_fr import UNSPSC
u = UNSPSC() # cache/téléchargement automatique
u = UNSPSC(db_path="./unspsc-bilingue.sqlite") # base locale expliciteUNSPSC peut s'utiliser comme gestionnaire de contexte pour fermer proprement la
connexion :
with UNSPSC() as u:
print(u.get(10101501).title)Toutes les méthodes de lecture renvoient des Entry (dataclass immuable) :
@dataclass(frozen=True)
class Entry:
code: int
level: str # 'segment' | 'family' | 'class' | 'commodity'
title_en: str
title_fr: Optional[str]
definition_en: Optional[str] = None
@property
def title(self) -> str: ... # title_fr si présent, sinon title_en
@property
def is_translated(self) -> bool: ... # title_fr is not Noneentry = u.get(10101501)
entry.title # 'Chats'
entry.title_en # 'Cats'
entry.is_translated # True
entry = u.get(57000000) # segment sans traduction française
entry.title # 'Humanitarian Relief Items, Kits, or Accessories' (repli EN)
entry.is_translated # FalseRenvoie l'Entry pour un code, ou None s'il n'existe pas.
u.get(10101501) # → Entry(level='commodity', …)
u.get(99999999) # → NonePour les rares codes existant à deux niveaux (voir
classification.md),
level lève l'ambiguïté ; sans lui, le niveau canonique (le plus haut) est retourné :
u.get(26132000) # → level='class' (canonique)
u.get(26132000, level="commodity") # → level='commodity'Enfants directs d'un code. Sans argument : les 58 segments racine. Sur un produit (niveau le plus profond) : liste vide.
u.children() # les 58 segments
u.children(10100000) # classes de la famille "Animaux vivants"
u.children(10101501) # [] : un produit n'a pas d'enfantsChaîne des parents, du segment jusqu'au parent direct (exclut le code lui-même) :
[e.level for e in u.ancestors(10101501)]
# ['segment', 'family', 'class']Recherche plein texte (SQLite FTS5), insensible aux accents et à la casse, classée par
pertinence (bm25). Le dernier mot de query est traité en préfixe.
u.search("ciment", lang="fr")
u.search("electricite", lang="fr") # sans accent : trouve "électricité"
u.search("cemen", lang="en", level="commodity", limit=5) # préfixe + filtresfrom unspsc_fr import level_of, parent_code
level_of(10101501) # 'commodity' — déduit de la forme du code
level_of(10100000) # 'family'
parent_code(10101501, "commodity") # 10101500
parent_code(10101500, "class") # 10100000
parent_code(10000000, "segment") # NoneCes deux fonctions n'ouvrent aucune connexion : elles opèrent uniquement sur l'arithmétique du code (voir classification.md), utile pour valider ou trier des codes dans un pipeline sans dépendre de la base.
Repérer des candidats de code depuis une description produit. search() fait une
recherche plein texte à correspondance exacte sur tous les mots (logique ET) : une
requête à plusieurs mots ne renvoie rien si un seul des mots est absent des libellés
indexés. Une phrase telle que « sacs de ciment » échoue donc souvent telle quelle — les
mots de liaison (« de », « pour », « et »…) n'apparaissent pas dans les libellés
UNSPSC. Filtrer ces mots et retomber sur les termes les plus distinctifs donne de
meilleurs candidats :
from unspsc_fr import UNSPSC
STOPWORDS = {"de", "du", "des", "le", "la", "les", "un", "une", "et", "pour", "en", "avec"}
def candidates(u: UNSPSC, description: str, limit: int = 3):
tokens = [w for w in description.lower().split() if w not in STOPWORDS]
for q in (" ".join(tokens), *tokens): # phrase filtrée, puis mot par mot
if q and (hits := u.search(q, lang="fr", level="commodity", limit=limit)):
return hits
return []
with UNSPSC() as u:
for description in ["sacs de ciment", "ordinateur portable"]:
hits = candidates(u, description)
print(description, "→", [(e.code, e.title) for e in hits])C'est une heuristique de repérage, pas un classifieur : elle renvoie des candidats plausibles par correspondance lexicale, sans compréhension sémantique — « service de conseil » ne trouvera pas la bonne classe si ses mots-clés n'apparaissent pas explicitement dans les libellés. Une classification fiable sur des descriptions libres demande des embeddings sémantiques (voir les pistes évoquées dans le README du dépôt).
Construire un fil d'Ariane (breadcrumb) pour un produit :
def breadcrumb(u: UNSPSC, code: int) -> str:
chain = [*u.ancestors(code), u.get(code)]
return " > ".join(e.title for e in chain if e)
breadcrumb(u, 10101501)
# 'Fournitures, accessoires et matériel pour plantes et animaux vivants > Animaux vivants > Bétail > Chats'Exporter tous les produits traduits d'un segment en CSV :
import csv
with UNSPSC() as u, open("segment_10.csv", "w", newline="") as f:
writer = csv.writer(f)
for family in u.children(10000000):
for klass in u.children(family.code):
for product in u.children(klass.code):
if product.is_translated:
writer.writerow([product.code, product.title_fr])La version est déclarée une seule fois, dans unspsc_fr.__version__
(python/src/unspsc_fr/__init__.py) — pyproject.toml la lit dynamiquement via
tool.hatch.version.
# 1. modifier __version__ dans python/src/unspsc_fr/__init__.py
git commit -am "unspsc-fr 0.2.0"
git push
git tag py-v0.2.0
git push origin py-v0.2.0 # déclenche .github/workflows/publish-pypi.yml