From cd107a02133d0c444e296f6c53eeada35c51fceb Mon Sep 17 00:00:00 2001 From: Colin Maudry Date: Thu, 2 Jul 2026 10:53:01 +0200 Subject: [PATCH] Refactor structure modules pages/compte, petits changements de texte --- .gitignore | 2 + src/pages/a_propos/donnees.py | 46 +++++++++++++++++++ src/pages/compte/__init__.py | 0 .../abonnement.py} | 27 +---------- .../abonnement_mes_infos.py} | 0 .../{compte_admin.py => compte/admin.py} | 0 src/pages/{compte.py => compte/racine.py} | 0 .../{compte_roadmap.py => compte/roadmap.py} | 0 src/pages/{compte_vues.py => compte/vues.py} | 0 tests/roadmap/test_compte_roadmap.py | 2 +- tests/subscriptions/conftest.py | 2 +- tests/subscriptions/test_compte_abonnement.py | 20 ++++---- 12 files changed, 62 insertions(+), 37 deletions(-) create mode 100644 src/pages/a_propos/donnees.py create mode 100644 src/pages/compte/__init__.py rename src/pages/{compte_abonnement.py => compte/abonnement.py} (90%) rename src/pages/{compte_abonnement_mes_infos.py => compte/abonnement_mes_infos.py} (100%) rename src/pages/{compte_admin.py => compte/admin.py} (100%) rename src/pages/{compte.py => compte/racine.py} (100%) rename src/pages/{compte_roadmap.py => compte/roadmap.py} (100%) rename src/pages/{compte_vues.py => compte/vues.py} (100%) diff --git a/.gitignore b/.gitignore index 74b1d52..33fb083 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ build # Runtime databases (never commit) users.sqlite *.sqlite +*.sqlite-* !tests/*.sqlite !tests/**/*.sqlite @@ -23,3 +24,4 @@ users.sqlite .superpowers/ .codegraph/ .claude +*.duckdb* diff --git a/src/pages/a_propos/donnees.py b/src/pages/a_propos/donnees.py new file mode 100644 index 0000000..6191eb0 --- /dev/null +++ b/src/pages/a_propos/donnees.py @@ -0,0 +1,46 @@ +import os + +from dash import dcc, html, register_page + +from src.figures import get_sources_tables +from src.pages._apropos_shell import apropos_shell +from src.utils.seo import META_CONTENT + +register_page( + __name__, + path="/a-propos/donnees", + title="Données | À propos | colibre", + description="Données brutes, qualité des données et sources utilisées par colibre pour consolider les marchés publics français.", + image_url=META_CONTENT["image_url"], +) + + +def layout(**_): + contenu = html.Div( + [ + html.H2("Consommer les données brutes"), + dcc.Markdown( + """ +Vous pouvez consommer les données qui alimentent colibre en les téléchargeant [sur data.gouv.fr](https://www.data.gouv.fr/datasets/donnees-essentielles-de-la-commande-publique-consolidees-format-tabulaire) (Parquet, CSV), pensez à lire la description du jeu de données + +Une API REST tabulaire (JSON) est également disponible par abonnement mensuel pour accéder aux mêmes données et alimenter une application. +Documentation interactive : [Swagger UI](/api/v1/swagger). Si cela vous intéresse, [contactez-moi](/a-propos/contact).""" + ), + html.H2("Qualité et exhaustivité des données", className="mt-4"), + dcc.Markdown( + """Les données visibles sur ce site proviennent exclusivement de la publication de données + ouvertes par les acheteurs publics ou en leur nom, régie par + [l'arrêté du 22 décembre 2022](https://www.legifrance.gouv.fr/jorf/id/JORFTEXT000046850496). Leur + qualité est donc principalement liée à la qualité de leur saisie par les agents publics, parfois + peu aidé·es par la qualité des outils à leur disposition. + +Je pense que l'analyse de marchés +individuels et le comptage de marchés sur des critères autres que financiers sont plutôt fiables. En revanche, certains montants de marché estimés à des valeurs farfelues ([1 euro](https://colibre.fr/marches/432766947000192025S01301), [1 milliard](https://colibre.fr/marches/2459004280001320210000000271)) faussent les calculs par aggrégation (sommes, moyennes, médianes) et donc la production de statistiques financières fiables. Acheteurs, acheteuses : s'il vous plaît, essayez d'estimer les montants des marchés publics attribués de manière plus précise. + +Quant à l'exhaustivité, je consolide toutes les sources de données exploitables que j'ai pu identifier (voir [Sources de données](/a-propos/donnees)). Je tiens à souligner la belle continuité de la publication par la DGFiP des données des marchés publics remontées via le [protocole PES](https://www.collectivites-locales.gouv.fr/finances-locales/le-protocole-dechange-standard-pes). Merci à leurs équipes.""" + ), + html.H2("Sources de données", className="mt-4"), + get_sources_tables(os.getenv("SOURCE_STATS_CSV_PATH")), + ] + ) + return apropos_shell("donnees", contenu) diff --git a/src/pages/compte/__init__.py b/src/pages/compte/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/pages/compte_abonnement.py b/src/pages/compte/abonnement.py similarity index 90% rename from src/pages/compte_abonnement.py rename to src/pages/compte/abonnement.py index 3d9c2e5..5bbf0b7 100644 --- a/src/pages/compte_abonnement.py +++ b/src/pages/compte/abonnement.py @@ -3,6 +3,7 @@ from dash import Input, Output, State, callback, dcc, html, register_page from flask_login import current_user from src.pages._compte_shell import account_guard, account_shell +from src.pages.a_propos.abonnement import abonnement_features from src.subscriptions import db, plans from src.utils.frontend import format_date_french @@ -74,31 +75,7 @@ def _plan_cards(trial_used=False, trial_for=plans.trial_days): def _explainer(): col_left = dbc.Col( - [ - html.H4("Ce que les abonnements financent"), - html.Ul( - [ - html.Li("Abonnement Frisbii (solution de paiement) : 50 €"), - html.Li("Serveur Scaleway : 40 €"), - html.Li("Espace de coworking : 250 €"), - html.Li( - [ - "Salaire médian (", - html.Span( - "coût employeur", - id="salaire-modal-trigger", - style={ - "cursor": "pointer", - "textDecoration": "underline", - "color": "var(--bs-link-color)", - }, - ), - ") : 3 840 €", - ] - ), - ] - ), - ], + [html.H4("Fonctionnalités réservées aux abonné•es :"), abonnement_features], md=6, style={ "borderRight": "1px solid var(--bs-border-color)", diff --git a/src/pages/compte_abonnement_mes_infos.py b/src/pages/compte/abonnement_mes_infos.py similarity index 100% rename from src/pages/compte_abonnement_mes_infos.py rename to src/pages/compte/abonnement_mes_infos.py diff --git a/src/pages/compte_admin.py b/src/pages/compte/admin.py similarity index 100% rename from src/pages/compte_admin.py rename to src/pages/compte/admin.py diff --git a/src/pages/compte.py b/src/pages/compte/racine.py similarity index 100% rename from src/pages/compte.py rename to src/pages/compte/racine.py diff --git a/src/pages/compte_roadmap.py b/src/pages/compte/roadmap.py similarity index 100% rename from src/pages/compte_roadmap.py rename to src/pages/compte/roadmap.py diff --git a/src/pages/compte_vues.py b/src/pages/compte/vues.py similarity index 100% rename from src/pages/compte_vues.py rename to src/pages/compte/vues.py diff --git a/tests/roadmap/test_compte_roadmap.py b/tests/roadmap/test_compte_roadmap.py index 6126f40..6fd6e54 100644 --- a/tests/roadmap/test_compte_roadmap.py +++ b/tests/roadmap/test_compte_roadmap.py @@ -1,5 +1,5 @@ def test_module_imports_and_registers(): - from src.pages import compte_roadmap + from src.pages.compte import roadmap as compte_roadmap assert callable(compte_roadmap.layout) assert callable(compte_roadmap.cast_vote) diff --git a/tests/subscriptions/conftest.py b/tests/subscriptions/conftest.py index 1d93c4b..912a598 100644 --- a/tests/subscriptions/conftest.py +++ b/tests/subscriptions/conftest.py @@ -11,7 +11,7 @@ import pytest # enregistrés), elle est ré-enregistrée → "Duplicate callback outputs" # (salaire-modal, resiliation-modal) qui casse le rendu de TOUTES les pages dans # la suite Selenium complète. En important src.app ici, la découverte tourne en -# premier et les imports ultérieurs de compte_abonnement sont mis en cache. +# premier et les imports ultérieurs de src.pages.compte.abonnement sont mis en cache. from src.app import app # noqa: F401, E402 diff --git a/tests/subscriptions/test_compte_abonnement.py b/tests/subscriptions/test_compte_abonnement.py index a3005e4..fc771dc 100644 --- a/tests/subscriptions/test_compte_abonnement.py +++ b/tests/subscriptions/test_compte_abonnement.py @@ -1,7 +1,7 @@ def test_plan_cards_present_when_no_subscription(monkeypatch): monkeypatch.setenv("FRISBII_PLAN_SIMPLE", "plan_simple") monkeypatch.setenv("FRISBII_PLAN_SOUTIEN", "plan_soutien") - from src.pages import compte_abonnement + from src.pages.compte import abonnement as compte_abonnement cards = compte_abonnement._plan_cards(trial_for=lambda key: 2) text = str(cards) @@ -13,7 +13,7 @@ def test_plan_cards_present_when_no_subscription(monkeypatch): def test_plan_cards_no_trial_when_trial_used(monkeypatch): monkeypatch.setenv("FRISBII_PLAN_SIMPLE", "plan_simple") monkeypatch.setenv("FRISBII_PLAN_SOUTIEN", "plan_soutien") - from src.pages import compte_abonnement + from src.pages.compte import abonnement as compte_abonnement text = str(compte_abonnement._plan_cards(trial_used=True, trial_for=lambda key: 2)) assert "Sans période d'essai" in text @@ -21,7 +21,7 @@ def test_plan_cards_no_trial_when_trial_used(monkeypatch): def test_active_view_shows_cancel(monkeypatch): - from src.pages import compte_abonnement + from src.pages.compte import abonnement as compte_abonnement row = { "plan": "simple", @@ -33,7 +33,7 @@ def test_active_view_shows_cancel(monkeypatch): def test_active_view_trial_banner(monkeypatch): - from src.pages import compte_abonnement + from src.pages.compte import abonnement as compte_abonnement row = { "plan": "simple", @@ -47,7 +47,7 @@ def test_subscribe_buttons_disabled_when_tous_abonnes(monkeypatch): monkeypatch.setenv("FRISBII_PLAN_SIMPLE", "plan_simple") monkeypatch.setenv("FRISBII_PLAN_SOUTIEN", "plan_soutien") monkeypatch.setattr("src.utils.TOUS_ABONNES", True) - from src.pages import compte_abonnement + from src.pages.compte import abonnement as compte_abonnement text = str(compte_abonnement._plan_cards(trial_for=lambda key: 2)) assert "btn-secondary disabled" in text @@ -58,7 +58,7 @@ def test_subscribe_buttons_active_when_flag_off(monkeypatch): monkeypatch.setenv("FRISBII_PLAN_SIMPLE", "plan_simple") monkeypatch.setenv("FRISBII_PLAN_SOUTIEN", "plan_soutien") monkeypatch.setattr("src.utils.TOUS_ABONNES", False) - from src.pages import compte_abonnement + from src.pages.compte import abonnement as compte_abonnement text = str(compte_abonnement._plan_cards(trial_for=lambda key: 2)) assert "btn-primary" in text @@ -66,7 +66,7 @@ def test_subscribe_buttons_active_when_flag_off(monkeypatch): def test_banner_present_when_tous_abonnes(monkeypatch): monkeypatch.setattr("src.utils.TOUS_ABONNES", True) - from src.pages import compte_abonnement + from src.pages.compte import abonnement as compte_abonnement text = str(compte_abonnement._tous_abonnes_banner()) assert "accessibles à tous et toutes" in text @@ -74,20 +74,20 @@ def test_banner_present_when_tous_abonnes(monkeypatch): def test_banner_absent_when_flag_off(monkeypatch): monkeypatch.setattr("src.utils.TOUS_ABONNES", False) - from src.pages import compte_abonnement + from src.pages.compte import abonnement as compte_abonnement assert compte_abonnement._tous_abonnes_banner() is None def test_show_active_view_true_for_live_statuses(monkeypatch): - from src.pages import compte_abonnement + from src.pages.compte import abonnement as compte_abonnement for status in ("pending", "trial", "active", "cancelled", "expired"): assert compte_abonnement._show_active_view({"status": status}) is True def test_show_active_view_false_for_failed_or_none(monkeypatch): - from src.pages import compte_abonnement + from src.pages.compte import abonnement as compte_abonnement assert compte_abonnement._show_active_view({"status": "failed"}) is False assert compte_abonnement._show_active_view(None) is False