mkdir -p dila/CASS && cd dila/CASS
curl -O https://echanges.dila.gouv.fr/OPENDATA/CASS/Freemium_cass_global_20250713-140000.tar.gz
tar xzf Freemium_cass_global_20250713-140000.tar.gz
# Puis chaque archive incrémentale, dans l'ordre chronologique
curl -s https://echanges.dila.gouv.fr/OPENDATA/CASS/ | grep -o 'CASS_2026[0-9-]*\.tar\.gz' | sort -u > maj.txt
for f in $(cat maj.txt); do curl -sO https://echanges.dila.gouv.fr/OPENDATA/CASS/$f && tar xzf $f; done
法国最高法院(Cour de cassation)近期的判决遵循一种可识别的结构。在所审查的判决中,可以看到「Faits et procédure」(事实与程序)、「Examen des moyens」(理由审查)这些标题,随后每项理由下都有「Énoncé du moyen」(理由陈述)和「Réponse de la Cour」(法院答复),最后是由「PAR CES MOTIFS」(基于上述理由)引出的裁判主文。每700个token切分一次,会将提交给法院的问题与法院的答复分开,产生含义不明确的片段。请先按这些标题切分,再仅对过长的块进一步拆分。
import re
COUPURE = re.compile(r"\n(?=(?:Faits et procédure|Examen des moyens|Sur le |Énoncé du moyen|Enoncé du moyen|Réponse de la Cour|PAR CES MOTIFS))")
def extraits(d, max_car=2200):
en_tete = f"{d['titre']} ({d['ecli']})\n"
sortie, tampon = [], ''
for bloc in COUPURE.split(d['texte']):
for para in bloc.split('\n'):
if len(tampon) + len(para) > max_car and tampon:
sortie.append(en_tete + tampon)
tampon = ''
tampon += para + '\n'
if tampon.strip():
sortie.append(en_tete + tampon)
return sortie
from sentence_transformers import SentenceTransformer
from qdrant_client import QdrantClient, models
emb = SentenceTransformer('BAAI/bge-m3', device='cuda')
emb.max_seq_length = 1024
client = QdrantClient(host='localhost', port=6333)
if not client.collection_exists('cass'):
client.create_collection('cass', vectors_config=models.VectorParams(
size=1024, distance=models.Distance.COSINE))
def indexer(dossier, taille_lot=64):
n, lot = 0, []
def vider():
vecs = emb.encode([x[0] for x in lot], batch_size=32, normalize_embeddings=True)
client.upsert('cass', points=[models.PointStruct(id=x[2], vector=v.tolist(), payload=x[1])
for x, v in zip(lot, vecs)])
lot.clear()
for d in decisions(dossier):
for texte in extraits(d):
n += 1
payload = {k: d[k] for k in ('titre', 'ecli', 'pourvoi', 'juridiction', 'formation', 'solution')}
payload['date_int'] = int(d['date'].replace('-', ''))
payload['texte'] = texte
lot.append((texte, payload, n))
if len(lot) >= taille_lot:
vider()
if lot:
vider()
def chercher(question, depuis=None, formation=None, k=8):
conds = []
if depuis:
conds.append(models.FieldCondition(key='date_int', range=models.Range(gte=depuis)))
if formation:
conds.append(models.FieldCondition(key='formation', match=models.MatchValue(value=formation)))
vec = emb.encode(question, normalize_embeddings=True).tolist()
res = client.query_points('cass', query=vec, limit=k,
query_filter=models.Filter(must=conds) if conds else None)
return res.points
for p in chercher('rupture anticipée du CDD par l employeur', depuis=20230101):
print(p.payload['titre'], p.payload['pourvoi'], round(p.score, 3))
Tu es un assistant de recherche en jurisprudence. Tu réponds uniquement à partir des
extraits fournis. Pour chaque affirmation, cite entre crochets le numéro de pourvoi
et l'ECLI de la décision. Si les extraits ne permettent pas de répondre, écris :
aucune décision retrouvée. Tu ne donnes pas d'avis juridique.
可以从 DILA 服务器获取:刊登于《公报》的判决收录在 CASS 档案中,未刊登的判决收录在 INCA 档案中;也可以通过法国最高司法法院(Cour de cassation)的 Judilibre API 获取。相关数据集的介绍页面位于 data.gouv.fr。这些档案只是简单的 XML 文件,很容易在本地处理。