Merge branch 'main' into feature/28_vue_acheteur
This commit is contained in:
@@ -19,7 +19,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
environment: ${{ github.ref_name }}
|
||||
steps:
|
||||
- name: Checkout repositorypu
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up SSH key
|
||||
@@ -40,4 +40,4 @@ jobs:
|
||||
key: ${{ secrets.ARTIFACT_SSH_KEY }}
|
||||
passphrase: ${{ secrets.SSH_PSWD }}
|
||||
command_timeout: 5m
|
||||
script: ${{ secrets.APP_PATH }}/deploy.sh
|
||||
script: ${{ secrets.APP_PATH }}/deploy.sh ${{ env.APP_NAME }}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
DATA_FILE_PARQUET_PATH=https://www.data.gouv.fr/fr/datasets/r/11cea8e8-df3e-4ed1-932b-781e2635e432
|
||||
PORT=8050
|
||||
DEVELOPMENT=True
|
||||
SOURCE_STATS_CSV_PATH="https://www.data.gouv.fr/api/1/datasets/r/8ded94de-3b80-4840-a5bb-7faad1c9c234"
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
decp.info is a web application that enable analysis and download of
|
||||
French public procurement data.
|
||||
Copyright (C) 2025 Colin Maudry
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
@@ -1,5 +1,7 @@
|
||||
# decp.info
|
||||
|
||||
> v2.0.1
|
||||
|
||||
Outil d'exploration et de téléchargement des données essentielles de la commande publique.
|
||||
|
||||
=> [decp.info](https://decp.info)
|
||||
@@ -10,16 +12,44 @@ Outil d'exploration et de téléchargement des données essentielles de la comma
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install .
|
||||
|
||||
# Copie et personnalisation du .env
|
||||
cp template.env .env
|
||||
nano .env
|
||||
|
||||
# Pour la production
|
||||
gunicorn app:server
|
||||
|
||||
# Pour avoir le debuggage et le hot reload
|
||||
python run.py
|
||||
```
|
||||
## Dépôts de code connexes
|
||||
|
||||
## Déploiement
|
||||
|
||||
- **Production** (branche `main`, [decp.info](https://decp.info)) : déploiement manuel via un déclenchement de la Github Action [Déploiement](https://github.com/ColinMaudry/decp.info/actions/workflows/deploy.yaml).
|
||||
- **Test** (branche `dev`, [test.decp.info](https://test.decp.info)) : déploiement automatique à chaque push sur la branche `dev`, via la même Github Action.
|
||||
|
||||
## Liens connexes
|
||||
|
||||
- [decp-processing](https://github.com/ColinMaudry/decp-processing) (traitement et publication des données)
|
||||
- [decp-table-schema](https://github.com/ColinMaudry/decp-table-schema) (schéma de données tabulaire)
|
||||
- [colin.maudry.com](https://colin.maudry.com) (blog)
|
||||
|
||||
## Notes de version
|
||||
|
||||
### 2.0.0-alpha
|
||||
#### 2.0.1 (23 septembre 2025)
|
||||
|
||||
- Bloquage du bouton de téléchargement si trop de lignes (+ 65000) [#38](https://github.com/ColinMaudry/decp.info/issues/38)
|
||||
- Amélioration du script de déploiement (deploy.sh)
|
||||
- Meilleures instructions d'installation et lancement
|
||||
- Coquilles 🐚
|
||||
|
||||
### 2.0.0 (23 septembre 2025)
|
||||
|
||||
- détails des sources de données
|
||||
- section "À propos" plus développée
|
||||
- correction de bugs dans les filtres de la data table
|
||||
|
||||
#### 2.0.0-alpha
|
||||
|
||||
- Data table fonctionnelle
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
#!bin/bash
|
||||
|
||||
# Utilisé principalement avec les Github Actions
|
||||
# cf. .github/workflows/deploy.yaml
|
||||
|
||||
appname = "$1"
|
||||
|
||||
systemctl stop $appname
|
||||
cd /var/www/$appname
|
||||
git pull
|
||||
source .venv/bin/activate
|
||||
pip install .
|
||||
deactivate
|
||||
chown -R $appname:www-data *
|
||||
systemctl start $appname
|
||||
+2
-1
@@ -1,13 +1,14 @@
|
||||
[project]
|
||||
name = "decp.info"
|
||||
description = ""
|
||||
version = "2.0.0"
|
||||
version = "2.0.1"
|
||||
requires-python = ">= 3.10"
|
||||
authors = [
|
||||
{ name = "Colin Maudry", email = "colin+decp@maudry.com" }
|
||||
]
|
||||
dependencies = [
|
||||
"dash==3.2.0",
|
||||
"dash[compress]",
|
||||
"polars",
|
||||
"gunicorn",
|
||||
"dash-bootstrap-components",
|
||||
|
||||
+14
-1
@@ -2,13 +2,26 @@ import logging
|
||||
|
||||
import dash_bootstrap_components as dbc
|
||||
from dash import Dash, dcc, html, page_container, page_registry
|
||||
from flask import send_from_directory
|
||||
|
||||
app = Dash(external_stylesheets=[dbc.themes.SIMPLEX], title="decp.info", use_pages=True)
|
||||
app = Dash(
|
||||
external_stylesheets=[dbc.themes.SIMPLEX],
|
||||
title="decp.info",
|
||||
use_pages=True,
|
||||
compress=True,
|
||||
)
|
||||
# COSMO (belle font, blue),
|
||||
# UNITED (rouge, ubuntu font),
|
||||
# LUMEN (gros séparateur, blue clair),
|
||||
# SIMPLEX (rouge, séparateur)
|
||||
|
||||
|
||||
# robots.txt
|
||||
@app.server.route("/robots.txt")
|
||||
def robots():
|
||||
return send_from_directory("./assets", "robots.txt", mimetype="text/plain")
|
||||
|
||||
|
||||
logger = logging.getLogger("decp.info")
|
||||
logging.basicConfig(
|
||||
format="%(asctime)s %(levelname)-8s %(message)s",
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
# START YOAST BLOCK
|
||||
# Copié depuis https://next.ink/robots.txt
|
||||
# ---------------------------
|
||||
User-agent: *
|
||||
Allow: /
|
||||
|
||||
# ---------------------------
|
||||
# END YOAST BLOCK
|
||||
|
||||
User-agent: GPTBot
|
||||
Disallow: /
|
||||
User-agent: ChatGPT-User
|
||||
Disallow: /
|
||||
User-agent: Google-Extended
|
||||
Disallow: /
|
||||
User-agent: PerplexityBot
|
||||
Disallow: /
|
||||
User-agent: Amazonbot
|
||||
Disallow: /
|
||||
User-agent: ClaudeBot
|
||||
Disallow: /
|
||||
User-Agent: FacebookBot
|
||||
Disallow: /
|
||||
User-Agent: Applebot
|
||||
Disallow: /
|
||||
User-agent: anthropic-ai
|
||||
Disallow: /
|
||||
User-agent: Bytespider
|
||||
Disallow: /
|
||||
User-agent: Claude-Web
|
||||
Disallow: /
|
||||
User-agent: Diffbot
|
||||
Disallow: /
|
||||
User-agent: ImagesiftBot
|
||||
Disallow: /
|
||||
User-agent: Omgilibot
|
||||
Disallow: /
|
||||
User-agent: Omgili
|
||||
Disallow: /
|
||||
User-agent: YouBot
|
||||
Disallow: /
|
||||
@@ -10,6 +10,14 @@
|
||||
float: left;
|
||||
}
|
||||
|
||||
#source_table p {
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
#source_table {
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
/* Réduire la taille du texte de la colonne Objet */
|
||||
|
||||
td[data-dash-column="objet"] {
|
||||
|
||||
+82
-15
@@ -2,15 +2,16 @@ import json
|
||||
|
||||
import plotly.express as px
|
||||
import polars as pl
|
||||
from dash import dash_table, html
|
||||
|
||||
|
||||
def get_map_count_marches(lf):
|
||||
def get_map_count_marches(lf: pl.LazyFrame):
|
||||
lf = lf.with_columns(
|
||||
pl.col("lieuExecution_code").str.head(2).str.zfill(2).alias("Département")
|
||||
)
|
||||
lf = (
|
||||
lf.unique(subset="uid")
|
||||
.select(["uid", "Département"])
|
||||
lf.select(["uid", "Département"])
|
||||
.drop_nulls()
|
||||
.unique(subset="uid")
|
||||
.group_by("Département")
|
||||
.len("uid")
|
||||
@@ -59,34 +60,100 @@ def get_barchart_sources(lf: pl.LazyFrame, type_date: str):
|
||||
"datePublicationDonnees": "publication des données",
|
||||
}
|
||||
|
||||
lf = lf.select("uid", type_date, "source")
|
||||
lf = lf.select("uid", type_date, "sourceDataset")
|
||||
|
||||
lf = lf.unique("uid")
|
||||
|
||||
# Rassemblement des datasets Atexo pour ne pas surcharger le graphique
|
||||
lf = lf.with_columns(
|
||||
pl.when(pl.col("sourceDataset").str.starts_with("atexo"))
|
||||
.then(pl.lit("plateformes atexo"))
|
||||
.otherwise(pl.col("sourceDataset"))
|
||||
.alias("sourceDataset")
|
||||
)
|
||||
|
||||
# Rassemblement des datasets AWS pour ne pas surcharger le graphique
|
||||
lf = lf.with_columns(
|
||||
pl.when(pl.col("sourceDataset").str.contains(r"aws|marches\-publics.info"))
|
||||
.then(pl.lit("aws"))
|
||||
.otherwise(pl.col("sourceDataset"))
|
||||
.alias("sourceDataset")
|
||||
)
|
||||
|
||||
lf = lf.with_columns(pl.col(type_date).dt.year().alias("annee"))
|
||||
lf = lf.filter(
|
||||
pl.col(type_date).is_not_null() & pl.col("annee").is_between(2018, 2025)
|
||||
pl.col(type_date).is_not_null() & pl.col("annee").is_between(2019, 2025)
|
||||
)
|
||||
lf = lf.sort(by=[type_date, "source"], descending=False)
|
||||
lf = lf.with_columns(pl.col(type_date).cast(pl.String).str.head(7))
|
||||
|
||||
lf = lf.group_by([type_date, "source"]).len()
|
||||
lf = lf.with_columns(
|
||||
pl.when(pl.col("source").is_null()).then(
|
||||
pl.lit("Source inconnue").alias("source")
|
||||
)
|
||||
lf = (
|
||||
lf.group_by([type_date, "sourceDataset"])
|
||||
.len()
|
||||
.sort(by=[type_date, "len"], descending=True)
|
||||
)
|
||||
lf = lf.sort(by=[type_date, "source"], descending=False)
|
||||
|
||||
# lf = lf.with_columns(
|
||||
# pl.when(pl.col("sourceDataset").is_null()).then(
|
||||
# pl.lit("Source inconnue")).alias("sourceDataset")
|
||||
# )
|
||||
|
||||
lf = lf.sort(by=["sourceDataset"], descending=False)
|
||||
df: pl.DataFrame = lf.collect()
|
||||
|
||||
fig = px.bar(
|
||||
df,
|
||||
x=type_date,
|
||||
y="len",
|
||||
color="source",
|
||||
color="sourceDataset",
|
||||
title=f"Nombre de marchés attribués par date de {labels[type_date]} et source de données",
|
||||
labels={
|
||||
"len": "Nombre de marchés",
|
||||
type_date: f"Mois de {labels[type_date]}",
|
||||
"source": "Source de données",
|
||||
"sourceDataset": "Source de données",
|
||||
},
|
||||
)
|
||||
|
||||
return fig
|
||||
|
||||
|
||||
def get_sources_tables(source_path) -> html.Div:
|
||||
df = pl.read_csv(source_path)
|
||||
df = df.with_columns(
|
||||
(
|
||||
pl.lit('<a href = "')
|
||||
+ pl.col("url")
|
||||
+ pl.lit('">')
|
||||
+ pl.col("nom")
|
||||
+ pl.lit("</a>")
|
||||
).alias("nom")
|
||||
)
|
||||
df = df.drop("url")
|
||||
df = df.sort(by=["nb_marchés"], descending=True)
|
||||
|
||||
datatable = dash_table.DataTable(
|
||||
id="source_table",
|
||||
columns=[
|
||||
{
|
||||
"name": i,
|
||||
"id": i,
|
||||
"presentation": "markdown",
|
||||
"type": "text",
|
||||
"format": {"nully": "N/A"},
|
||||
}
|
||||
for i in df.schema.names()
|
||||
],
|
||||
style_cell_conditional=[
|
||||
{
|
||||
"if": {"column_id": ["nom", "organisation"]},
|
||||
"minWidth": "350px",
|
||||
"textAlign": "left",
|
||||
"overflow": "hidden",
|
||||
"lineHeight": "14px",
|
||||
"whiteSpace": "normal",
|
||||
},
|
||||
],
|
||||
sort_action="native",
|
||||
markdown_options={"html": True},
|
||||
)
|
||||
datatable.data = df.to_dicts()
|
||||
|
||||
return html.Div(children=datatable)
|
||||
|
||||
+62
-10
@@ -1,7 +1,14 @@
|
||||
import os
|
||||
|
||||
from dash import dcc, html, register_page
|
||||
from dotenv import load_dotenv
|
||||
|
||||
from src.figures import get_sources_tables
|
||||
|
||||
title = "À propos"
|
||||
|
||||
load_dotenv()
|
||||
|
||||
register_page(
|
||||
__name__, path="/a-propos", title=f"decp.info - {title}", name=title, order=5
|
||||
)
|
||||
@@ -12,7 +19,7 @@ layout = [
|
||||
children=[
|
||||
html.H2(title),
|
||||
dcc.Markdown(
|
||||
"""Outil d'exploration des [Données Essentielles de la Commande Publique](), développé par Colin Maudry, sous licence GPL v3 (libre et gratuit).
|
||||
"""Outil d'exploration libre et gratuit des données de marchés publics, développé par Colin Maudry.
|
||||
|
||||
Ce projet vise à démocratiser l'accès aux données des marchés publics et à un outil performant et gratuit. Si vous le trouvez utile
|
||||
j'aimerais beaucoup échanger avec vous pour comprendre vos cas d'usages et vos besoins. Cet outil ne peut rester performant que si je comprends les problèmes qu'il peut aider à résoudre. Ce projet ne peut rester gratuit que grâce au financement du développement de nouvelles fonctionnalités.
|
||||
@@ -21,28 +28,73 @@ En effet, le potentiel des données d'attribution de marchés et des données qu
|
||||
les fonctionnalités actuelles de decp.info. Il est ainsi possible de rajouter
|
||||
|
||||
- de nombreuses visualisations de données (cartes, graphiques, tableaux) sur des thématiques variées (vivacité de la concurrence, secteurs d'activité, insertion par l'activité économique (IAE), distance acheteur-fournisseur...)
|
||||
- la sauvegarde de filtre pour les retrouver plus tard et les partager
|
||||
- la sauvegarde de filtres pour les retrouver plus tard et les partager
|
||||
- des alertes par email si des marchés correspondant à certains critères
|
||||
- le développement d'une API pour alimenter d'autres logiciels
|
||||
- ...et toutes les fonctionnalités auxquelles vous pourrez penser :)
|
||||
- ...et toutes les fonctionnalités auxquelles vous pourrez penser
|
||||
"""
|
||||
),
|
||||
html.H4("Pour contribuer", id="contribuer"),
|
||||
dcc.Markdown("""
|
||||
- via l'achat d'une prestation de service (devis, prestation, facture), vous pouvez financer le développement de [fonctionnalités prévues](https://github.com/ColinMaudry/decp.info/issues), ou d'autres !
|
||||
- ma société accepte aussi les dons (pas de réduction d'impôt possible)
|
||||
- envoyez un mail et on discute !
|
||||
|
||||
#### Pour aller plus loin
|
||||
#### Pour explorer le projet
|
||||
|
||||
- ✉️ [inscription à la liste de diffusion](https://6254d9a3.sibforms.com/serve/MUIFAEonUVkoSVrdgey18CTgLyI16xw4yeu-M-YOUzhWE_AgfQfbgkyT7GvA_RYLro9MfuRqkzQxSvu7-uzbMSv2a2ZQPsliM7wtiiqIL8kR2zOvl6m11fb5qjcOxMAYsLiY_YBi3P7NY95CTJ8vRY4CpsDclF2iLooOElKkTgIgi5nePe7zAIrgiYM5v2EuALlGJZMEG9vBP-Cu) (annonces des mises à jour et évènements, maximum une fois par mois)
|
||||
- 💾 [données consolidées en Open Data](https://www.data.gouv.fr/datasets/donnees-essentielles-de-la-commande-publique-consolidees-format-tabulaire/)
|
||||
- 🗞️ [mon blog](https://colin.maudry.com), qui parle beaucoup de transparence des marchés publics
|
||||
- 📔 [wiki du projet](https://github.com/ColinMaudry/decp-processing/wiki)
|
||||
- 🚰 code source
|
||||
- [de decp.info](https://github.com/ColinMaudry/decp.info)
|
||||
- [du traitement des données](https://github.com/ColinMaudry/decp-processing)
|
||||
|
||||
#### Contact
|
||||
|
||||
- venez discuter de la transparence de la commande publique [sur le forum teamopendata.org](https://teamopendata.org/c/commande-publique/101)
|
||||
"""),
|
||||
html.H4("Contact", id="contact"),
|
||||
dcc.Markdown("""
|
||||
- Email : [colin+decp@maudry.com](mailto:colin+decp@maudry.com)
|
||||
- Bluesky : [@col1m.bsky.social](https://bsky.app/profile/col1m.bsky.social)
|
||||
- Mastodon : [col1m@mamot.fr](https://mamot.fr/@col1m)
|
||||
- LinkedIn : [colinmaudry](https://www.linkedin.com/in/colinmaudry/)
|
||||
"""
|
||||
),
|
||||
- venez discuter de la transparence de la commande publique [sur le forum teamopendata.org](https://teamopendata.org/c/commande-publique/101)
|
||||
"""),
|
||||
html.H4("Sources de données", id="sources"),
|
||||
get_sources_tables(os.getenv("SOURCE_STATS_CSV_PATH")),
|
||||
html.H4("Mentions légales", id="mentions-legales"),
|
||||
dcc.Markdown("""
|
||||
##### Publication
|
||||
|
||||
Site Web développé et édité par [SAS Colmo](https://annuaire-entreprises.data.gouv.fr/entreprise/colmo-989393350), 989 393 350 RCS Rennes au capital de 3 000 euros.
|
||||
|
||||
Siège social : 1 carrefour Jouaust, 35000 Rennes
|
||||
|
||||
Hébergement : serveur situé en France et administré par Scaleway, 8 rue de la Ville l’Evêque, 75008 Paris
|
||||
|
||||
##### Suivi d'audience
|
||||
|
||||
Ce site dépose un petit fichier texte (un « cookie ») sur votre ordinateur lorsque vous le consultez ([Wikipédia](https://fr.wikipedia.org/wiki/Cookie_(informatique))). Cela me permet de mesurer le nombre de visites, de distinguer les nouveaux visiteurs des utilisateurs réguliers et ainsi de communiquer sur l'impact de decp.info.
|
||||
|
||||
**Ce site n’affiche pas de bannière de consentement aux cookies, pourquoi ?**
|
||||
|
||||
C’est vrai, vous n’avez pas eu à cliquer sur un bloc qui recouvre la moitié de la page pour dire que vous êtes d’accord avec le dépôt de cookies.
|
||||
|
||||
Rien d’exceptionnel, je respecte simplement la loi, qui dit que certains outils de suivi d’audience, correctement configurés pour respecter la vie privée, sont exemptés d’autorisation préalable.
|
||||
|
||||
J’utilise pour cela [Matomo](https://matomo.org/), un outil [libre](https://matomo.org/free-software/), paramétré pour être en conformité avec [la recommandation « Cookies »](https://www.cnil.fr/fr/solutions-pour-les-cookies-de-mesure-daudience) de la CNIL. Cela signifie que votre adresse IP, par exemple, est anonymisée avant d’être enregistrée. Il m’est donc impossible d’associer vos visites sur ce site à votre personne."""),
|
||||
# Matomo propose cependant ce formulaire si vous souhaitez totalement désactiver le suivi de vos sessions sur ce site :"""),
|
||||
# html.Div(
|
||||
# id="matomo-opt-out",
|
||||
# style={
|
||||
# "border": "1pt solid lightgrey",
|
||||
# "padding": "12px",
|
||||
# "margin": "auto 0 auto 12px",
|
||||
# "width": "80%",
|
||||
# },
|
||||
# children=["Vous utilisez un bloqueur de suivi de trafic."],
|
||||
# ),
|
||||
# html.Script(
|
||||
# src="https://analytics.maudry.com/index.php?module=CoreAdminHome&action=optOutJS&divId=matomo-opt-out&language=auto&showIntro=1"
|
||||
# ),
|
||||
],
|
||||
)
|
||||
]
|
||||
|
||||
+41
-8
@@ -24,8 +24,6 @@ df_filtered = pl.DataFrame()
|
||||
# Suppression des colonnes inutiles
|
||||
lf = lf.drop(
|
||||
[
|
||||
"titulaire_siren",
|
||||
"acheteur_siren",
|
||||
"donneesActuelles",
|
||||
]
|
||||
)
|
||||
@@ -81,6 +79,14 @@ datatable = dash_table.DataTable(
|
||||
"lineHeight": "14px",
|
||||
"whiteSpace": "normal",
|
||||
},
|
||||
{
|
||||
"if": {"column_id": "acheteur_nom"},
|
||||
"minWidth": "250px",
|
||||
"textAlign": "left",
|
||||
"overflow": "hidden",
|
||||
"lineHeight": "14px",
|
||||
"whiteSpace": "normal",
|
||||
},
|
||||
],
|
||||
data_timestamp=0,
|
||||
markdown_options={"html": True},
|
||||
@@ -90,7 +96,9 @@ layout = [
|
||||
html.Div(
|
||||
html.Details(
|
||||
children=[
|
||||
html.Summary(html.H3("Mode d'emploi")),
|
||||
html.Summary(
|
||||
html.H3("Mode d'emploi", style={"text-decoration": "underline"}),
|
||||
),
|
||||
dcc.Markdown(
|
||||
"""
|
||||
|
||||
@@ -129,7 +137,11 @@ layout = [
|
||||
html.Div(
|
||||
[
|
||||
html.P("lignes", id="nb_rows"),
|
||||
html.Button("Télécharger au format Excel", id="btn-download-data"),
|
||||
html.Button(
|
||||
"Téléchargement désactivé au-delà de 65 000 lignes",
|
||||
id="btn-download-data",
|
||||
disabled=True,
|
||||
),
|
||||
dcc.Download(id="download-data"),
|
||||
html.P("Données mises à jour le " + str(update_date)),
|
||||
],
|
||||
@@ -145,6 +157,9 @@ layout = [
|
||||
Output("table", "data"),
|
||||
Output("table", "data_timestamp"),
|
||||
Output("nb_rows", "children"),
|
||||
Output("btn-download-data", "disabled"),
|
||||
Output("btn-download-data", "children"),
|
||||
Output("btn-download-data", "title"),
|
||||
Input("table", "page_current"),
|
||||
Input("table", "page_size"),
|
||||
Input("table", "filter_query"),
|
||||
@@ -203,7 +218,9 @@ def update_table(page_current, page_size, filter_query, sort_by, data_timestamp)
|
||||
|
||||
df_filtered = dff.clone()
|
||||
|
||||
nb_rows = f"{format_number(dff.height)} lignes"
|
||||
height = dff.height
|
||||
|
||||
nb_rows = f"{format_number(height)} lignes"
|
||||
|
||||
# Pagination des données
|
||||
start_row = page_current * page_size
|
||||
@@ -211,7 +228,23 @@ def update_table(page_current, page_size, filter_query, sort_by, data_timestamp)
|
||||
dff = dff.slice(start_row, page_size)
|
||||
dicts = dff.to_dicts()
|
||||
|
||||
return dicts, data_timestamp + 1, nb_rows
|
||||
if height > 65000:
|
||||
download_disabled = True
|
||||
download_text = "Téléchargement désactivé au-delà de 65 000 lignes"
|
||||
download_title = "Excel ne supporte pas d'avoir plus de 65 000 URLs dans une même feuille de calcul. Contactez-moi pour me présenter votre besoin en téléchargement afin que je puisse adapter la solution."
|
||||
else:
|
||||
download_disabled = False
|
||||
download_text = "Télécharger au format Excel"
|
||||
download_title = ""
|
||||
|
||||
return (
|
||||
dicts,
|
||||
data_timestamp + 1,
|
||||
nb_rows,
|
||||
download_disabled,
|
||||
download_text,
|
||||
download_title,
|
||||
)
|
||||
|
||||
|
||||
@callback(
|
||||
@@ -227,8 +260,8 @@ def download_data(n_clicks, hidden_columns: list = None):
|
||||
|
||||
# Rétablissement des colonnes source et sourceOpenData (voir add_resource_link)
|
||||
df_to_download = df_to_download.with_columns(
|
||||
pl.col("source").str.extract(r'href="(.*?)"').alias("sourceOpenData"),
|
||||
pl.col("source").str.extract(r'">(.*?)<').alias("source"),
|
||||
pl.col("source").str.extract(r'href="(.*?)"').alias("sourceFile"),
|
||||
pl.col("source").str.extract(r'">(.*?)<').alias("sourceDataset"),
|
||||
)
|
||||
|
||||
# Les colonnes masquées sont supprimées
|
||||
|
||||
+21
-31
@@ -15,43 +15,33 @@ layout = [
|
||||
className="container",
|
||||
children=[
|
||||
html.H2(title),
|
||||
dcc.Markdown("""
|
||||
À savoir, les données suivantes existent mais sont en cours d'intégration dans ce projet :
|
||||
|
||||
- les données publiées dans le [format DECP 2019 (période 2018-2022)](https://www.data.gouv.fr/fr/datasets/donnees-essentielles-de-la-commande-publique-fichiers-consolides/#/resources/16962018-5c31-4296-9454-5998585496d2)
|
||||
- les données [collectées par l'AIFE](https://github.com/ColinMaudry/decp-processing/issues/68) (API DUME, notamment achatpublic.info)
|
||||
- les données [des plateformes Atexo](https://github.com/ColinMaudry/decp-processing/issues/57)
|
||||
"""),
|
||||
dcc.Loading(
|
||||
overlay_style={"visibility": "visible", "filter": "blur(2px)"},
|
||||
id="loading-1",
|
||||
id="loading-statistques",
|
||||
type="default",
|
||||
children=[
|
||||
html.Div(
|
||||
children=[
|
||||
dcc.Loading(
|
||||
overlay_style={
|
||||
"visibility": "visible",
|
||||
"filter": "blur(2px)",
|
||||
},
|
||||
id="loading-stats",
|
||||
type="default",
|
||||
children=[
|
||||
dcc.Graph(figure=get_map_count_marches(lf)),
|
||||
dcc.Graph(
|
||||
figure=get_barchart_sources(
|
||||
lf, "dateNotification"
|
||||
)
|
||||
),
|
||||
dcc.Graph(
|
||||
figure=get_barchart_sources(
|
||||
lf, "datePublicationDonnees"
|
||||
)
|
||||
),
|
||||
],
|
||||
)
|
||||
]
|
||||
),
|
||||
dcc.Markdown("""
|
||||
La publication de données essentielles de marchés publics (DECP) est souvent effectuée par
|
||||
les plateformes de marchés publics (profils d'acheteurs). Cependant, certaines plateformes ne publient pas,
|
||||
ou publient d'une manière qui rend la récupération des données compliquée. Les données présentées sur ce site
|
||||
ne représentent donc pas tous les marchés attribués en France, seulement une partie significative.
|
||||
|
||||
L'ajout de nouvelles plateformes [est en cours](https://github.com/ColinMaudry/decp-processing/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22source%20de%20donn%C3%A9es%22),
|
||||
toutes les [contributions](/a-propos#contribuer) sont les bienvenues pour atteindre l'exhaustivité.
|
||||
"""),
|
||||
dcc.Graph(figure=get_map_count_marches(lf)),
|
||||
dcc.Graph(
|
||||
figure=get_barchart_sources(lf, "dateNotification")
|
||||
),
|
||||
dcc.Graph(
|
||||
figure=get_barchart_sources(
|
||||
lf, "datePublicationDonnees"
|
||||
)
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
|
||||
+4
-6
@@ -44,10 +44,10 @@ def split_filter_part(filter_part):
|
||||
def add_resource_link(lff: pl.LazyFrame) -> pl.LazyFrame:
|
||||
lff = lff.with_columns(
|
||||
(
|
||||
'<a href="' + pl.col("sourceOpenData") + '">' + pl.col("source") + "</a>"
|
||||
'<a href="' + pl.col("sourceFile") + '">' + pl.col("sourceDataset") + "</a>"
|
||||
).alias("source")
|
||||
)
|
||||
lff = lff.drop("sourceOpenData")
|
||||
lff = lff.drop(["sourceFile", "sourceDataset"])
|
||||
return lff
|
||||
|
||||
|
||||
@@ -117,14 +117,12 @@ def get_decp_data() -> pl.LazyFrame:
|
||||
logger.info(
|
||||
f"Lecture du fichier parquet ({os.getenv('DATA_FILE_PARQUET_PATH')})..."
|
||||
)
|
||||
df: pl.DataFrame = pl.read_parquet(os.getenv("DATA_FILE_PARQUET_PATH"))
|
||||
lff: pl.LazyFrame = pl.scan_parquet(os.getenv("DATA_FILE_PARQUET_PATH"))
|
||||
except ComputeError:
|
||||
# Le fichier est probablement en cours de mise à jour
|
||||
logger.info("Échec, nouvelle tentative dans 10s...")
|
||||
sleep(10)
|
||||
df: pl.DataFrame = pl.read_parquet(os.getenv("DATA_FILE_PARQUET_PATH"))
|
||||
|
||||
lff: pl.LazyFrame = df.lazy()
|
||||
lff: pl.LazyFrame = pl.scan_parquet(os.getenv("DATA_FILE_PARQUET_PATH"))
|
||||
|
||||
# Remplacement des valeurs numériques par des chaînes de caractères
|
||||
# lff = numbers_to_strings(lff)
|
||||
|
||||
Reference in New Issue
Block a user