Ce que vous saurez faire
- Analyser 50 PDF juridiques en batch
- Extraire structure JSON
- Créer pages Notion avec métadonnées
- Lier documents entre eux
Étape 1 — Setup
pip install anthropic pymupdf notion-client tenacity
Étape 2 — Lire PDF + OCR si besoin
import pymupdf
from pathlib import Path
def lire_pdf_ocr(path):
doc = pymupdf.open(path)
texte = "\n".join(p.get_text() for p in doc)
# Si peu de texte, probablement scanné
if len(texte.strip()) < 200 * len(doc):
import pytesseract
from PIL import Image
import io
ocr = []
for p in doc:
pix = p.get_pixmap(dpi=200)
img = Image.open(io.BytesIO(pix.tobytes("png")))
ocr.append(pytesseract.image_to_string(img, lang="fra"))
texte = "\n".join(ocr)
return texte
Étape 3 — Prompt d’extraction
import json
from anthropic import Anthropic
from tenacity import retry, stop_after_attempt, wait_exponential
claude = Anthropic()
PROMPT = """À partir du document, produis un JSON strict:
{
"titre": "",
"type": "arret|contrat|avis|loi|autre",
"juridiction": "",
"date": "YYYY-MM-DD",
"parties": [""],
"resume": "150 mots max",
"points_cles": ["3-5 puces"],
"clauses_notables": ["références articles"],
"tags": ["4-6 tags fr"],
"documents_relies": ["titres exacts"]
}"""
@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=2))
def analyser(pdf_path):
texte = lire_pdf_ocr(pdf_path)[:60000]
r = claude.messages.create(
model="claude-sonnet-4-6", max_tokens=2000,
system=[{"type":"text","text":PROMPT,"cache_control":{"type":"ephemeral"}}],
messages=[{"role":"user","content":
f"<document>\n{texte}\n</document>\n\nJSON:"}])
return json.loads(r.content[0].text)
Étape 4 — Créer pages Notion
from notion_client import Client as Notion
import os
notion = Notion(auth=os.environ["NOTION_TOKEN"])
DB = os.environ["NOTION_DATABASE_ID"]
def creer_notion(analyse, pdf_name):
page = notion.pages.create(
parent={"database_id": DB},
properties={
"Titre": {"title":[{"text":{"content":analyse["titre"][:200]}}]},
"Type": {"select":{"name":analyse["type"]}},
"Juridiction": {"rich_text":[{"text":{"content":analyse["juridiction"]}}]},
"Date": {"date":{"start":analyse["date"]}},
"Fichier": {"rich_text":[{"text":{"content":pdf_name}}]},
"Tags": {"multi_select":[{"name":t[:100]} for t in analyse["tags"]]},
"Resume": {"rich_text":[{"text":{"content":analyse["resume"][:2000]}}]},
},
children=[
{"object":"block","heading_2":{"rich_text":[{"text":{"content":"Points clés"}}]}},
*[{"object":"block","bulleted_list_item":{"rich_text":[{"text":{"content":p}}]}}
for p in analyse["points_cles"]],
{"object":"block","heading_2":{"rich_text":[{"text":{"content":"Clauses notables"}}]}},
*[{"object":"block","bulleted_list_item":{"rich_text":[{"text":{"content":c}}]}}
for c in analyse["clauses_notables"]],
])
return page["id"]
Étape 5 — Pipeline parallèle
from concurrent.futures import ThreadPoolExecutor, as_completed
pdfs = list(Path("./docs_juridiques").glob("*.pdf"))
resultats = []
def traiter(pdf):
try:
analyse = analyser(str(pdf))
page_id = creer_notion(analyse, pdf.name)
return {"pdf":pdf.name, "page_id":page_id, "status":"ok"}
except Exception as e:
return {"pdf":pdf.name, "error":str(e), "status":"error"}
with ThreadPoolExecutor(max_workers=5) as pool:
futures = [pool.submit(traiter, p) for p in pdfs]
for fut in as_completed(futures):
r = fut.result()
print(r["pdf"], r["status"])
resultats.append(r)
with open("traitement_log.json","w") as f:
json.dump(resultats, f, indent=2, ensure_ascii=False)
Étape 6 — Relations Notion
# Phase 2: lier les pages selon "documents_relies"
titres_vers_page = {}
for p in notion.databases.query(database_id=DB)["results"]:
titre = p["properties"]["Titre"]["title"][0]["plain_text"]
titres_vers_page[titre] = p["id"]
for p in notion.databases.query(database_id=DB)["results"]:
# Extract relations des "documents_relies" block
blocks = notion.blocks.children.list(p["id"])["results"]
# ... lier via notion.pages.update avec property "Documents reliés"
Étape 7 — Déduplication des tags
from collections import Counter
pages = notion.databases.query(database_id=DB)["results"]
compteur = Counter()
for p in pages:
for t in p["properties"]["Tags"]["multi_select"]:
compteur[t["name"]] += 1
# Fusionner variantes
fusions = {"contrat": ["contrats","contractuel"],
"OHADA": ["Ohada","ohada"]}
Étape 8 — Recherche sémantique sur Notion
import voyageai, chromadb
vo = voyageai.Client()
chroma = chromadb.PersistentClient("./notion_chroma")
coll = chroma.get_or_create_collection("juridique")
for p in pages:
titre = p["properties"]["Titre"]["title"][0]["plain_text"]
resume = p["properties"]["Resume"]["rich_text"][0]["plain_text"] if p["properties"]["Resume"]["rich_text"] else ""
emb = vo.embed([titre + " " + resume], model="voyage-3",
input_type="document").embeddings[0]
coll.add(documents=[resume], embeddings=[emb],
metadatas=[{"notion_id":p["id"],"titre":titre}], ids=[p["id"]])
Coûts 50 PDF 20 pages
Lecture + OCR: gratuit (local)
Claude Sonnet avec cache: ~4,5 USD
Notion API: gratuit
Embeddings: 0,06 USD
Total: ~5 USD (3k FCFA) pour 50 documents
Besoin d'un site web ?
Confiez-nous la Création de Votre Site Web
Site vitrine, e-commerce ou application web — nous transformons votre vision en réalité digitale. Accompagnement personnalisé de A à Z.
À partir de 250.000 FCFA
Parlons de Votre Projet
Publicité