Remplace l'appel externe `pdftotext -layout` (poppler-utils) par pdfplumber, une bibliotheque Python pure : plus aucune dependance systeme, ce qui permettra d'empaqueter l'application en executable autonome. - `read_pdf()` retourne un `PdfContent` (texte layout + mots de la premiere page avec coordonnees) ; `extract_text_from_pdf()` est conserve comme wrapper de compatibilite. - Metadonnees : la reference est desormais extraite via les coordonnees geometriques des mots (fiable sur l'en-tete multi-colonnes), regex du destinataire durcie (forme pointee `S.C.I.`). - Operations : reduction des espaces multiples dans les descriptions. - Dockerfile : suppression de poppler-utils. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
75 lines
2.3 KiB
Python
75 lines
2.3 KiB
Python
"""Extraction de texte et de mots depuis les PDFs via pdfplumber.
|
|
|
|
pdfplumber est une bibliothèque Python pure : aucune dépendance système
|
|
(contrairement à l'ancien `pdftotext`/poppler), ce qui permet d'empaqueter
|
|
l'application en exécutable autonome (Windows notamment).
|
|
"""
|
|
|
|
from dataclasses import dataclass, field
|
|
|
|
import pdfplumber
|
|
|
|
|
|
@dataclass
|
|
class PdfContent:
|
|
"""Contenu extrait d'un PDF.
|
|
|
|
Attributes:
|
|
text: Texte de toutes les pages avec mise en page préservée
|
|
(équivalent de `pdftotext -layout`). Consommé par les parseurs
|
|
opérations et locataires.
|
|
words: Mots de la première page avec leurs coordonnées
|
|
(``text``, ``x0``, ``x1``, ``top``, ``bottom``). Utilisé par le
|
|
parseur de métadonnées pour les champs d'en-tête multi-colonnes
|
|
où l'alignement par espaces n'est pas fiable.
|
|
"""
|
|
|
|
text: str
|
|
words: list[dict] = field(default_factory=list)
|
|
|
|
|
|
def read_pdf(pdf_path: str) -> PdfContent:
|
|
"""Lit un PDF et retourne son texte (layout) et les mots de l'en-tête.
|
|
|
|
Args:
|
|
pdf_path: Chemin vers le fichier PDF
|
|
|
|
Returns:
|
|
Un objet :class:`PdfContent`.
|
|
"""
|
|
text_parts: list[str] = []
|
|
header_words: list[dict] = []
|
|
|
|
with pdfplumber.open(pdf_path) as pdf:
|
|
for page_index, page in enumerate(pdf.pages):
|
|
text_parts.append(page.extract_text(layout=True) or "")
|
|
# Les métadonnées se trouvent uniquement sur la première page.
|
|
if page_index == 0:
|
|
header_words = [
|
|
{
|
|
"text": w["text"],
|
|
"x0": w["x0"],
|
|
"x1": w["x1"],
|
|
"top": w["top"],
|
|
"bottom": w["bottom"],
|
|
}
|
|
for w in page.extract_words()
|
|
]
|
|
|
|
return PdfContent(text="\n".join(text_parts), words=header_words)
|
|
|
|
|
|
def extract_text_from_pdf(pdf_path: str) -> str:
|
|
"""Extrait le texte du PDF avec mise en page préservée.
|
|
|
|
Conserve la signature historique (retourne une chaîne) pour la CLI et
|
|
les parseurs qui ne consomment que le texte.
|
|
|
|
Args:
|
|
pdf_path: Chemin vers le fichier PDF
|
|
|
|
Returns:
|
|
Texte extrait du PDF avec mise en page préservée
|
|
"""
|
|
return read_pdf(pdf_path).text
|