Merge branch 'feature/mode_demploi_modal' into dev

This commit is contained in:
Colin Maudry
2026-01-30 21:14:03 +01:00
7 changed files with 116 additions and 83 deletions
+39 -6
View File
@@ -27,18 +27,50 @@ b {
font-weight: 600 !important;
}
h1,
h2,
h3,
h4,
h5 {
font-weight: 600;
}
h3 {
margin: 36px 0 20px 0;
}
/* Base Button Styles */
/* Base Button Styles
button {
font-weight: 400;
background-color: #fff;
border-radius: 3px;
appearance: auto;
border: solid var(--primary-color) 1px;
/* couleur thème foncée */
} */
button.btn.btn-primary,
button.show-hide {
display: block;
border-radius: 3px;
outline: 0;
color: #fff;
border: 0;
height: 30px;
padding-top: 2px;
background-image: linear-gradient(
rgb(209, 96, 73),
rgb(179, 56, 33) 26%,
rgb(159, 36, 22)
);
}
button.btn.btn-primary:hover,
button.show-hide:hover {
background-image: linear-gradient(
rgb(239, 126, 103),
rgb(209, 86, 63) 26%,
rgb(189, 66, 52)
);
}
button[disabled] {
@@ -160,12 +192,13 @@ p.version > a {
.table-menu {
font-size: 16px;
margin: 12px 0 12px 0;
height: 36px;
height: 50px;
display: flex;
align-items: baseline;
}
.table-menu > * {
margin: 8px 16px 8px 0;
float: left;
}
#source_table {
@@ -213,11 +246,11 @@ table.cell-table th {
font-family: "Inter", sans-serif;
text-align: left;
font-weight: 600;
padding-right: 12px;
padding: 2px 12px 4px 2px;
border: 1px solid rgb(179, 56, 33) !important;
background-color: rgb(179, 56, 33);
border-bottom: none !important;
height: 34px;
height: 32px;
}
.dash-table-container
+17 -38
View File
@@ -6,7 +6,7 @@ import plotly.graph_objects as go
import polars as pl
from dash import dash_table, dcc, html
from src.utils import format_number
from src.utils import data_schema, format_number
def get_map_count_marches(df: pl.DataFrame):
@@ -91,11 +91,8 @@ def get_yearly_statistics(statistics, today_str) -> html.Div:
page_size=10,
sort_action="none",
filter_action="none",
style_header={
"border": "solid 1px rgb(179, 56, 33)",
"backgroundColor": "rgb(179, 56, 33)",
"color": "white",
},
style_header={"fontFamily": "Inter", "fontSize": "16px"},
style_cell={"fontFamily": "Inter", "fontSize": "16px"},
)
return html.Div(children=table, className="marches_table")
@@ -210,11 +207,8 @@ def get_sources_tables(source_path) -> html.Div:
],
sort_action="native",
markdown_options={"html": True},
style_header={
"border": "solid 1px rgb(179, 56, 33)",
"backgroundColor": "rgb(179, 56, 33)",
"color": "white",
},
style_header={"fontFamily": "Inter", "fontSize": "16px"},
style_cell={"fontFamily": "Inter", "fontSize": "16px"},
)
return html.Div(children=datatable)
@@ -295,20 +289,18 @@ class DataTable(dash_table.DataTable):
"lineHeight": "18px",
"whiteSpace": "normal",
},
{
"if": {"column_id": "montant"},
"textAlign": "right",
},
{
"if": {"column_id": "dureeMois"},
"textAlign": "right",
},
{
"if": {"column_id": "titulaire_distance"},
"textAlign": "right",
},
]
for key in data_schema.keys():
field = data_schema[key]
if field["type"] in ["number", "integer"]:
rule = {
"if": {"column_id": field["name"]},
"textAlign": "right",
# "fontFamily": "Fira Code",
}
style_cell_conditional.append(rule)
# Initialisation de la classe parente avec les arguments
super().__init__(
id=dtid,
@@ -330,6 +322,8 @@ class DataTable(dash_table.DataTable):
style_cell_conditional=style_cell_conditional,
data_timestamp=0,
markdown_options={"html": True},
style_header={"fontFamily": "Inter", "fontSize": "16px"},
style_cell={"fontFamily": "Inter", "fontSize": "16px"},
tooltip_duration=8000,
tooltip_delay=350,
hidden_columns=hidden_columns,
@@ -360,13 +354,6 @@ def get_duplicate_matrix() -> html.Div:
Passez votre souris sur une case pour avoir les pourcentages exacts. À noter que ces statistiques sont produites avant le dédoublonnement qui a lieu avant la publication en Open Data et sur ce site.""")
# Assuming result_df is your DataFrame with structure:
# | sourceDataset | unique | dataset1 | dataset2 | dataset3 |
# |---------------|--------|----------|----------|----------|
# | dataset1 | 0.8 | | 0.15 | 0.2 |
# | dataset2 | 0.75 | 0.15 | | 0.12 |
# | dataset3 | 0.85 | 0.2 | 0.12 | |
# Extract data
z_data = result_df.select(pl.all().exclude("sourceDataset")).fill_null(0).to_numpy()
x_labels = result_df.columns[1:] # columns after "sourceDataset"
@@ -378,12 +365,6 @@ def get_duplicate_matrix() -> html.Div:
z=z_data,
x=x_labels,
y=y_labels,
# colorscale=[
# [0, "white"], # 0% → white
# [0.10, "lightblue"], # 1% → light blue (soft start)
# [0.50, "steelblue"], # 50% → medium blue
# [1, "darkblue"], # 100% → dark blue
# ],
colorscale=[
[0.0, "white"], # 0% → white
[0.10, "lightsalmon"], # 10% → light warm tone
@@ -391,8 +372,6 @@ def get_duplicate_matrix() -> html.Div:
],
zmin=0,
zmax=1,
# texttemplate="%{z:.0%}", # Format as percentage
# textfont={"size": 10, "color": "black"}, # Smaller font
hoverongaps=False,
showscale=True,
hovertemplate=(
+5 -3
View File
@@ -71,7 +71,7 @@ layout = [
className="org_year",
children=dcc.Dropdown(
id="acheteur_year",
options=["Toutes"]
options=["Toutes les années"]
+ [
str(year)
for year in range(
@@ -109,6 +109,7 @@ layout = [
html.Button(
"Téléchargement au format Excel",
id="btn-download-data-acheteur",
className="btn btn-primary",
),
dcc.Download(id="download-data-acheteur"),
],
@@ -136,6 +137,7 @@ layout = [
html.Button(
"Téléchargement désactivé au-delà de 65 000 lignes",
id="btn-download-filtered-data-acheteur",
className="btn btn-primary",
disabled=True,
),
dcc.Download(id="acheteur-download-filtered-data"),
@@ -229,7 +231,7 @@ def get_acheteur_marches_data(url, acheteur_year: str) -> tuple:
acheteur_siret = url.split("/")[-1]
lff = df.lazy()
lff = lff.filter(pl.col("acheteur_id") == acheteur_siret)
if acheteur_year and acheteur_year != "Toutes":
if acheteur_year and acheteur_year != "Toutes les années":
acheteur_year = int(acheteur_year)
lff = lff.filter(pl.col("dateNotification").dt.year() == acheteur_year)
lff = lff.sort(["dateNotification", "uid"], descending=True, nulls_last=True)
@@ -290,7 +292,7 @@ def download_acheteur_data(
def to_bytes(buffer):
df_to_download.write_excel(
buffer, worksheet="DECP" if annee in ["Toutes", None] else annee
buffer, worksheet="DECP" if annee in ["Toutes les années", None] else annee
)
date = datetime.datetime.now().strftime("%Y-%m-%d_%H:%M:%S")
+5 -5
View File
@@ -46,20 +46,20 @@ layout = html.Div(
"width": "500px",
"border": "1px solid #ccc",
"borderRight": "none",
"borderRadius": "4px 0 0 4px",
"borderRadius": "3px 0 0 3px",
"padding": "5px 10px",
"outline": "none",
"height": "34px",
},
),
html.Button(
"🔍",
"=>",
id="search-button",
className="btn btn-primary",
style={
"border": "1px solid #ccc",
"borderRadius": "0 4px 4px 0",
"borderRadius": "0 3px 3px 0",
"marginLeft": "0",
"backgroundColor": "#f0f0f0",
"cursor": "pointer",
"height": "auto", # Ensure it matches input height if necessary, often relying on padding/line-height
},
),
+40 -15
View File
@@ -4,6 +4,7 @@ import urllib.parse
import uuid
from datetime import datetime
import dash_bootstrap_components as dbc
import polars as pl
from dash import (
ClientsideFunction,
@@ -125,14 +126,15 @@ layout = [
style={"maxWidth": "1000px"},
),
html.Div(
html.Details(
children=[
html.Summary(
html.H4("Mode d'emploi", style={"textDecoration": "underline"}),
),
dcc.Markdown(
dangerously_allow_html=True,
children=f"""
[
dbc.Button("Mode d'emploi", id="tableau_help_open"),
dbc.Modal(
[
dbc.ModalHeader(dbc.ModalTitle("Header")),
dbc.ModalBody(
dcc.Markdown(
dangerously_allow_html=True,
children=f"""
##### Définition des colonnes
Pour voir la définition d'une colonne, passez votre souris sur son en-tête.
@@ -144,11 +146,9 @@ layout = [
- Champs textuels : la recherche retourne les valeurs qui contiennent le texte recherché et n'est pas sensible à la casse (majuscules/minuscules).
- Exemple : `rennes` retourne "RENNES METROPOLE".
- Les guillemets simples (apostrophe du 4) doivent être prédédées d'une barre oblique (AltGr + 8). Exemple : `services d\\\'assurances`
- Lorsque vous ouvrez une URL de vue (voir "Partager une vue" plus bas), le format équivalent `icontains rennes` est utilisé. Mais dans vos filtres pas besoin de taper `icontains` !
- Champs numériques (Durée en mois, Montant, ...) : vous pouvez...
- soit taper un nombre pour trouver les valeurs strictement égales. Exemple : `12` ne retourne que des 12
- soit le précéder de **>** ou **<** pour filtrer les valeurs supérieures ou inférieures. Exemple pour les offres reçues : `> 4` retourne les marchés ayant reçu plus de 4 offres.
- lorsque vous ouvrez une URL de vue (voir "Partager une vue" plus bas), le format équivalent `i<` ou `i>` est utilisé, mais c'est un bug : vous n'avez pas besoin de taper le `i` pour appliquer ce filtre.
- Champs date (Date de notification, ...) : vous pouvez également utiliser **>** ou **<**. Exemples :
- `< 2024-01-31` pour "avant le 31 janvier 2024"
- `2024` pour "en 2024", `> 2022` pour "à partir de 2022".
@@ -188,10 +188,24 @@ layout = [
(informations, marchés attribués/remportés, etc.)
""",
),
],
id="instructions",
),
),
),
dbc.ModalFooter(
dbc.Button(
"Close",
id="tableau_help_close",
className="ms-auto",
n_clicks=0,
)
),
],
id="tableau_help",
is_open=False,
fullscreen="md-down",
scrollable=True,
size="lg",
),
],
id="header",
),
# html.Div(
@@ -210,7 +224,7 @@ layout = [
html.P("lignes", id="nb_rows"),
html.Div(id="copy-container"),
dcc.Input(id="share-url", readOnly=True, style={"display": "none"}),
html.Button(
dbc.Button(
"Téléchargement désactivé au-delà de 65 000 lignes",
id="btn-download-data",
disabled=True,
@@ -389,3 +403,14 @@ def show_confirmation(n_clicks):
style={"color": "green", "fontWeight": "bold", "marginLeft": "10px"},
)
return no_update
@callback(
Output("tableau_help", "is_open"),
[Input("tableau_help_open", "n_clicks"), Input("tableau_help_close", "n_clicks")],
[State("tableau_help", "is_open")],
)
def toggle_tableau_help(click_open, click_close, is_open):
if click_open or click_close:
return not is_open
return is_open
+3 -3
View File
@@ -71,7 +71,7 @@ layout = [
className="org_year",
children=dcc.Dropdown(
id="titulaire_year",
options=["Toutes"]
options=["Toutes les années"]
+ [
str(year)
for year in range(
@@ -235,7 +235,7 @@ def get_titulaire_marches_data(url, titulaire_year: str) -> tuple:
(pl.col("titulaire_id") == titulaire_siret)
& (pl.col("titulaire_typeIdentifiant") == "SIRET")
)
if titulaire_year and titulaire_year != "Toutes":
if titulaire_year and titulaire_year != "Toutes les années":
lff = lff.filter(
pl.col("dateNotification").cast(pl.String).str.starts_with(titulaire_year)
)
@@ -305,7 +305,7 @@ def download_titulaire_data(
def to_bytes(buffer):
df_to_download.write_excel(
buffer, worksheet="DECP" if annee in ["Toutes", None] else annee
buffer, worksheet="DECP" if annee in ["Toutes les années", None] else annee
)
date = datetime.datetime.now().strftime("%Y-%m-%d_%H:%M:%S")
+7 -13
View File
@@ -284,7 +284,7 @@ def filter_table_data(
lff: pl.LazyFrame, filter_query: str, filter_source: str
) -> pl.LazyFrame:
_schema = lff.collect_schema()
track_search(f"{filter_source}: {filter_query}")
track_search(filter_query, filter_source)
filtering_expressions = filter_query.split(" && ")
for filter_part in filtering_expressions:
col_name, operator, filter_value = split_filter_part(filter_part)
@@ -464,21 +464,15 @@ def get_data_schema() -> dict:
return new_schema
def track_search(query):
if (
len(query) >= 4
and os.getenv("DEVELOPMENT").lower != "true"
and os.getenv("MATOMO_DOMAIN")
):
if os.getenv("DEVELOPMENT").lower() == "true":
url = "https://test.decp.info"
else:
url = "https://decp.info"
def track_search(query, category):
if len(query) >= 4 and not development and os.getenv("MATOMO_DOMAIN"):
url = "https://decp.info"
params = {
"idsite": os.getenv("MATOMO_ID_SITE"),
"url": url,
"rec": "1",
"action_name": "front_page_search",
"action_name": "search" if category == "home_page_search" else "filter",
"search_cat": category,
"rand": uuid.uuid4().hex,
"apiv": "1",
"h": localtime().tm_hour,
@@ -506,7 +500,7 @@ def search_org(dff: pl.DataFrame, query: str, org_type: str) -> pl.DataFrame:
return dff.select(pl.lit(False).alias("matches"))
# Enregistrement des recherche dans Matomo
track_search(query)
track_search(query, "home_page_search")
# Normalize query
normalized_query = unidecode(query.strip()).upper()