From 992f6d4f93f3643853cce7a229aa1bf17f923bdd Mon Sep 17 00:00:00 2001 From: Colin Maudry Date: Mon, 22 Jun 2026 17:46:31 +0200 Subject: [PATCH] Recherche dans les tableaux insensible aux accents #79 --- src/utils/table.py | 30 +++++++++++++++++++++++++----- tests/test_table.py | 27 +++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 5 deletions(-) diff --git a/src/utils/table.py b/src/utils/table.py index ef5221c..fa17880 100644 --- a/src/utils/table.py +++ b/src/utils/table.py @@ -4,6 +4,7 @@ import uuid import polars as pl from dash import no_update from polars import selectors as cs +from unidecode import unidecode from src.db import count_marches, count_unique_marches, query_marches, schema from src.utils import logger @@ -215,6 +216,25 @@ def format_values(dff: pl.DataFrame) -> pl.DataFrame: return dff +_ACCENT_REPLACEMENTS = [ + ("[éèêëÉÈÊË]", "e"), + ("[àâäÀÂÄ]", "a"), + ("[ùûüÙÛÜ]", "u"), + ("[îïÎÏ]", "i"), + ("[ôöÔÖ]", "o"), + ("[çÇ]", "c"), + ("[ñÑ]", "n"), + ("[æÆ]", "ae"), + ("[œŒ]", "oe"), +] + + +def _deaccent_col(expr: pl.Expr) -> pl.Expr: + for pattern, replacement in _ACCENT_REPLACEMENTS: + expr = expr.str.replace_all(pattern, replacement) + return expr + + def filter_table_data(lff: pl.LazyFrame, filter_query: str) -> pl.LazyFrame: _schema = lff.collect_schema() filtering_expressions = filter_query.split(" && ") @@ -256,17 +276,17 @@ def filter_table_data(lff: pl.LazyFrame, filter_query: str) -> pl.LazyFrame: elif operator == "contains": if col_type in ["String", "Date"] and isinstance(filter_value, str): filter_value = filter_value.strip('"') + normalized_value = unidecode(filter_value) + col_expr = _deaccent_col(pl.col(col_name)) if filter_value.endswith("*"): lff = lff.filter( - pl.col(col_name).str.starts_with(filter_value[:-1]) + col_expr.str.starts_with(normalized_value[:-1]) ) elif filter_value.startswith("*"): - lff = lff.filter( - pl.col(col_name).str.ends_with(filter_value[1:]) - ) + lff = lff.filter(col_expr.str.ends_with(normalized_value[1:])) else: lff = lff.filter( - pl.col(col_name).str.contains("(?i)" + filter_value) + col_expr.str.contains("(?i)" + normalized_value) ) elif col_type.startswith("Int") or col_type.startswith("Float"): lff = lff.filter(pl.col(col_name) == filter_value) diff --git a/tests/test_table.py b/tests/test_table.py index 643e34c..0df5e50 100644 --- a/tests/test_table.py +++ b/tests/test_table.py @@ -44,6 +44,33 @@ def test_filter_table_data_does_not_call_track_search(monkeypatch, sample_lff): assert result.height == 1 +def test_filter_table_data_accent_insensitive(): + """Chercher sans accent doit trouver des valeurs accentuées, et vice versa.""" + from src.utils.table import filter_table_data + + lff = pl.LazyFrame( + [ + {"uid": "1", "acheteur_nom": "Mairie de Nîmes", "objet": "Voirie"}, + {"uid": "2", "acheteur_nom": "Commune de Reims", "objet": "Éclairage"}, + {"uid": "3", "acheteur_nom": "Ville de Paris", "objet": "Travaux"}, + ] + ) + + # Sans accent → trouve la valeur accentuée + result = filter_table_data(lff, "{acheteur_nom} icontains Nimes").collect() + assert result.height == 1 + assert result["uid"][0] == "1" + + # Avec accent → trouve la valeur accentuée + result = filter_table_data(lff, "{acheteur_nom} icontains Nîmes").collect() + assert result.height == 1 + + # Sans accent → trouve la valeur avec accent initial (É) + result = filter_table_data(lff, "{objet} icontains eclairage").collect() + assert result.height == 1 + assert result["uid"][0] == "2" + + def test_normalize_sort_by_handles_empty(): from src.utils.table import normalize_sort_by