Compare commits

...

6 Commits

Author SHA1 Message Date
Colin Maudry 0db800fdab Changelog 2.6.2 2026-02-22 18:41:01 +01:00
Colin Maudry 4619dd2708 Correction du téléchargemnent buggé dans /tableau + test 2026-02-22 18:39:31 +01:00
Colin Maudry 367e5f64ff Changelog 2.6.1 2026-02-17 14:50:29 +01:00
Colin Maudry adda58bada Suppresion des liens canoniques, pas trouvé comment les insérer assez tôt 2026-02-17 14:24:46 +01:00
Colin Maudry 2b0b048520 Amélioration du nom titulaire/acheteur 2026-02-15 16:54:30 +01:00
Colin Maudry 5c43bfea78 AMélioration de la génération de liens canoniques (pas de doublons) 2026-02-15 16:50:09 +01:00
10 changed files with 72 additions and 50 deletions
+8
View File
@@ -1,3 +1,11 @@
##### 2.6.2 (22 février 2026)
- Correction du téléchargemnent buggé dans /tableau
##### 2.6.1 (17 février 2026)
- Corrections la création des liens canoniques (SEO)
#### 2.6.0 (5 février 2026) #### 2.6.0 (5 février 2026)
- Suite de la refonte graphique - Suite de la refonte graphique
+1 -1
View File
@@ -1,6 +1,6 @@
# decp.info # decp.info
> v2.6.0 > v2.6.2
> Outil d'exploration et de téléchargement des données essentielles de la commande publique. > Outil d'exploration et de téléchargement des données essentielles de la commande publique.
=> [decp.info](https://decp.info) => [decp.info](https://decp.info)
+4 -2
View File
@@ -1,7 +1,7 @@
[project] [project]
name = "decp.info" name = "decp.info"
description = "Interface d'exploration et d'analyse des marchés publics français." description = "Interface d'exploration et d'analyse des marchés publics français."
version = "2.6.0" version = "2.6.2"
requires-python = ">= 3.10" requires-python = ">= 3.10"
authors = [ authors = [
{ name = "Colin Maudry", email = "colin@colmo.tech" } { name = "Colin Maudry", email = "colin@colmo.tech" }
@@ -28,6 +28,7 @@ dev = [
"selenium", "selenium",
"webdriver-manager", "webdriver-manager",
"dash[testing]", "dash[testing]",
"fastexcel"
] ]
[tool.pytest.ini_options] [tool.pytest.ini_options]
@@ -38,6 +39,7 @@ testpaths = [
"tests" "tests"
] ]
env = [ env = [
"DATA_FILE_PARQUET_PATH=tests/test.parquet" "DATA_FILE_PARQUET_PATH=tests/test.parquet",
"DEVELOPMENT=true"
] ]
addopts = "-p no:warnings" addopts = "-p no:warnings"
+1
View File
@@ -87,6 +87,7 @@ app.index_string = """
<title>{%title%}</title> <title>{%title%}</title>
{%favicon%} {%favicon%}
{%css%} {%css%}
<!-- canonical link -->
</head> </head>
<body> <body>
{%app_entry%} {%app_entry%}
+6 -11
View File
@@ -1,4 +1,5 @@
import datetime import datetime
from typing import Any
import dash_bootstrap_components as dbc import dash_bootstrap_components as dbc
import polars as pl import polars as pl
@@ -17,7 +18,6 @@ from dash import (
from src.callbacks import get_top_org_table from src.callbacks import get_top_org_table
from src.figures import DataTable, make_column_picker, point_on_map from src.figures import DataTable, make_column_picker, point_on_map
from src.utils import ( from src.utils import (
add_canonical_link,
columns, columns,
df, df,
df_acheteurs, df_acheteurs,
@@ -34,12 +34,12 @@ from src.utils import (
def get_title(acheteur_id: str = None) -> str: def get_title(acheteur_id: str = None) -> str:
df_acheteur = df_acheteurs.filter(pl.col("acheteur_id") == acheteur_id).select( acheteur_nom = df_acheteurs.filter(pl.col("acheteur_id") == acheteur_id).select(
"acheteur_nom" "acheteur_nom"
) )
acheteur_nom = df_acheteur.item(0, 0) if acheteur_nom.height > 0:
return f"Marchés publics attribués par {acheteur_nom.item(0, 0)} | decp.info"
return f"Marchés publics attribués par {acheteur_nom} | decp.info" return "Marchés publics attribués | decp.info"
register_page( register_page(
@@ -353,7 +353,7 @@ def get_top_titulaires(data):
) )
def download_acheteur_data( def download_acheteur_data(
n_clicks, n_clicks,
data: [dict], data: list[dict[str, Any]],
acheteur_nom: str, acheteur_nom: str,
annee: str, annee: str,
): ):
@@ -476,8 +476,3 @@ def toggle_acheteur_columns(click_open, click_close, is_open):
) )
def reset_view(n_clicks): def reset_view(n_clicks):
return "", [] return "", []
@callback(Input("acheteur_url", "pathname"))
def cb_add_canonical_link(pathname):
add_canonical_link(pathname)
+1 -13
View File
@@ -22,7 +22,6 @@ from dash import (
from figures import make_column_picker from figures import make_column_picker
from src.figures import DataTable from src.figures import DataTable
from src.utils import ( from src.utils import (
add_canonical_link,
columns, columns,
df, df,
filter_table_data, filter_table_data,
@@ -327,7 +326,7 @@ def download_data(n_clicks, filter_query, sort_by, hidden_columns: list = None):
if filter_query: if filter_query:
lff = filter_table_data(lff, filter_query, "tab download") lff = filter_table_data(lff, filter_query, "tab download")
if len(sort_by) > 0: if sort_by and len(sort_by) > 0:
lff = sort_table_data(lff, sort_by) lff = sort_table_data(lff, sort_by)
def to_bytes(buffer): def to_bytes(buffer):
@@ -534,14 +533,3 @@ def toggle_tableau_columns(click_open, click_close, is_open):
) )
def reset_view(n_clicks): def reset_view(n_clicks):
return "", [] return "", []
@callback(Input("tableau_url", "pathname"))
def cb_add_canonical_link(pathname):
add_canonical_link(pathname)
# @callback(Input("tableau_url", "pathname"), Output("btn-copy-url", "children"))
# def cb_add_canonical_link(pathname):
# add_canonical_link(pathname)
#
+7 -12
View File
@@ -1,4 +1,5 @@
import datetime import datetime
from typing import Any
import dash_bootstrap_components as dbc import dash_bootstrap_components as dbc
import polars as pl import polars as pl
@@ -17,7 +18,6 @@ from dash import (
from src.callbacks import get_top_org_table from src.callbacks import get_top_org_table
from src.figures import DataTable, make_column_picker, point_on_map from src.figures import DataTable, make_column_picker, point_on_map
from src.utils import ( from src.utils import (
add_canonical_link,
columns, columns,
df, df,
df_titulaires, df_titulaires,
@@ -34,12 +34,12 @@ from src.utils import (
def get_title(titulaire_id: str = None) -> str: def get_title(titulaire_id: str = None) -> str:
titulaire_nom = ( titulaire_nom = df_titulaires.filter(pl.col("titulaire_id") == titulaire_id).select(
df_titulaires.filter(pl.col("titulaire_id") == titulaire_id) "titulaire_nom"
.select("titulaire_nom")
.item(0, 0)
) )
return f"Marchés publics remportés par {titulaire_nom} | decp.info" if titulaire_nom.height > 0:
return f"Marchés publics remportés par {titulaire_nom.item(0, 0)} | decp.info"
return "Marchés publics remportés | decp.info"
register_page( register_page(
@@ -367,7 +367,7 @@ def get_top_acheteurs(data):
) )
def download_titulaire_data( def download_titulaire_data(
n_clicks, n_clicks,
data: [dict], data: list[dict[str, Any]],
titulaire_nom: str, titulaire_nom: str,
annee: str, annee: str,
): ):
@@ -490,8 +490,3 @@ def toggle_titulaire_columns(click_open, click_close, is_open):
) )
def reset_view(n_clicks): def reset_view(n_clicks):
return "", [] return "", []
@callback(Input("titulaire_url", "pathname"))
def cb_add_canonical_link(pathname):
add_canonical_link(pathname)
+1 -10
View File
@@ -5,7 +5,6 @@ import uuid
from collections import OrderedDict from collections import OrderedDict
from time import localtime, sleep from time import localtime, sleep
import dash
import polars as pl import polars as pl
import polars.selectors as cs import polars.selectors as cs
from dash import no_update from dash import no_update
@@ -443,7 +442,7 @@ def get_default_hidden_columns(page):
"codeCPV", "codeCPV",
"dureeRestanteMois", "dureeRestanteMois",
] ]
elif page == "titulaire": elif page == "tableau":
displayed_columns = os.getenv("DISPLAYED_COLUMNS") displayed_columns = os.getenv("DISPLAYED_COLUMNS")
else: else:
displayed_columns = os.getenv("DISPLAYED_COLUMNS") displayed_columns = os.getenv("DISPLAYED_COLUMNS")
@@ -740,14 +739,6 @@ def make_org_jsonld(org_id, org_type, org_name=None, type_org_id="SIRET") -> dic
return jsonld return jsonld
def add_canonical_link(pathname):
@dash.hooks.index()
def update_index(html_string):
url = f"https://{domain_name}{pathname}"
canonical_tag = f'<link rel="canonical" href="{url}" />'
return html_string.replace("</head>", f"{canonical_tag}\n </head>")
df: pl.DataFrame = get_decp_data() df: pl.DataFrame = get_decp_data()
schema = df.collect_schema() schema = df.collect_schema()
+14 -1
View File
@@ -43,8 +43,21 @@ def test_data():
pl.DataFrame(data).write_parquet("tests/test.parquet") pl.DataFrame(data).write_parquet("tests/test.parquet")
yield path yield path
if os.path.exists(path):
os.unlink(path)
print(path, "deleted")
def pytest_setup_options(): def pytest_setup_options():
options = Options() options = Options()
options.add_argument("--window-size=1200,800") options.add_argument("--window-size=1200,1200 ")
options.add_experimental_option(
"prefs",
{
"download.default_directory": "/home/colin/git/decp.info",
"download.prompt_for_download": False,
"download.directory_upgrade": True,
"safebrowsing.enabled": True,
},
)
return options return options
+29
View File
@@ -1,3 +1,4 @@
import polars as pl
from dash.testing.composite import DashComposite from dash.testing.composite import DashComposite
from selenium.webdriver import Keys from selenium.webdriver import Keys
from selenium.webdriver.common.by import By from selenium.webdriver.common.by import By
@@ -58,3 +59,31 @@ def test_002_filter_persistence(dash_duo: DashComposite):
filter_input.send_keys(Keys.ENTER) filter_input.send_keys(Keys.ENTER)
filter_input = open_page_and_check_filter_input() filter_input = open_page_and_check_filter_input()
assert filter_input.get_attribute("value") == "11" assert filter_input.get_attribute("value") == "11"
def test_003_tableau_download(dash_duo: DashComposite):
from pages.acheteur import download_acheteur_data
from pages.tableau import download_data
from pages.titulaire import download_titulaire_data
from src.app import app
# Juste pour instancier l'app
print(app.server.name)
dicts = pl.read_parquet("tests/test.parquet").to_dicts()
outputs = [
download_data(1, "", [], None),
download_acheteur_data(1, dicts, "a1", "2025"),
download_titulaire_data(1, dicts, "t1", "2025"),
]
for output in outputs:
assert isinstance(output, dict)
for f in ["content", "filename", "type", "base64"]:
assert f in output
assert isinstance(output["content"], str) and len(output["content"]) > 100
assert isinstance(output["filename"], str) and output["filename"].startswith(
"decp_"
)
assert output["type"] is None
assert output["base64"] is True