Spaces:
Runtime error
Runtime error
Claude Opus 5
feat(recherche): le titre passe devant le corps du texte, et les fautes de frappe passent
893a679 Download app/services/article_reference.py from rahim237cm/jurix-api: direct link, hf CLI and curl.
- Browser
- Download file 4.5 kB
-
https://huggingface.co/spaces/rahim237cm/jurix-api/resolve/main/app/services/article_reference.py
- Command line
-
hf download hf://spaces/rahim237cm/jurix-api/app/services/article_reference.py
-
curl -L -o article_reference.py https://huggingface.co/spaces/rahim237cm/jurix-api/resolve/main/app/services/article_reference.py
4.5 kB
| """ | |
| Reconnaissance d'une reference d'article dans une requete. | |
| « article 35 du code minier » doit ouvrir le Code Minier sur son article 35. | |
| Cela suppose trois choses : reconnaitre le numero, reconnaitre le document, et | |
| normaliser le numero sous la forme reellement stockee. | |
| CE MODULE EXISTE PARCE QUE LA LOGIQUE ETAIT ECRITE HUIT FOIS. Un inventaire du | |
| depot trouve des motifs « article N » dans rag_service (trois : detection dans | |
| la question, CITATION_REGEX sur la reponse du modele, normalisation), | |
| search_service, reranker, legal_domain_classifier, chunk_refiner et | |
| text_chunker — tous legerement differents. Celui du RAG refuse « art.35 » faute | |
| d'espace ; celui du reranker est le seul a accepter le prefixe L/R/D. Ces ecarts | |
| ne se voient jamais a la lecture, seulement a l'usage. | |
| Author: JuriX Team | |
| """ | |
| import re | |
| from dataclasses import dataclass | |
| from typing import Optional | |
| # Ordinaux ecrits en toutes lettres. `text_chunker.normalize_article_number` | |
| # ecrit "1" en base pour « Article premier » : sans cette table, une requete | |
| # « article premier du code minier » produirait la cible "PREMIER", qui ne | |
| # correspond a aucune ligne. | |
| _ORDINAL_WORDS = { | |
| "PREMIER": "1", "PREMIERE": "1", "PREMIÈRE": "1", "1ER": "1", "1ÈRE": "1", | |
| "1ERE": "1", "FIRST": "1", | |
| "DEUXIEME": "2", "DEUXIÈME": "2", "SECOND": "2", "SECONDE": "2", | |
| "TROISIEME": "3", "TROISIÈME": "3", "THIRD": "3", | |
| } | |
| _LEADING = re.compile(r"^(ARTICLE|ART\.?|SECTION)\s*", re.IGNORECASE) | |
| # Motifs essayes DANS L'ORDRE. Les deux premiers exigent une mention de | |
| # document ; les deux derniers acceptent un numero seul. L'ordre compte : sans | |
| # lui, « article 35 du code minier » serait capte par le motif « numero seul », | |
| # qui jetterait « du code minier ». | |
| _PATTERNS = ( | |
| # « article 35 du code minier », « art.35 de la constitution » | |
| re.compile( | |
| r"\bart(?:icle|\.)?\s*([LRD]\s*)?(\d+(?:[-.]\d+)*(?:\s*(?:bis|ter|quater))?)" | |
| r"\s+(?:de\s+la\s+|de\s+l['’]\s*|du\s+|de\s+|des\s+)(.+)", | |
| re.IGNORECASE, | |
| ), | |
| # « article premier de la constitution » | |
| re.compile( | |
| r"\bart(?:icle|\.)?\s+(premier|première|1er|1ère)" | |
| r"\s+(?:de\s+la\s+|de\s+l['’]\s*|du\s+|de\s+|des\s+)(.+)", | |
| re.IGNORECASE, | |
| ), | |
| # « article 35 » seul — aucune mention de document | |
| re.compile( | |
| r"\bart(?:icle|\.)?\s*([LRD]\s*)?(\d+(?:[-.]\d+)*)\s*$", re.IGNORECASE | |
| ), | |
| re.compile(r"\bart(?:icle|\.)?\s+(premier|première|1er|1ère)\s*$", re.IGNORECASE), | |
| ) | |
| class ArticleReference: | |
| """Numero demande, et mention du document si elle est presente.""" | |
| number: str | |
| doc_hint: str = "" | |
| def has_hint(self) -> bool: | |
| return bool(self.doc_hint.strip()) | |
| def normalize_number(number: Optional[str]) -> str: | |
| """ | |
| Ramene un numero d'article a la forme stockee dans `articles.number`. | |
| « Article 1er », « premier », « 1ER » et « 1 » designent le meme article. | |
| """ | |
| if not number: | |
| return "" | |
| cleaned = _LEADING.sub("", str(number).strip().upper()) | |
| cleaned = cleaned.strip(" .:-") | |
| cleaned = re.sub(r"\s+", " ", cleaned) | |
| return _ORDINAL_WORDS.get(cleaned, cleaned) | |
| def parse_reference(query: str) -> Optional[ArticleReference]: | |
| """ | |
| Extrait la reference d'article d'une requete, ou None. | |
| >>> parse_reference("article 35 du code minier") | |
| ArticleReference(number='35', doc_hint='code minier') | |
| >>> parse_reference("art.35") | |
| ArticleReference(number='35', doc_hint='') | |
| >>> parse_reference("le regime des permis de recherche") is None | |
| True | |
| """ | |
| if not query: | |
| return None | |
| text = query.strip() | |
| for index, pattern in enumerate(_PATTERNS): | |
| match = pattern.search(text) | |
| if not match: | |
| continue | |
| groups = match.groups() | |
| if index == 0: | |
| prefix, number, hint = groups | |
| raw = f"{prefix.strip()} {number}".strip() if prefix else number | |
| return ArticleReference(normalize_number(raw), hint.strip(" .,;:")) | |
| if index == 1: | |
| number, hint = groups | |
| return ArticleReference(normalize_number(number), hint.strip(" .,;:")) | |
| if index == 2: | |
| prefix, number = groups | |
| raw = f"{prefix.strip()} {number}".strip() if prefix else number | |
| return ArticleReference(normalize_number(raw)) | |
| return ArticleReference(normalize_number(groups[0])) | |
| return None | |