feat: support .eml Picnic + correction fuzzy matching

Support .eml :
  - pipeline._eml_to_html() extrait le HTML des emails Picnic
  - Déposer un .eml dans inbox/picnic/ fonctionne comme un .html
  - Pas de nouvelle dépendance (module email stdlib)
  - 5 tests ajoutés (test_eml.py)

Correction fuzzy matching :
  - Le score est maintenant calculé sur le nom seul (avant " | ")
  - Évite que les différences de marque/poids pénalisent le score
  - Résultat : 8 paires trouvées vs 0 avant la correction

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-25 18:23:57 +01:00
parent be4d4a7076
commit 8af474c928
3 changed files with 146 additions and 2 deletions

View File

@@ -51,12 +51,17 @@ def find_fuzzy_matches(
]
# Produit cartésien filtré par seuil
# On compare uniquement le nom (avant le premier " | ") pour éviter que
# les différences de marque/quantité ("| MDD | 1kg" vs "| - | -") ne
# pénalisent artificiellement le score.
matches = []
for p in picnic_names:
p_name = p.split(" | ")[0].strip()
for lec in leclerc_names:
if p == lec:
continue # exact match déjà géré par get_compare_prices
score = fuzz.token_sort_ratio(p, lec)
lec_name = lec.split(" | ")[0].strip()
score = fuzz.token_sort_ratio(p_name, lec_name)
if score >= threshold:
matches.append({"name_picnic": p, "name_leclerc": lec, "score": score})

View File

@@ -10,7 +10,9 @@ Usage :
inserted = import_receipt("samples/picnic_sample.html", source="picnic")
"""
import email
import logging
from email import policy
from pathlib import Path
from tickettracker.db import schema, repository
@@ -95,7 +97,10 @@ def _parse(file_path: Path, source: str):
"""
if source == "picnic":
from tickettracker.parsers import picnic
html_content = file_path.read_text(encoding="utf-8", errors="replace")
if file_path.suffix.lower() == ".eml":
html_content = _eml_to_html(file_path)
else:
html_content = file_path.read_text(encoding="utf-8", errors="replace")
return picnic.parse(html_content)
if source == "leclerc":
@@ -104,3 +109,30 @@ def _parse(file_path: Path, source: str):
# Jamais atteint grâce à la validation en amont, mais satisfait mypy
raise ValueError(f"Source inconnue : '{source}'")
def _eml_to_html(file_path: Path) -> str:
"""Extrait la partie HTML d'un fichier .eml (email de confirmation Picnic).
Lit le .eml avec le module email stdlib, parcourt les parties MIME
et retourne le contenu de la première partie text/html trouvée.
Args:
file_path: Chemin vers le fichier .eml.
Returns:
Contenu HTML sous forme de chaîne.
Raises:
ValueError: Si aucune partie HTML n'est trouvée dans le .eml.
"""
raw = file_path.read_bytes()
msg = email.message_from_bytes(raw, policy=policy.default)
for part in msg.walk():
if part.get_content_type() == "text/html":
return part.get_content()
raise ValueError(
f"Aucune partie HTML trouvée dans le fichier .eml : {file_path.name}"
)