import polars as pl def _make_lff(rows): return pl.LazyFrame(rows) def test_compute_considerations_stats_basic(): from src.figures import compute_considerations_stats lff = _make_lff( [ # u1 : social oui (Clause), env non (Sans objet) { "uid": "u1", "considerationsSociales": "Clause sociale", "considerationsEnvironnementales": "Sans objet", }, # u2 : social non (Sans objet), env oui (Critère) { "uid": "u2", "considerationsSociales": "Sans objet", "considerationsEnvironnementales": "Critère environnemental", }, # u3 : social oui (Marché réservé), env null { "uid": "u3", "considerationsSociales": "Marché réservé", "considerationsEnvironnementales": None, }, # u4 : social autre valeur (pas "Sans objet"), env null { "uid": "u4", "considerationsSociales": "Pas de considération sociale", "considerationsEnvironnementales": "Sans objet", }, ] ) stats = compute_considerations_stats(lff) # champs_renseignes : basé sur sociales. 4 non-null / 4 total -> (4, 100%). assert stats["champs_renseignes"] == (4, 100) # Sociales renseignées : dén=4 non-null, num=3 != "Sans objet" (u1/u3/u4) -> (4, 3, 75%). assert stats["sociales_renseignees"] == (4, 3, 75) # Env renseignées : dén=3 non-null (u1/u2/u4), num=1 != "Sans objet" (u2) -> (3, 1, 33%). assert stats["environnementales_renseignees"] == (3, 1, 33) def test_compute_considerations_stats_dedup_per_uid(): from src.figures import compute_considerations_stats lff = _make_lff( [ # u1 présent 2 fois (2 titulaires) -> compté une seule fois { "uid": "u1", "considerationsSociales": "Clause sociale", "considerationsEnvironnementales": "Sans objet", }, { "uid": "u1", "considerationsSociales": "Clause sociale", "considerationsEnvironnementales": "Sans objet", }, { "uid": "u2", "considerationsSociales": "Sans objet", "considerationsEnvironnementales": "Sans objet", }, ] ) stats = compute_considerations_stats(lff) # 2 uid distincts. Social 2 non-null, 1 != "Sans objet" (u1) -> (2, 1, 50%). assert stats["champs_renseignes"] == (2, 100) assert stats["sociales_renseignees"] == (2, 1, 50) # Env 2 non-null, 0 != "Sans objet" -> (2, 0, 0%). assert stats["environnementales_renseignees"] == (2, 0, 0) def test_compute_considerations_stats_missing_column(): from src.figures import compute_considerations_stats lff = _make_lff( [ {"uid": "u1", "considerationsSociales": "Clause sociale"}, {"uid": "u2", "considerationsSociales": "Sans objet"}, ] ) stats = compute_considerations_stats(lff) # Colonne env absente -> (0, 0). Social : 2 non-null, 1 != "Sans objet" -> (2, 1, 50%). assert stats["champs_renseignes"] == (2, 100) assert stats["sociales_renseignees"] == (2, 1, 50) assert stats["environnementales_renseignees"] == (0, 0) def test_compute_considerations_stats_empty(): from src.figures import compute_considerations_stats lff = pl.LazyFrame( { "uid": pl.Series([], dtype=pl.String), "considerationsSociales": pl.Series([], dtype=pl.String), "considerationsEnvironnementales": pl.Series([], dtype=pl.String), } ) stats = compute_considerations_stats(lff) assert stats["champs_renseignes"] == (0, 0) assert stats["sociales_renseignees"] == (0, 0) assert stats["environnementales_renseignees"] == (0, 0) def test_get_considerations_card_content_returns_three_progress_bars(): import dash_bootstrap_components as dbc from dash import html from src.figures import get_considerations_card_content lff = pl.LazyFrame( [ { "uid": "u1", "considerationsSociales": "Clause sociale", "considerationsEnvironnementales": "Sans objet", }, { "uid": "u2", "considerationsSociales": "Sans objet", "considerationsEnvironnementales": "Critère environnemental", }, ] ) div = get_considerations_card_content(lff) assert isinstance(div, html.Div) def find_progress(component, found): if isinstance(component, dbc.Progress): found.append(component) children = getattr(component, "children", None) if isinstance(children, (list, tuple)): for c in children: find_progress(c, found) elif children is not None and not isinstance(children, str): find_progress(children, found) return found inner_bars = [b for b in find_progress(div, []) if getattr(b, "bar", False)] assert len(inner_bars) == 3 bar_ren, bar_social, bar_env = inner_bars # Bar 1 : champs renseignés (2/2 = 100%, gris) assert bar_ren.value == 100 assert bar_ren.color == "#6c757d" # Bar 2 : sociales parmi renseignés (u1 != "Sans objet" -> 1/2 = 50%, rose) assert bar_social.value == 50 assert bar_social.color == "#CC6677" # Bar 3 : env parmi renseignés (u2 != "Sans objet" -> 1/2 = 50%, vert) assert bar_env.value == 50 assert bar_env.color == "#117733" def test_build_org_markers_groups_and_counts(): from src.figures import build_org_markers lff = pl.LazyFrame( [ { "uid": "u1", "acheteur_longitude": 2.35, "acheteur_latitude": 48.85, "acheteur_nom": "ACHETEUR A", }, { "uid": "u2", "acheteur_longitude": 2.35, "acheteur_latitude": 48.85, "acheteur_nom": "ACHETEUR A", }, { "uid": "u3", "acheteur_longitude": -1.68, "acheteur_latitude": 48.11, "acheteur_nom": "ACHETEUR B", }, ] ) markers = build_org_markers(lff, "acheteur") assert len(markers) == 2 marker_a = next(m for m in markers if m["tooltip"].startswith("ACHETEUR A")) assert marker_a["tooltip"] == "ACHETEUR A (2 marchés)" assert marker_a["lat"] == 48.85 assert marker_a["lon"] == 2.35 assert marker_a["marker_color"] == "#E69F00" def test_build_org_markers_filters_null_coordinates(): from src.figures import build_org_markers lff = pl.LazyFrame( [ { "uid": "u1", "acheteur_longitude": None, "acheteur_latitude": None, "acheteur_nom": "ACHETEUR A", }, { "uid": "u2", "acheteur_longitude": 2.35, "acheteur_latitude": 48.85, "acheteur_nom": "ACHETEUR B", }, ] ) markers = build_org_markers(lff, "acheteur") assert len(markers) == 1 assert markers[0]["tooltip"] == "ACHETEUR B (1 marchés)" def test_build_org_markers_missing_columns_returns_empty(): from src.figures import build_org_markers lff = pl.LazyFrame([{"uid": "u1", "acheteur_nom": "ACHETEUR A"}]) assert build_org_markers(lff, "acheteur") == [] assert build_org_markers(lff, "titulaire") == [] def _org_map_dff(): return pl.DataFrame( [ { "uid": "u1", "acheteur_longitude": 2.35, "acheteur_latitude": 48.85, "acheteur_nom": "ACHETEUR A", "titulaire_longitude": -1.68, "titulaire_latitude": 48.11, "titulaire_nom": "TITULAIRE A", } ] ) def test_get_org_location_map_bounds_cover_home_and_counterpart(): import dash_leaflet as dl from src.figures import get_org_location_map leaflet_map = get_org_location_map(_org_map_dff(), "acheteur", "test-map") assert isinstance(leaflet_map, dl.Map) assert leaflet_map.bounds == [[48.11, -1.68], [48.85, 2.35]] geojson_layers = [c for c in leaflet_map.children if isinstance(c, dl.GeoJSON)] assert {layer.id for layer in geojson_layers} == { "test-map-acheteur", "test-map-titulaire", } def test_get_org_location_map_marks_home_org_marker_acheteur(): import dash_leaflet as dl from src.figures import get_org_location_map leaflet_map = get_org_location_map(_org_map_dff(), "acheteur", "test-map") geojson_layers = { layer.id: layer for layer in leaflet_map.children if isinstance(layer, dl.GeoJSON) } acheteur_features = geojson_layers["test-map-acheteur"].data["features"] titulaire_features = geojson_layers["test-map-titulaire"].data["features"] assert all(f["properties"]["is_home"] for f in acheteur_features) assert all(not f["properties"].get("is_home") for f in titulaire_features) def test_get_org_location_map_marks_home_org_marker_titulaire(): import dash_leaflet as dl from src.figures import get_org_location_map leaflet_map = get_org_location_map(_org_map_dff(), "titulaire", "test-map") geojson_layers = { layer.id: layer for layer in leaflet_map.children if isinstance(layer, dl.GeoJSON) } acheteur_features = geojson_layers["test-map-acheteur"].data["features"] titulaire_features = geojson_layers["test-map-titulaire"].data["features"] assert all(f["properties"]["is_home"] for f in titulaire_features) assert all(not f["properties"].get("is_home") for f in acheteur_features) def test_get_org_location_map_defaults_to_france_view_without_coordinates(): from src.figures import get_org_location_map dff = pl.DataFrame( [{"uid": "u1", "acheteur_nom": "ACHETEUR A", "titulaire_nom": "TITULAIRE A"}] ) leaflet_map = get_org_location_map(dff, "acheteur", "test-map") assert leaflet_map.center == [46.6, 2.2] assert leaflet_map.zoom == 5