diff --git a/.codegraph/.gitignore b/.codegraph/.gitignore new file mode 100644 index 0000000..9de0f16 --- /dev/null +++ b/.codegraph/.gitignore @@ -0,0 +1,16 @@ +# CodeGraph data files +# These are local to each machine and should not be committed + +# Database +*.db +*.db-wal +*.db-shm + +# Cache +cache/ + +# Logs +*.log + +# Hook markers +.dirty diff --git a/.codegraph/config.json b/.codegraph/config.json new file mode 100644 index 0000000..6adc6e7 --- /dev/null +++ b/.codegraph/config.json @@ -0,0 +1,140 @@ +{ + "version": 1, + "include": [ + "**/*.ts", + "**/*.tsx", + "**/*.js", + "**/*.jsx", + "**/*.py", + "**/*.go", + "**/*.rs", + "**/*.java", + "**/*.c", + "**/*.h", + "**/*.cpp", + "**/*.hpp", + "**/*.cc", + "**/*.cxx", + "**/*.cs", + "**/*.php", + "**/*.rb", + "**/*.swift", + "**/*.kt", + "**/*.kts", + "**/*.dart", + "**/*.svelte", + "**/*.liquid", + "**/*.pas", + "**/*.dpr", + "**/*.dpk", + "**/*.lpr", + "**/*.dfm", + "**/*.fmx" + ], + "exclude": [ + "**/.git/**", + "**/node_modules/**", + "**/vendor/**", + "**/Pods/**", + "**/dist/**", + "**/build/**", + "**/out/**", + "**/bin/**", + "**/obj/**", + "**/target/**", + "**/*.min.js", + "**/*.bundle.js", + "**/.next/**", + "**/.nuxt/**", + "**/.svelte-kit/**", + "**/.output/**", + "**/.turbo/**", + "**/.cache/**", + "**/.parcel-cache/**", + "**/.vite/**", + "**/.astro/**", + "**/.docusaurus/**", + "**/.gatsby/**", + "**/.webpack/**", + "**/.nx/**", + "**/.yarn/cache/**", + "**/.pnpm-store/**", + "**/storybook-static/**", + "**/.expo/**", + "**/web-build/**", + "**/ios/Pods/**", + "**/ios/build/**", + "**/android/build/**", + "**/android/.gradle/**", + "**/__pycache__/**", + "**/.venv/**", + "**/venv/**", + "**/site-packages/**", + "**/dist-packages/**", + "**/.pytest_cache/**", + "**/.mypy_cache/**", + "**/.ruff_cache/**", + "**/.tox/**", + "**/.nox/**", + "**/*.egg-info/**", + "**/.eggs/**", + "**/go/pkg/mod/**", + "**/target/debug/**", + "**/target/release/**", + "**/.gradle/**", + "**/.m2/**", + "**/generated-sources/**", + "**/.kotlin/**", + "**/.dart_tool/**", + "**/.vs/**", + "**/.nuget/**", + "**/artifacts/**", + "**/publish/**", + "**/cmake-build-*/**", + "**/CMakeFiles/**", + "**/bazel-*/**", + "**/vcpkg_installed/**", + "**/.conan/**", + "**/Debug/**", + "**/Release/**", + "**/x64/**", + "**/.pio/**", + "**/release/**", + "**/*.app/**", + "**/*.asar", + "**/DerivedData/**", + "**/.build/**", + "**/.swiftpm/**", + "**/xcuserdata/**", + "**/Carthage/Build/**", + "**/SourcePackages/**", + "**/__history/**", + "**/__recovery/**", + "**/*.dcu", + "**/.composer/**", + "**/storage/framework/**", + "**/bootstrap/cache/**", + "**/.bundle/**", + "**/tmp/cache/**", + "**/public/assets/**", + "**/public/packs/**", + "**/.yardoc/**", + "**/coverage/**", + "**/htmlcov/**", + "**/.nyc_output/**", + "**/test-results/**", + "**/.coverage/**", + "**/.idea/**", + "**/logs/**", + "**/tmp/**", + "**/temp/**", + "**/_build/**", + "**/docs/_build/**", + "**/site/**" + ], + "languages": [], + "frameworks": [], + "maxFileSize": 1048576, + "extractDocstrings": true, + "trackCallSites": true +} diff --git a/arreté.txt b/arreté.txt new file mode 100644 index 0000000..c83f6b8 --- /dev/null +++ b/arreté.txt @@ -0,0 +1,169 @@ +Arrêté du 22 mars 2019 relatif aux fonctionnalités et exigences minimales des profils d'acheteurs + +Dernière mise à jour des données de ce texte : 01 avril 2019 + +NOR : ECOM1831551A + +JORF n°0077 du 31 mars 2019 + + +Le ministre de l'économie et des finances et la ministre des outre-mer, +Vu le règlement n° 910/2014 du Parlement européen et du Conseil du 23 juillet 2014 sur l'identification électronique et les services de confiance pour les transactions électroniques au sein du marché intérieur et abrogeant la directive 1999/93/CE ; +Vu le règlement d'exécution (UE) n° 2016/7 de la Commission européenne du 5 janvier 2016 établissant le formulaire type pour le document unique de marché européen ; +Vu le code de la commande publique, et notamment ses articles R. 2132-2, R. 2132-3, R. 2196-1 et R. 3131-1 ; +Vu la loi n° 2004-575 du 21 juin 2004 pour la confiance dans l'économique numérique ; +Vu l'ordonnance n° 2005-1516 du 8 décembre 2005 relative aux échanges électroniques entre les usagers et les autorités administratives et entre les autorités administratives, notamment ses articles 9, 10, 11 et 12, +Arrêtent : + +Article 1 + + +I. - Le profil d'acheteur permet à l'acheteur d'effectuer les actions suivantes  : +1° S'identifier et s'authentifier ; +2° Publier des avis d'appel à la concurrence et leurs éventuelles modifications ; +3° Mettre à disposition des documents de la consultation ; +4° Réceptionner et conserver des candidatures y compris si elles se présentent sous la forme du document unique de marché européen électronique constituant un échange de données structurées ; +5° Réceptionner et conserver des offres, y compris hors délais ; +6° Compléter un formulaire nécessaire à la publication des données essentielles prévues par l'arrêté du 22 mars 2019 relatif aux données essentielles dans la commande publique ou importer ces données lorsqu'elles sont disponibles dans un autre système d'information ; +7° Accéder à un service de courrier électronique au sens de l'article 1 de la loi du 21 juin 2004 susvisée ; +8° Accéder à un historique des évènements permettant l'enregistrement et la traçabilité des actions ayant eu lieu sur le profil d'acheteur notamment le retrait et le dépôt de documents ; +9° Répondre aux questions soumises par les entreprises ; +10° Obtenir les documents justificatifs et moyens de preuve lorsque ceux-ci peuvent être directement obtenus auprès d'autres administrations. +II. - Le profil d'acheteurs permet à l'opérateur économique d'effectuer les actions suivantes  : +1° S'identifier et s'authentifier ; +2° Connaître les prérequis techniques et les modules d'extension nécessaires pour utiliser le profil d'acheteur ; +3° Accéder à un espace permettant de tester que la configuration du poste de travail utilisé est en adéquation avec les prérequis techniques du profil d'acheteur ; +4° Effectuer une recherche permettant d'accéder notamment aux avis d'appel à la concurrence, aux consultations et aux données essentielles ; +5° Consulter et télécharger en accès gratuit, libre, direct et complet les documents de la consultation, les avis d'appel à la concurrence et leurs éventuelles modifications ; +6° Accéder à un espace permettant de simuler le dépôt de documents ; +7° Déposer une candidature y compris si elle se présente sous la forme du document unique de marché européen électronique constituant un échange de données structurées ; +8° Déposer des offres, y compris les dépôts successifs quand la procédure le requiert et les offres signées électroniquement ; +9° Solliciter une assistance ou consulter un support utilisateur permettant d'apporter des réponses aux problématiques techniques ; +10° Formuler des questions à l'acheteur ; +11° Consulter et télécharger les données essentielles conformément aux dispositions de l'arrêté du 22 mars 2019 relatif aux données essentielles dans la commande publique. + +Article 2 + + +I. - Le profil d'acheteur répond aux exigences fixées dans les référentiels généraux de sécurité, d'interopérabilité et d'accessibilité prévues aux articles 9 et 11 de l'ordonnance n° 2005-1516 du 8 décembre 2005 susvisée. +II. - Les fonctionnalités visées à l'article 1er répondent aux exigences techniques, de sécurité et d'accessibilité suivantes  : +1° Le profil d'acheteur accepte les fichiers communément disponibles et notamment les fichiers aux formats .XML et .JSON ; +2° La taille et les formats des documents et avis d'appel à la concurrence sont indiqués ; +3° L'horodatage est qualifié conformément aux dispositions du règlement n° 910/2014 du Parlement européen et du Conseil du 23 juillet 2014 susvisé ; +4° Le profil d'acheteur assure l'intégrité des données ; +5° Le profil d'acheteur permet une visualisation adaptée au média utilisé ; +6° Le profil d'acheteur garantit la confidentialité des candidatures, des offres et des demandes de participation jusqu'à l'expiration du délai prévu pour leur présentation. Les documents sont inaccessibles avant cette date. A l'expiration de ce délai, ils ne sont accessibles qu'aux personnes autorisées. Le profil d'acheteur recourt à des moyens de cryptologie ou à un outil de gestion des droits d'accès et des privilèges ou à une technique équivalente ; +7° Le profil d'acheteur est interopérable avec les autres outils et dispositifs de communication électronique et d'échanges d'informations utilisés dans le cadre de la commande publique. +III. - Les dépôts, par l'opérateur économique, de documents sur le profil d'acheteur donnent immédiatement lieu à l'envoi d'un accusé de réception automatique portant les mentions suivantes  : + + +- l'identification de l'opérateur économique auteur du dépôt ; +- le nom de l'acheteur public ; +- l'intitulé et l'objet de la consultation concernée ; +- la date et l'heure de réception des documents ; +- la liste détaillée des documents transmis. + +Article 3 + + +I. - Pour les autorités concédantes, le profil d'acheteur permet d'effectuer les actions suivantes : +1° S'identifier et s'authentifier ; +2° Mettre à disposition des documents de la consultation ; +3° Réceptionner et conserver des candidatures ; +4° Réceptionner et conserver des offres, y compris hors délais ; +5° Compléter un formulaire nécessaire à la publication des données essentielles prévues par l'arrêté du 22 mars 2019 relatif aux données essentielles dans la commande publique précité ou importer ces données lorsqu'elles sont disponibles dans un autre système d'information ; +6° Accéder à un historique des évènements permettant l'enregistrement et la traçabilité des actions ayant eu lieu sur le profil d'acheteur notamment le retrait et le dépôt de documents. +II. - Le profil d'acheteur permet à l'opérateur économique d'effectuer les actions suivantes : +1° S'identifier et s'authentifier ; +2° Connaître les prérequis techniques et les modules d'extension nécessaires pour utiliser le profil d'acheteur ; +3° Accéder à un espace permettant de tester que la configuration du poste de travail utilisé est en adéquation avec les prérequis techniques du profil d'acheteur ; +4° Effectuer une recherche permettant d'accéder notamment aux consultations et aux données essentielles ; +5° Consulter et télécharger en accès gratuit, libre, direct et complet les documents de la consultation ; +6° Accéder à un espace permettant de simuler le dépôt de documents ; +7° Déposer une candidature ; +8° Déposer des offres ; +9° Solliciter une assistance ou consulter un support utilisateur permettant d'apporter des réponses aux problématiques techniques ; +10° Consulter et télécharger les données essentielles conformément aux dispositions de l'arrêté du 22 mars 2019 relatif aux données essentielles dans la commande publique précité. +III. - Les fonctionnalités visées au I et au II de l'article 3 répondent aux exigences techniques, de sécurité et d'accessibilité suivantes  : +1° Le profil d'acheteur garantit la confidentialité des candidatures, des offres et des demandes de participation jusqu'à l'expiration du délai prévu pour leur présentation. Les documents sont inaccessibles avant cette date. A l'expiration de ce délai, ils ne sont accessibles qu'aux personnes autorisées. Le profil d'acheteur recourt à des moyens de cryptologie ou à un outil de gestion des droits d'accès et des privilèges ou à une technique équivalente ; +2° Assurer l'intégrité des données ; +3° Permettre une visualisation adaptée au média utilisé ; +4° Le profil d'acheteur répond aux exigences fixées dans les référentiels généraux de sécurité, d'interopérabilité et d'accessibilité prévues aux articles 9 et 11 de l'ordonnance n° 2005-1516 du 8 décembre 2005. +IV. - Les dépôts, par l'opérateur économique, de documents sur le profil d'acheteur donnent immédiatement lieu à l'envoi d'un accusé réception automatique portant les mentions suivantes  : + + +- l'identification de l'opérateur économique auteur du dépôt ; +- le nom de l'autorité concédante ; +- l'intitulé et l'objet de la consultation concernée ; +- la date et l'heure de réception des documents ; +- la liste détaillée des documents transmis. + +Article 4 + + +I. - Le profil d'acheteur figure sur une liste publiée sur le portail unique interministériel destiné à rassembler et à mettre à disposition librement l'ensemble des informations publiques. +II. - Chaque profil d'acheteur est identifié par  : + + +- le SIRET de l'acheteur ; +- l'adresse URL du profil d'acheteur ; +- l'adresse URL du DCAT prévue à l'article 8 de l'arrêté du 22 mars 2019 relatif aux données essentielles dans la commande publique précité. +- les coordonnées du ou des acheteurs concernés. + + +III. - La déclaration du profil d'acheteur est effectuée par l'acheteur ou toute personne habilitée par celui-ci sur le portail unique interministériel destiné à rassembler et à mettre à disposition librement l'ensemble des informations publiques. La déclaration comporte l'identité du déclarant, l'identité de l'organisme chargé de la gestion du profil d'acheteur, l'adresse URL du profil d'acheteur, l'adresse URL du DCAT et les coordonnées du ou des acheteurs concernés. + +Article 5 + + +I. - Pour son application à Saint-Barthélemy et à Saint-Pierre-et-Miquelon le 3° de l'article 2 est ainsi rédigé  : +« l'horodatage est qualifié conformément aux dispositions applicables en métropole en vertu du règlement n° 910/2014 du Parlement européen et du Conseil du 23 juillet 2014 susvisé ; » +II. - Les dispositions du présent arrêté sont applicables en Nouvelle-Calédonie, en Polynésie française, dans les îles Wallis et Futuna et dans les Terres australes et antarctiques françaises pour les contrats de la commande publiques conclus par l'Etat et ses établissements publics, sous réserves des adaptations suivantes  : +1° Pour son application en Nouvelle-Calédonie et en Polynésie française, le 3° de l'article 2 est ainsi rédigé  : +« l'horodatage est qualifié conformément aux dispositions applicables localement ; » +2° Pour son application les dans les îles Wallis et Futuna et dans les Terres australes et antarctiques françaises le 3° de l'article 2 est ainsi rédigé  : +« l'horodatage est qualifié conformément aux dispositions applicables en métropole en vertu du règlement n° 910/2014 du Parlement européen et du Conseil du 23 juillet 2014 susvisé. » + +Article 6 + + +Le présent arrêté constitue l'annexe 7 du code de la commande publique. + +Article 7 + +A modifié les dispositions suivantes + + Abroge Arrêté du 14 avril 2017 (Ab) + Abroge Arrêté du 14 avril 2017 - art. 1 (Ab) + Abroge Arrêté du 14 avril 2017 - art. 2 (Ab) + Abroge Arrêté du 14 avril 2017 - art. 3 (Ab) + Abroge Arrêté du 14 avril 2017 - art. 4 (Ab) + Abroge Arrêté du 14 avril 2017 - art. 5 (Ab) + Abroge Arrêté du 14 avril 2017 - art. 6 (Ab) + +Article 8 + + +Le présent arrêté entre en vigueur le 1er avril 2019. +Il s'applique aux marchés publics pour lesquels une consultation est engagée ou un avis d'appel à la concurrence est envoyé à la publication à compter de sa date d'entrée en vigueur. +Il s'applique aux contrats de concession pour lesquels une consultation est engagée ou un avis de concession est envoyé à la publication à compter de sa date d'entrée en vigueur. + +Article 9 + + +La directrice des affaires juridiques des ministères économiques et financiers et le directeur général des outre-mer sont chargés de l'exécution du présent arrêté, qui sera publié au Journal officiel de la République française. + + +Fait le 22 mars 2019. + + +Le ministre de l'économie et des finances, +Pour le ministre et par délégation : +La directrice des affaires juridiques, +L. Bedier + + +La ministre des outre-mer, +Pour la ministre et par délégation : +Le directeur général des outre-mer, +E. Berthier diff --git a/comptes_utilisateurs.md b/comptes_utilisateurs.md new file mode 100644 index 0000000..e9cd7b2 --- /dev/null +++ b/comptes_utilisateurs.md @@ -0,0 +1,29 @@ +# Contexte + +Je souhate ajouter la possibilité pour les utilisateurs de créer un compte utilisateur. Les données des utilisateurs sont stockés dans une base de données sqlite située à la racine du projet. Les seules données demandées sont une adresse email et un mot de passe. Un lien dans le menu supérieur, tout à droite, permet d'accéder à une page qui permet soit de se connecter, soit de créer un compte. + +Il s'agit des fondations des comptes utilisateurs, d'autres fonctionnalités viendront s'ajouter. + +# Inscription + +Données nécessaires : + +- adresse email +- mot de passe + +Le mot de passe est hashé en base de données. + +# Connexion + +- adresse email +- mot passe + +Lien vers une page de réinitialisation du mot de passe. + +# Page du compte + +Possibilité de changer le mot de passe. + +# Configuration + +Ces fonctionnalités nécessitent d'envoyer des emails. Les options nécessaire à une connexion à un service SMTP sont fournies sous la forme de variables d'environnement. diff --git a/data/cj.csv b/data/cj.csv new file mode 100644 index 0000000..31cc270 --- /dev/null +++ b/data/cj.csv @@ -0,0 +1,261 @@ +code,libellé +0000,Organisme de placement collectif en valeurs mobilières sans personnalité morale +1000,Entrepreneur individuel +2110,Indivision entre personnes physiques +2120,Indivision avec personne morale +2210,Société créée de fait entre personnes physiques +2220,Société créée de fait avec personne morale +2310,Société en participation entre personnes physiques +2320,Société en participation avec personne morale +2385,Société en participation de professions libérales +2400,Fiducie +2700,Paroisse hors zone concordataire +2800,Assujetti unique à la TVA +2900,Autre groupement de droit privé non doté de la personnalité morale +3110,Représentation ou agence commerciale d'état ou organisme public étranger immatriculé au RCS +3120,Société commerciale étrangère immatriculée au RCS +3205,Organisation internationale +3210,"État, collectivité ou établissement public étranger" +3220,Société étrangère non immatriculée au RCS +3290,Autre personne morale de droit étranger +4110,Établissement public national à caractère industriel ou commercial doté d'un comptable public +4120,Établissement public national à caractère industriel ou commercial non doté d'un comptable public +4130,Exploitant public +4140,Établissement public local à caractère industriel ou commercial +4150,Régie d'une collectivité locale à caractère industriel ou commercial +4160,Institution Banque de France +5191,Société de caution mutuelle +5192,Société coopérative de banque populaire +5193,Caisse de crédit maritime mutuel +5194,Caisse (fédérale) de crédit mutuel +5195,Association coopérative inscrite (droit local Alsace Moselle) +5196,Caisse d'épargne et de prévoyance à forme coopérative +5202,Société en nom collectif +5203,Société en nom collectif coopérative +5306,Société en commandite simple +5307,Société en commandite simple coopérative +5308,Société en commandite par actions +5309,Société en commandite par actions coopérative +5310,Société en libre partenariat (SLP) +5370,Société de Participations Financières de Profession Libérale Société en commandite par actions (SPFPL SCA) +5385,Société d'exercice libéral en commandite par actions +5410,SARL nationale +5415,SARL d'économie mixte +5422,SARL immobilière pour le commerce et l'industrie (SICOMI) +5426,SARL immobilière de gestion +5430,SARL d'aménagement foncier et d'équipement rural (SAFER) +5431,SARL mixte d'intérêt agricole (SMIA) +5432,SARL d'intérêt collectif agricole (SICA) +5442,SARL d'attribution +5443,SARL coopérative de construction +5451,SARL coopérative de consommation +5453,SARL coopérative artisanale +5454,SARL coopérative d'intérêt maritime +5455,SARL coopérative de transport +5458,SARL coopérative de production (SCOP) +5459,SARL union de sociétés coopératives +5460,Autre SARL coopérative +5470,Société de Participations Financières de Profession Libérale Société à responsabilité limitée (SPFPL SARL) +5485,Société d'exercice libéral à responsabilité limitée +5499,Société à responsabilité limitée (sans autre indication) +5505,SA à participation ouvrière à conseil d'administration +5510,SA nationale à conseil d'administration +5515,SA d'économie mixte à conseil d'administration +5520,Fonds à forme sociétale à conseil d'administration +5522,SA immobilière pour le commerce et l'industrie (SICOMI) à conseil d'administration +5525,SA immobilière d'investissement à conseil d'administration +5530,SA d'aménagement foncier et d'équipement rural (SAFER) à conseil d'administration +5531,Société anonyme mixte d'intérêt agricole (SMIA) à conseil d'administration +5532,SA d'intérêt collectif agricole (SICA) à conseil d'administration +5542,SA d'attribution à conseil d'administration +5543,SA coopérative de construction à conseil d'administration +5546,SA de HLM à conseil d'administration +5547,SA coopérative de production de HLM à conseil d'administration +5548,SA de crédit immobilier à conseil d'administration +5551,SA coopérative de consommation à conseil d'administration +5552,SA coopérative de commerçants-détaillants à conseil d'administration +5553,SA coopérative artisanale à conseil d'administration +5554,SA coopérative (d'intérêt) maritime à conseil d'administration +5555,SA coopérative de transport à conseil d'administration +5558,SA coopérative de production (SCOP) à conseil d'administration +5559,SA union de sociétés coopératives à conseil d'administration +5560,Autre SA coopérative à conseil d'administration +5570,Société de Participations Financières de Profession Libérale Société anonyme à conseil d'administration (SPFPL SA à conseil d'administration) +5585,Société d'exercice libéral à forme anonyme à conseil d'administration +5599,SA à conseil d'administration (s.a.i.) +5605,SA à participation ouvrière à directoire +5610,SA nationale à directoire +5615,SA d'économie mixte à directoire +5620,Fonds à forme sociétale à directoire +5622,SA immobilière pour le commerce et l'industrie (SICOMI) à directoire +5625,SA immobilière d'investissement à directoire +5630,Safer anonyme à directoire +5631,SA mixte d'intérêt agricole (SMIA) +5632,SA d'intérêt collectif agricole (SICA) +5642,SA d'attribution à directoire +5643,SA coopérative de construction à directoire +5646,SA de HLM à directoire +5647,Société coopérative de production de HLM anonyme à directoire +5648,SA de crédit immobilier à directoire +5651,SA coopérative de consommation à directoire +5652,SA coopérative de commerçants-détaillants à directoire +5653,SA coopérative artisanale à directoire +5654,SA coopérative d'intérêt maritime à directoire +5655,SA coopérative de transport à directoire +5658,SA coopérative de production (SCOP) à directoire +5659,SA union de sociétés coopératives à directoire +5660,Autre SA coopérative à directoire +5670,Société de Participations Financières de Profession Libérale Société anonyme à Directoire (SPFPL SA à directoire) +5685,Société d'exercice libéral à forme anonyme à directoire +5699,SA à directoire (s.a.i.) +5710,"SAS, société par actions simplifiée" +5770,Société de Participations Financières de Profession Libérale Société par actions simplifiée (SPFPL SAS) +5785,Société d'exercice libéral par action simplifiée +5800,Société européenne +6100,Caisse d'Épargne et de Prévoyance +6210,Groupement européen d'intérêt économique (GEIE) +6220,Groupement d'intérêt économique (GIE) +6316,Coopérative d'utilisation de matériel agricole en commun (CUMA) +6317,Société coopérative agricole +6318,Union de sociétés coopératives agricoles +6411,Société d'assurance à forme mutuelle +6511,Sociétés Interprofessionnelles de Soins Ambulatoires  +6521,Société civile de placement collectif immobilier (SCPI) +6532,Société civile d'intérêt collectif agricole (SICA) +6533,Groupement agricole d'exploitation en commun (GAEC) +6534,Groupement foncier agricole +6535,Groupement agricole foncier +6536,Groupement forestier +6537,Groupement pastoral +6538,Groupement foncier et rural +6539,Société civile foncière +6540,Société civile immobilière +6541,Société civile immobilière de construction-vente +6542,Société civile d'attribution +6543,Société civile coopérative de construction +6544,Société civile immobilière d' accession progressive à la propriété +6551,Société civile coopérative de consommation +6554,Société civile coopérative d'intérêt maritime +6558,Société civile coopérative entre médecins +6560,Autre société civile coopérative +6561,SCP d'avocats +6562,SCP d'avocats aux conseils +6563,SCP d'avoués d'appel +6564,SCP d'huissiers +6565,SCP de notaires +6566,SCP de commissaires-priseurs +6567,SCP de greffiers de tribunal de commerce +6568,SCP de conseils juridiques +6569,SCP de commissaires aux comptes +6571,SCP de médecins +6572,SCP de dentistes +6573,SCP d'infirmiers +6574,SCP de masseurs-kinésithérapeutes +6575,SCP de directeurs de laboratoire d'analyse médicale +6576,SCP de vétérinaires +6577,SCP de géomètres experts +6578,SCP d'architectes +6585,Autre société civile professionnelle +6589,Société civile de moyens +6595,Caisse locale de crédit mutuel +6596,Caisse de crédit agricole mutuel +6597,Société civile d'exploitation agricole +6598,Exploitation agricole à responsabilité limitée +6599,Autre société civile +6901,Autre personne de droit privé inscrite au registre du commerce et des sociétés +7111,Autorité constitutionnelle +7112,Autorité administrative ou publique indépendante +7113,Ministère +7120,Service central d'un ministère +7150,Service du ministère de la Défense +7160,Service déconcentré à compétence nationale d'un ministère (hors Défense) +7171,Service déconcentré de l'État à compétence (inter) régionale +7172,Service déconcentré de l'État à compétence (inter) départementale +7179,(Autre) Service déconcentré de l'État à compétence territoriale +7190,Ecole nationale non dotée de la personnalité morale +7210,Commune et commune nouvelle +7220,Département +7225,Collectivité et territoire d'Outre Mer +7229,(Autre) Collectivité territoriale +7230,Région +7312,Commune associée et commune déléguée +7313,Section de commune +7314,Ensemble urbain +7321,Association syndicale autorisée +7322,Association foncière urbaine +7323,Association foncière de remembrement +7331,Établissement public local d'enseignement +7340,Pôle métropolitain +7341,Secteur de commune +7342,District urbain +7343,Communauté urbaine +7344,Métropole +7345,Syndicat intercommunal à vocation multiple (SIVOM) +7346,Communauté de communes +7347,Communauté de villes +7348,Communauté d'agglomération +7349,Autre établissement public local de coopération non spécialisé ou entente +7351,Institution interdépartementale ou entente +7352,Institution interrégionale ou entente +7353,Syndicat intercommunal à vocation unique (SIVU) +7354,Syndicat mixte fermé +7355,Syndicat mixte ouvert +7356,Commission syndicale pour la gestion des biens indivis des communes +7357,Pôle d'équilibre territorial et rural (PETR) +7361,Centre communal d'action sociale +7362,Caisse des écoles +7363,Caisse de crédit municipal +7364,Établissement d'hospitalisation +7365,Syndicat inter hospitalier +7366,Établissement public local social et médico-social +7367,Centre Intercommunal d'action sociale (CIAS) +7371,Office public d'habitation à loyer modéré (OPHLM) +7372,Service départemental d'incendie et de secours (SDIS) +7373,Établissement public local culturel +7378,Régie d'une collectivité locale à caractère administratif +7379,(Autre) Établissement public administratif local +7381,Organisme consulaire +7382,Établissement public national ayant fonction d'administration centrale +7383,Établissement public national à caractère scientifique culturel et professionnel +7384,Autre établissement public national d'enseignement +7385,Autre établissement public national administratif à compétence territoriale limitée +7389,Établissement public national à caractère administratif +7410,Groupement d'intérêt public (GIP) +7430,Établissement public des cultes d'Alsace-Lorraine +7450,"Etablissement public administratif, cercle et foyer dans les armées " +7470,Groupement de coopération sanitaire à gestion publique +7490,Autre personne morale de droit administratif +8110,Régime général de la Sécurité Sociale +8120,Régime spécial de Sécurité Sociale +8130,Institution de retraite complémentaire +8140,Mutualité sociale agricole +8150,Régime maladie des non-salariés non agricoles +8160,Régime vieillesse ne dépendant pas du régime général de la Sécurité Sociale +8170,Régime d'assurance chômage +8190,Autre régime de prévoyance sociale +8210,Mutuelle +8250,Assurance mutuelle agricole +8290,Autre organisme mutualiste +8310,Comité social économique d’entreprise +8311,Comité social économique d'établissement +8410,Syndicat de salariés +8420,Syndicat patronal +8450,Ordre professionnel ou assimilé +8470,Centre technique industriel ou comité professionnel du développement économique +8490,Autre organisme professionnel +8510,Institution de prévoyance +8520,Institution de retraite supplémentaire +9110,Syndicat de copropriété +9150,Association syndicale libre +9210,Association non déclarée +9220,Association déclarée +9221,Association déclarée d'insertion par l'économique +9222,Association intermédiaire +9223,Groupement d'employeurs +9224,Association d'avocats à responsabilité professionnelle individuelle +9230,"Association déclarée, reconnue d'utilité publique" +9240,Congrégation +9260,"Association de droit local (Bas-Rhin, Haut-Rhin et Moselle)" +9300,Fondation +9900,Autre personne morale de droit privé +9970,Groupement de coopération sanitaire à gestion privée diff --git a/decpinfo.log b/decpinfo.log new file mode 100644 index 0000000..9322b39 --- /dev/null +++ b/decpinfo.log @@ -0,0 +1,100 @@ + File "", line 1149, in _find_and_load_unlocked + File "", line 690, in _load_unlocked + File "", line 940, in exec_module + File "", line 241, in _call_with_frames_removed + File "/var/www/decpinfo/run.py", line 1, in + from src.app import app + File "/var/www/decpinfo/src/app.py", line 30, in + app: Dash = Dash( + ^^^^^ + File "/var/www/decpinfo/.venv/lib/python3.11/site-packages/dash/dash.py", line 639, in __init__ + self.init_app() + File "/var/www/decpinfo/.venv/lib/python3.11/site-packages/dash/dash.py", line 752, in init_app + self.enable_pages() + File "/var/www/decpinfo/.venv/lib/python3.11/site-packages/dash/dash.py", line 2535, in enable_pages + _import_layouts_from_pages(self.config.pages_folder) + File "/var/www/decpinfo/.venv/lib/python3.11/site-packages/dash/_pages.py", line 442, in _import_layouts_from_pages + spec.loader.exec_module(page_module) # type: ignore[reportOptionalMemberAccess] + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/var/www/decpinfo/src/pages/observatoire.py", line 19, in + from src.db import schema + File "/var/www/decpinfo/src/db.py", line 126, in + DB_PATH = _ensure_database() + ^^^^^^^^^^^^^^^^^^ + File "/var/www/decpinfo/src/db.py", line 119, in _ensure_database + if should_rebuild(db_path, parquet_path): + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/var/www/decpinfo/src/db.py", line 23, in should_rebuild + return parquet_path.stat().st_mtime > db_path.stat().st_mtime + ^^^^^^^^^^^^^^^^^^^ + File "/usr/lib/python3.11/pathlib.py", line 1014, in stat + return os.stat(self, follow_symlinks=follow_symlinks) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +FileNotFoundError: [Errno 2] No such file or directory: '/srv/shared/decp/prod/dist/decp.parquet' +[2026-05-01 08:00:30 +0200] [2216551] [INFO] Worker exiting (pid: 2216551) +[2026-05-01 08:00:30 +0200] [2216552] [ERROR] Exception in worker process +Traceback (most recent call last): + File "/var/www/decpinfo/.venv/lib/python3.11/site-packages/gunicorn/arbiter.py", line 608, in spawn_worker + worker.init_process() + File "/var/www/decpinfo/.venv/lib/python3.11/site-packages/gunicorn/workers/base.py", line 135, in init_process + self.load_wsgi() + File "/var/www/decpinfo/.venv/lib/python3.11/site-packages/gunicorn/workers/base.py", line 147, in load_wsgi + self.wsgi = self.app.wsgi() + ^^^^^^^^^^^^^^^ + File "/var/www/decpinfo/.venv/lib/python3.11/site-packages/gunicorn/app/base.py", line 66, in wsgi + self.callable = self.load() + ^^^^^^^^^^^ + File "/var/www/decpinfo/.venv/lib/python3.11/site-packages/gunicorn/app/wsgiapp.py", line 57, in load + return self.load_wsgiapp() + ^^^^^^^^^^^^^^^^^^^ + File "/var/www/decpinfo/.venv/lib/python3.11/site-packages/gunicorn/app/wsgiapp.py", line 47, in load_wsgiapp + return util.import_app(self.app_uri) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/var/www/decpinfo/.venv/lib/python3.11/site-packages/gunicorn/util.py", line 370, in import_app + mod = importlib.import_module(module) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/usr/lib/python3.11/importlib/__init__.py", line 126, in import_module + return _bootstrap._gcd_import(name[level:], package, level) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "", line 1206, in _gcd_import + File "", line 1178, in _find_and_load + File "", line 1149, in _find_and_load_unlocked + File "", line 690, in _load_unlocked + File "", line 940, in exec_module + File "", line 241, in _call_with_frames_removed + File "/var/www/decpinfo/run.py", line 1, in + from src.app import app + File "/var/www/decpinfo/src/app.py", line 30, in + app: Dash = Dash( + ^^^^^ + File "/var/www/decpinfo/.venv/lib/python3.11/site-packages/dash/dash.py", line 639, in __init__ + self.init_app() + File "/var/www/decpinfo/.venv/lib/python3.11/site-packages/dash/dash.py", line 752, in init_app + self.enable_pages() + File "/var/www/decpinfo/.venv/lib/python3.11/site-packages/dash/dash.py", line 2535, in enable_pages + _import_layouts_from_pages(self.config.pages_folder) + File "/var/www/decpinfo/.venv/lib/python3.11/site-packages/dash/_pages.py", line 442, in _import_layouts_from_pages + spec.loader.exec_module(page_module) # type: ignore[reportOptionalMemberAccess] + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/var/www/decpinfo/src/pages/observatoire.py", line 19, in + from src.db import schema + File "/var/www/decpinfo/src/db.py", line 126, in + DB_PATH = _ensure_database() + ^^^^^^^^^^^^^^^^^^ + File "/var/www/decpinfo/src/db.py", line 119, in _ensure_database + if should_rebuild(db_path, parquet_path): + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/var/www/decpinfo/src/db.py", line 23, in should_rebuild + return parquet_path.stat().st_mtime > db_path.stat().st_mtime + ^^^^^^^^^^^^^^^^^^^ + File "/usr/lib/python3.11/pathlib.py", line 1014, in stat + return os.stat(self, follow_symlinks=follow_symlinks) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +FileNotFoundError: [Errno 2] No such file or directory: '/srv/shared/decp/prod/dist/decp.parquet' +[2026-05-01 08:00:30 +0200] [2216552] [INFO] Worker exiting (pid: 2216552) +[2026-05-01 08:00:30 +0200] [2216548] [ERROR] Worker (pid:2216549) exited with code 3 +[2026-05-01 08:00:30 +0200] [2216548] [ERROR] Worker (pid:2216550) was sent SIGTERM! +[2026-05-01 08:00:30 +0200] [2216548] [ERROR] Worker (pid:2216552) was sent SIGTERM! +[2026-05-01 08:00:30 +0200] [2216548] [ERROR] Worker (pid:2216551) was sent SIGTERM! +[2026-05-01 08:00:31 +0200] [2216548] [ERROR] Shutting down: Master +[2026-05-01 08:00:31 +0200] [2216548] [ERROR] Reason: Worker failed to boot. diff --git a/docs/superpowers/plans/2026-03-18-observatoire-localstorage-filters.md b/docs/superpowers/plans/2026-03-18-observatoire-localstorage-filters.md new file mode 100644 index 0000000..cea3c3b --- /dev/null +++ b/docs/superpowers/plans/2026-03-18-observatoire-localstorage-filters.md @@ -0,0 +1,318 @@ +# Observatoire localStorage Filter Persistence Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Persist all 10 observatoire filter values in `localStorage` so they survive navigation and page reloads; URL params (`acheteur_id` / `titulaire_id`) override stored values. + +**Architecture:** Add `dcc.Store(storage_type="local", id="observatoire-filters")` to the observatoire layout. A new save callback writes all 10 filter values to the store on any change (`prevent_initial_call=True` to avoid wiping localStorage on page mount). The existing `restore_filters_from_url` callback is expanded: it now outputs all 10 filters, accepts the store as `State`, and prioritises URL params over stored values. + +**Tech Stack:** Dash 3.4 `dcc.Store` with `storage_type="local"`, Dash callbacks (Input / Output / State), pytest + Selenium (`DashComposite`). + +--- + +## File Map + +| Action | File | +| ------ | --------------------------------------------------------------------- | +| Modify | `src/pages/observatoire.py` — layout + 2 callbacks | +| Modify | `tests/test_main.py` — add `test_009_observatoire_filter_persistence` | + +--- + +### Task 1: Write a failing integration test + +**Files:** + +- Modify: `tests/test_main.py` + +- [ ] **Step 1: Add test_009 at the end of `tests/test_main.py`** + +```python +def test_009_observatoire_filter_persistence(dash_duo: DashComposite): + from src.app import app + + dash_duo.start_server(app) + dash_duo.wait_for_text_to_equal(".logo > h1", "decp.info", timeout=4) + + # Clear localStorage to start from a clean state + dash_duo.driver.execute_script("localStorage.clear()") + + # Navigate to observatoire without URL params + dash_duo.wait_for_page(f"{dash_duo.server_url}/observatoire") + dash_duo.wait_for_element("#dashboard_acheteur_id", timeout=4) + + # Set the acheteur_id text input + # dcc.Input without debounce fires the save callback on every keystroke, + # so the value is written to localStorage as soon as typing finishes. + acheteur_input = dash_duo.find_element("#dashboard_acheteur_id") + dash_duo.clear_input(acheteur_input) + acheteur_input.send_keys("999000000000") + + import time + time.sleep(0.3) # allow the save callback to complete + + # Navigate away + dash_duo.wait_for_page(f"{dash_duo.server_url}/") + + # Navigate back without URL params + dash_duo.wait_for_page(f"{dash_duo.server_url}/observatoire") + dash_duo.wait_for_element("#dashboard_acheteur_id", timeout=4) + + import time + time.sleep(0.5) # allow callback chain to complete + + acheteur_input = dash_duo.find_element("#dashboard_acheteur_id") + assert acheteur_input.get_attribute("value") == "999000000000", ( + "acheteur_id should be restored from localStorage after navigating back" + ) + + # Also verify URL params still override localStorage + dash_duo.wait_for_page( + f"{dash_duo.server_url}/observatoire?acheteur_id=123" + ) + dash_duo.wait_for_element("#dashboard_acheteur_id", timeout=4) + time.sleep(0.5) + + acheteur_input = dash_duo.find_element("#dashboard_acheteur_id") + assert acheteur_input.get_attribute("value") == "123", ( + "URL param acheteur_id should override the value stored in localStorage" + ) +``` + +- [ ] **Step 2: Run the test to verify it fails** + +```bash +pytest tests/test_main.py::test_009_observatoire_filter_persistence -v +``` + +Expected: **FAIL** — the input value after navigating back will be `""`, not `"999000000000"`. + +- [ ] **Step 3: Commit** + +```bash +git add tests/test_main.py +git commit -m "test: failing test for observatoire localStorage filter persistence #65" +``` + +--- + +### Task 2: Add `dcc.Store` to the layout and debounce the text inputs + +**Files:** + +- Modify: `src/pages/observatoire.py` + +- [ ] **Step 1: Add the Store component to the layout** + +In `src/pages/observatoire.py`, add this line immediately after the existing `dcc.Location` (line 123): + +```python +dcc.Store(id="observatoire-filters", storage_type="local"), +``` + +So the top of the `layout` list becomes: + +```python +layout = [ + dcc.Location(id="dashboard_url", refresh="callback-nav"), + dcc.Store(id="observatoire-filters", storage_type="local"), + dbc.Modal( + ... +``` + +- [ ] **Step 2: Add `debounce=True` to both text inputs** + +`dcc.Input` without debounce fires on every keystroke, which would trigger the save callback (and the existing heavy `udpate_dashboard_cards` callback) on every character typed. `debounce=True` delays the callback until the user presses Enter or moves focus away. + +Change `dashboard_acheteur_id` (around line 178): + +```python +dcc.Input( + id="dashboard_acheteur_id", + placeholder="SIRET", + debounce=True, + style={"width": "100%"}, +), +``` + +Change `dashboard_titulaire_id` (around line 209): + +```python +dcc.Input( + id="dashboard_titulaire_id", + placeholder="SIRET", + debounce=True, + style={"width": "100%"}, +), +``` + +- [ ] **Step 3: Run the test — still fails (restore not implemented yet)** + +```bash +pytest tests/test_main.py::test_009_observatoire_filter_persistence -v +``` + +Expected: still **FAIL**. + +- [ ] **Step 4: Commit** + +```bash +git add src/pages/observatoire.py +git commit -m "feat: add dcc.Store and debounce text inputs on observatoire page #65" +``` + +--- + +### Task 3: Add a save-to-localStorage callback + +**Files:** + +- Modify: `src/pages/observatoire.py` + +- [ ] **Step 1: Add the save callback after the existing `restore_filters_from_url` callback** + +```python +@callback( + Output("observatoire-filters", "data"), + Input("dashboard_year", "value"), + Input("dashboard_acheteur_id", "value"), + Input("dashboard_acheteur_categorie", "value"), + Input("dashboard_acheteur_departement_code", "value"), + Input("dashboard_titulaire_id", "value"), + Input("dashboard_titulaire_categorie", "value"), + Input("dashboard_titulaire_departement_code", "value"), + Input("dashboard_marche_type", "value"), + Input("dashboard_marche_considerationsSociales", "value"), + Input("dashboard_marche_considerationsEnvironnementales", "value"), + prevent_initial_call=True, +) +def save_filters_to_storage( + year, + acheteur_id, + acheteur_categorie, + acheteur_departement_code, + titulaire_id, + titulaire_categorie, + titulaire_departement_code, + marche_type, + considerations_sociales, + considerations_environnementales, +): + return { + "year": year, + "acheteur_id": acheteur_id, + "acheteur_categorie": acheteur_categorie, + "acheteur_departement_code": acheteur_departement_code, + "titulaire_id": titulaire_id, + "titulaire_categorie": titulaire_categorie, + "titulaire_departement_code": titulaire_departement_code, + "marche_type": marche_type, + "considerations_sociales": considerations_sociales, + "considerations_environnementales": considerations_environnementales, + } +``` + +**Why `prevent_initial_call=True`:** Without it, this callback fires on every page load with all-`None` values (the component defaults), immediately overwriting any saved filters. Setting `prevent_initial_call=True` skips that first call; subsequent user-driven changes still fire normally. + +- [ ] **Step 2: Run the test — still fails (restore callback not updated yet)** + +```bash +pytest tests/test_main.py::test_009_observatoire_filter_persistence -v +``` + +Expected: still **FAIL**. + +- [ ] **Step 3: Commit** + +```bash +git add src/pages/observatoire.py +git commit -m "feat: save observatoire filters to localStorage on change #65" +``` + +--- + +### Task 4: Update the restore callback to read from localStorage + +**Files:** + +- Modify: `src/pages/observatoire.py` + +This is the core change. Replace the existing `restore_filters_from_url` callback entirely. + +- [ ] **Step 1: Replace the existing callback** + +Remove the current `restore_filters_from_url` function and its `@callback` decorator (lines 302–323) and replace with: + +```python +@callback( + Output("dashboard_year", "value"), + Output("dashboard_acheteur_id", "value"), + Output("dashboard_acheteur_categorie", "value"), + Output("dashboard_acheteur_departement_code", "value"), + Output("dashboard_titulaire_id", "value"), + Output("dashboard_titulaire_categorie", "value"), + Output("dashboard_titulaire_departement_code", "value"), + Output("dashboard_marche_type", "value"), + Output("dashboard_marche_considerationsSociales", "value"), + Output("dashboard_marche_considerationsEnvironnementales", "value"), + Input("dashboard_url", "search"), + Input("dashboard_url", "pathname"), + State("observatoire-filters", "data"), +) +def restore_filters(search, _pathname, stored_filters): + # URL params take absolute priority: clear all other filters and apply only + # the values present in the URL (acheteur_id and/or titulaire_id). + if search: + params = urllib.parse.parse_qs(search.lstrip("?")) + acheteur_id = (params.get("acheteur_id") or [None])[0] or None + titulaire_id = (params.get("titulaire_id") or [None])[0] or None + if acheteur_id or titulaire_id: + return None, acheteur_id, None, None, titulaire_id, None, None, None, None, None + + # No URL params: restore from localStorage if available. + if stored_filters: + return ( + stored_filters.get("year"), + stored_filters.get("acheteur_id"), + stored_filters.get("acheteur_categorie"), + stored_filters.get("acheteur_departement_code"), + stored_filters.get("titulaire_id"), + stored_filters.get("titulaire_categorie"), + stored_filters.get("titulaire_departement_code"), + stored_filters.get("marche_type"), + stored_filters.get("considerations_sociales"), + stored_filters.get("considerations_environnementales"), + ) + + return (no_update,) * 10 +``` + +**Key design decisions:** + +- `Input("dashboard_url", "pathname")` added alongside `search`: guarantees the callback fires on every navigation to the observatoire page, even when `search` stays `""` (no query string) across two navigations. The `_pathname` value itself is unused in the body; it exists only as a trigger. +- `State("observatoire-filters", "data")` (not `Input`): the store triggers no re-run when the save callback writes to it, avoiding an infinite loop. +- When URL params are present, _all_ non-URL filters are cleared (`None`), which causes the save callback to overwrite localStorage with only the URL-provided values. +- `(no_update,) * 10` when there is nothing to restore: leaves defaults intact. + +- [ ] **Step 2: Run the target test** + +```bash +pytest tests/test_main.py::test_009_observatoire_filter_persistence -v +``` + +Expected: **PASS**. + +- [ ] **Step 3: Run the full test suite** + +```bash +pytest -v +``` + +Expected: all tests **PASS**. Pay special attention to `test_006` and `test_007` (URL → input / share URL), which exercise the code path that was just rewritten. + +- [ ] **Step 4: Commit** + +```bash +git add src/pages/observatoire.py +git commit -m "feat: restore observatoire filters from localStorage on page load #65" +``` diff --git a/magali.html b/magali.html new file mode 100644 index 0000000..249b571 --- /dev/null +++ b/magali.html @@ -0,0 +1,2544 @@ + + + + + + + Portail Commande Publique — Bêta + + + + + + + +
+ Données en temps réel : + BOAMP + DECP data.gouv.fr + APProch data.gouv.fr +
+ + +
+
+
+

+ Et si tous les marchés publics étaient accessibles en un point + unique ? +

+

+ Consultations en cours · Programmations prévisionnelles · Données + d'attribution +

+
+
+ + + + +
+ + +
+
+ + + + + + +
+
+
+ +
+
+
🔍
+

+ Saisissez le nom d'un acheteur public, un objet de marché ou un code + CPV.
+ Exemple : Euroméditerranée · Métropole Aix-Marseille · MOE + aménagement · 45 +

+
+ + +
+
+ + +
+
+
+

Observatoire de la Commande Publique

+

+ Vue nationale sur les données d'attribution · Filtrez par année, + secteur ou territoire +

+
+
+ Année + +
+
+ Type d'acheteur + +
+
+ Recherche libre + +
+ +
+
+
+ +
+ +
+ + + + + Vue par acheteur · Top acheteurs et leurs fournisseurs +
+ + +
+
+
+ Vue nationale · Toutes données DECP +
+
Chargement en cours…
+
+
+ LIVE DECP data.economie.gouv.fr +
+
+ + +
+ ⚠️ Couverture partielle — Ces données représentent + les marchés publiés en open data (DECP ≥ 40 000 €). Les consultations + en cours (PLACE) et les programmations prévisionnelles (APProch) ne + sont pas encore intégrées à l'échelle nationale — c'est précisément + l'objet de ce portail. +
+ + +
+
+
+
Marchés attribués
+
LIVE
+
+
+
+
Volume total
+
LIVE
+
+
+
+
Montant moyen
+
LIVE
+
+
+
+
Titulaires distincts
+
LIVE
+
+
+ + +
+
+
+
+
Volume attribué par année
+
DECP · date de notification
+
+ LIVE +
+
+
+
+
+ Chargement… +
+
+
+
+
+
+
+
+ Top titulaires par volume +
+
Toutes années
+
+ LIVE +
+
+ + + + + + + + + + + + + + +
#TitulaireVolumeNb
+ Chargement… +
+
+
+
+
+
+
Répartition par type de marché
+
DECP · code CPV
+
+ LIVE +
+
+
+
+
+ Chargement… +
+
+
+
+
+
+
+
+ Top acheteurs par volume +
+
+ Pour cette sélection +
+
+ LIVE +
+
+
+
+
+ Chargement… +
+
+
+
+
+ +
+
🔗
+
+
+ Ces données sont propulsées par decp.info +
+
+ Ce portail s'appuie sur le travail de Colin Maudry (decp.info + 2.7.0). Analyses avancées, tendances nationales et données + enrichies disponibles sur son Observatoire. +
+
+ Voir l'Observatoire → +
+
+
+ + + + + + diff --git a/magali_ori.html b/magali_ori.html new file mode 100644 index 0000000..22d718c --- /dev/null +++ b/magali_ori.html @@ -0,0 +1,2583 @@ + + + + + + + Portail Commande Publique — Bêta + + + + + + + +
+ Données en temps réel : + BOAMP + DECP data.gouv.fr + APProch data.gouv.fr +
+ + +
+
+
+

+ Et si tous les marchés publics étaient accessibles en un point + unique ? +

+

+ Consultations en cours · Programmations prévisionnelles · Données + d'attribution +

+
+
+ + + + +
+ + +
+
+ + + + + + +
+
+
+ +
+
+
🔍
+

+ Saisissez le nom d'un acheteur public, un objet de marché ou un code + CPV.
+ Exemple : Euroméditerranée · Métropole Aix-Marseille · MOE + aménagement · 45 +

+
+ + +
+
+ + +
+
+
+

Observatoire de la Commande Publique

+

+ Vue nationale sur les données d'attribution · Filtrez par année, + secteur ou territoire +

+
+
+ Année + +
+
+ Type d'acheteur + +
+
+ Recherche libre + +
+ +
+
+
+ +
+ +
+ + + + + Vue par acheteur · Top acheteurs et leurs fournisseurs +
+ + +
+
+
+ Vue nationale · Toutes données DECP +
+
Chargement en cours…
+
+
+ LIVE DECP data.economie.gouv.fr +
+
+ + +
+ ⚠️ Couverture partielle — Ces données représentent + les marchés publiés en open data (DECP ≥ 40 000 €). Les consultations + en cours (PLACE) et les programmations prévisionnelles (APProch) ne + sont pas encore intégrées à l'échelle nationale — c'est précisément + l'objet de ce portail. +
+ + +
+
+
+
Marchés attribués
+
LIVE
+
+
+
+
Volume total
+
LIVE
+
+
+
+
Montant moyen
+
LIVE
+
+
+
+
Titulaires distincts
+
LIVE
+
+
+ + +
+
+
+
+
Volume attribué par année
+
DECP · date de notification
+
+ LIVE +
+
+
+
+
+ Chargement… +
+
+
+
+
+
+
+
+ Top titulaires par volume +
+
Toutes années
+
+ LIVE +
+
+ + + + + + + + + + + + + + +
#TitulaireVolumeNb
+ Chargement… +
+
+
+
+
+
+
Répartition par type de marché
+
DECP · code CPV
+
+ LIVE +
+
+
+
+
+ Chargement… +
+
+
+
+
+
+
+
+ Top acheteurs par volume +
+
+ Pour cette sélection +
+
+ LIVE +
+
+
+
+
+ Chargement… +
+
+
+
+
+ +
+
🔗
+
+
+ Ces données sont propulsées par decp.info +
+
+ Ce portail s'appuie sur le travail de Colin Maudry (decp.info + 2.7.0). Analyses avancées, tendances nationales et données + enrichies disponibles sur son Observatoire. +
+
+ Voir l'Observatoire → +
+
+
+ + + + + + diff --git a/profil_dacheteur_specs.md b/profil_dacheteur_specs.md new file mode 100644 index 0000000..d7b983a --- /dev/null +++ b/profil_dacheteur_specs.md @@ -0,0 +1,189 @@ +# Spécifications fonctionnelles et techniques d'un profil d'acheteur + +> Source : [Arrêté du 22 mars 2019](https://www.legifrance.gouv.fr/loda/id/JORFTEXT000038318516) relatif aux fonctionnalités et exigences minimales des profils d'acheteurs (NOR : ECOM1831551A). Annexe 7 du Code de la commande publique. En vigueur depuis le 1er avril 2019. + +## 1. Fonctionnalités pour l'acheteur (marchés publics) + +_Réf. : Article 1, I_ + +Le profil d'acheteur doit permettre à l'acheteur de : + +| ID | Fonctionnalité | Description | +| ------ | ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | +| ACH-01 | Identification et authentification | S'identifier et s'authentifier sur la plateforme | +| ACH-02 | Publication d'avis | Publier des avis d'appel à la concurrence et leurs éventuelles modifications | +| ACH-03 | Mise à disposition des DCE | Mettre à disposition des documents de la consultation | +| ACH-04 | Réception des candidatures | Réceptionner et conserver des candidatures, y compris sous forme de DUME (Document Unique de Marché Européen) électronique (échange de données structurées) | +| ACH-05 | Réception des offres | Réceptionner et conserver des offres, y compris hors délais | +| ACH-06 | Données essentielles | Compléter un formulaire de publication des données essentielles (DECP), ou importer ces données depuis un autre SI | +| ACH-07 | Courrier électronique | Accéder à un service de courrier électronique (au sens de l'article 1 de la loi n° 2004-575) | +| ACH-08 | Historique et traçabilité | Accéder à un historique des événements : enregistrement et traçabilité des actions (retrait, dépôt de documents, etc.) | +| ACH-09 | Réponses aux questions | Répondre aux questions soumises par les entreprises | +| ACH-10 | Justificatifs et preuves | Obtenir les documents justificatifs et moyens de preuve directement auprès d'autres administrations lorsque c'est possible | + +## 2. Fonctionnalités pour l'opérateur économique (marchés publics) + +_Réf. : Article 1, II_ + +Le profil d'acheteur doit permettre à l'opérateur économique de : + +| ID | Fonctionnalité | Description | +| ----- | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| OE-01 | Identification et authentification | S'identifier et s'authentifier sur la plateforme | +| OE-02 | Prérequis techniques | Connaître les prérequis techniques et les modules d'extension nécessaires pour utiliser le profil d'acheteur | +| OE-03 | Test de configuration | Accéder à un espace de test permettant de vérifier l'adéquation de la configuration du poste de travail avec les prérequis techniques | +| OE-04 | Recherche | Effectuer une recherche donnant accès aux avis d'appel à la concurrence, aux consultations et aux données essentielles | +| OE-05 | Consultation des documents | Consulter et télécharger en accès gratuit, libre, direct et complet les documents de la consultation, les avis d'appel à la concurrence et leurs modifications | +| OE-06 | Simulation de dépôt | Accéder à un espace permettant de simuler le dépôt de documents | +| OE-07 | Dépôt de candidature | Déposer une candidature, y compris sous forme de DUME électronique (données structurées) | +| OE-08 | Dépôt d'offres | Déposer des offres, y compris les dépôts successifs (quand la procédure le requiert) et les offres signées électroniquement | +| OE-09 | Assistance | Solliciter une assistance ou consulter un support utilisateur pour les problématiques techniques | +| OE-10 | Questions à l'acheteur | Formuler des questions à l'acheteur | +| OE-11 | Données essentielles | Consulter et télécharger les données essentielles de la commande publique | + +## 3. Exigences techniques, de sécurité et d'accessibilité (marchés publics) + +_Réf. : Article 2_ + +### 3.1. Conformité aux référentiels + +| ID | Exigence | Référentiel | +| ------ | ---------------- | ------------------------------------------------------------------------------------------------ | +| SEC-01 | Sécurité | Conformité au Référentiel Général de Sécurité (RGS) — art. 9 de l'ordonnance n° 2005-1516 | +| SEC-02 | Interopérabilité | Conformité au Référentiel Général d'Interopérabilité (RGI) — art. 9 de l'ordonnance n° 2005-1516 | +| SEC-03 | Accessibilité | Conformité au Référentiel Général d'Accessibilité (RGAA) — art. 11 de l'ordonnance n° 2005-1516 | + +### 3.2. Exigences techniques + +| ID | Exigence | Description | +| ------- | ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| TECH-01 | Formats de fichiers | Accepter les fichiers communément disponibles, et notamment les formats `.XML` et `.JSON` | +| TECH-02 | Information sur les formats | Indiquer la taille et les formats des documents et avis d'appel à la concurrence | +| TECH-03 | Horodatage qualifié | L'horodatage doit être qualifié conformément au règlement eIDAS (n° 910/2014) | +| TECH-04 | Intégrité des données | Assurer l'intégrité des données | +| TECH-05 | Responsive design | Permettre une visualisation adaptée au média utilisé (responsive) | +| TECH-06 | Confidentialité des soumissions | Garantir la confidentialité des candidatures, offres et demandes de participation jusqu'à l'expiration du délai de soumission. Les documents sont inaccessibles avant cette date, puis accessibles uniquement aux personnes autorisées. Recours obligatoire à des moyens de cryptologie, de gestion des droits d'accès/privilèges, ou technique équivalente | +| TECH-07 | Interopérabilité | Être interopérable avec les autres outils et dispositifs de communication électronique et d'échanges d'informations utilisés dans le cadre de la commande publique | + +### 3.3. Accusé de réception + +_Réf. : Article 2, III_ + +Chaque dépôt de documents par un opérateur économique doit déclencher **immédiatement** l'envoi d'un accusé de réception automatique contenant : + +| ID | Champ | Description | +| ----- | -------------------------- | -------------------------------------------------------- | +| AR-01 | Identification du déposant | Identification de l'opérateur économique auteur du dépôt | +| AR-02 | Nom de l'acheteur | Nom de l'acheteur public | +| AR-03 | Consultation | Intitulé et objet de la consultation concernée | +| AR-04 | Horodatage | Date et heure de réception des documents | +| AR-05 | Liste des documents | Liste détaillée des documents transmis | + +## 4. Fonctionnalités spécifiques aux concessions + +_Réf. : Article 3_ + +### 4.1. Fonctionnalités pour l'autorité concédante + +| ID | Fonctionnalité | Description | +| ----------- | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | +| CONC-ACH-01 | Identification et authentification | S'identifier et s'authentifier | +| CONC-ACH-02 | Mise à disposition des DCE | Mettre à disposition des documents de la consultation | +| CONC-ACH-03 | Réception des candidatures | Réceptionner et conserver des candidatures | +| CONC-ACH-04 | Réception des offres | Réceptionner et conserver des offres, y compris hors délais | +| CONC-ACH-05 | Données essentielles | Compléter un formulaire de publication des données essentielles ou importer ces données depuis un autre SI | +| CONC-ACH-06 | Historique et traçabilité | Accéder à un historique des événements : enregistrement et traçabilité des actions (retrait, dépôt de documents, etc.) | + +### 4.2. Fonctionnalités pour l'opérateur économique (concessions) + +| ID | Fonctionnalité | Description | +| ---------- | ---------------------------------- | ---------------------------------------------------------------------------------------------------- | +| CONC-OE-01 | Identification et authentification | S'identifier et s'authentifier | +| CONC-OE-02 | Prérequis techniques | Connaître les prérequis techniques et modules d'extension nécessaires | +| CONC-OE-03 | Test de configuration | Accéder à un espace de test de la configuration du poste de travail | +| CONC-OE-04 | Recherche | Effectuer une recherche donnant accès aux consultations et aux données essentielles | +| CONC-OE-05 | Consultation des documents | Consulter et télécharger en accès gratuit, libre, direct et complet les documents de la consultation | +| CONC-OE-06 | Simulation de dépôt | Accéder à un espace de simulation de dépôt de documents | +| CONC-OE-07 | Dépôt de candidature | Déposer une candidature | +| CONC-OE-08 | Dépôt d'offres | Déposer des offres | +| CONC-OE-09 | Assistance | Solliciter une assistance ou consulter un support utilisateur | +| CONC-OE-10 | Données essentielles | Consulter et télécharger les données essentielles | + +### 4.3. Exigences techniques (concessions) + +| ID | Exigence | Description | +| ----------- | --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| CONC-SEC-01 | Confidentialité | Garantir la confidentialité des candidatures, offres et demandes de participation jusqu'à expiration du délai. Recours à la cryptologie, gestion des droits d'accès, ou technique équivalente | +| CONC-SEC-02 | Intégrité | Assurer l'intégrité des données | +| CONC-SEC-03 | Responsive design | Permettre une visualisation adaptée au média utilisé | +| CONC-SEC-04 | Conformité aux référentiels | Conformité au RGS, RGI et RGAA (art. 9 et 11 de l'ordonnance n° 2005-1516) | + +### 4.4. Accusé de réception (concessions) + +Mêmes exigences que pour les marchés publics (section 3.3), à l'exception du champ AR-02 qui mentionne le nom de **l'autorité concédante** au lieu de l'acheteur public. + +## 5. Référencement du profil d'acheteur + +_Réf. : Article 4_ + +### 5.1. Publication + +Le profil d'acheteur doit figurer sur une liste publiée sur le portail unique interministériel de données ouvertes (data.gouv.fr). + +### 5.2. Identification + +Chaque profil d'acheteur est identifié par : + +| ID | Champ | Description | +| ------ | ------------- | ------------------------------------------------------ | +| REF-01 | SIRET | Numéro SIRET de l'acheteur | +| REF-02 | URL du profil | Adresse URL du profil d'acheteur | +| REF-03 | URL du DCAT | Adresse URL du catalogue DCAT des données essentielles | +| REF-04 | Coordonnées | Coordonnées du ou des acheteurs concernés | + +### 5.3. Déclaration + +La déclaration du profil est effectuée par l'acheteur ou une personne habilitée sur le portail de données ouvertes. Elle comporte : + +- L'identité du déclarant +- L'identité de l'organisme chargé de la gestion du profil d'acheteur +- L'adresse URL du profil d'acheteur +- L'adresse URL du DCAT +- Les coordonnées du ou des acheteurs concernés + +## 6. Dispositions particulières outre-mer + +_Réf. : Article 5_ + +| Territoire | Adaptation | +| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Saint-Barthélemy, Saint-Pierre-et-Miquelon | Horodatage qualifié conformément aux dispositions applicables en métropole en vertu du règlement eIDAS | +| Nouvelle-Calédonie, Polynésie française | Horodatage qualifié conformément aux dispositions applicables localement. Applicable uniquement aux contrats de l'État et de ses établissements publics | +| Wallis-et-Futuna, TAAF | Horodatage qualifié conformément aux dispositions applicables en métropole en vertu du règlement eIDAS. Applicable uniquement aux contrats de l'État et de ses établissements publics | + +## 7. Matrice de correspondance fonctionnelle + +Récapitulatif des fonctionnalités par type de contrat et rôle utilisateur : + +| Fonctionnalité | Acheteur (marchés) | OE (marchés) | Autorité concédante | OE (concessions) | +| ------------------------------------------ | :----------------: | :----------: | :-----------------: | :--------------: | +| Authentification | x | x | x | x | +| Publication d'avis | x | | | | +| Mise à disposition DCE | x | | x | | +| Consultation/téléchargement DCE | | x | | x | +| Réception candidatures | x | | x | | +| Dépôt candidature (dont DUME) | | x | | x | +| Réception offres | x | | x | | +| Dépôt offres (dont signature électronique) | | x | | x | +| Données essentielles (saisie/import) | x | | x | | +| Données essentielles (consultation) | | x | | x | +| Courrier électronique | x | | | | +| Historique/traçabilité | x | | x | | +| Réponse aux questions | x | | | | +| Questions à l'acheteur | | x | | | +| Justificatifs via autres administrations | x | | | | +| Prérequis techniques | | x | | x | +| Test de configuration | | x | | x | +| Recherche | | x | | x | +| Simulation de dépôt | | x | | x | +| Assistance/support | | x | | x | diff --git a/sasu.ipynb b/sasu.ipynb new file mode 100644 index 0000000..6678a9e --- /dev/null +++ b/sasu.ipynb @@ -0,0 +1,197 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "c5b66458e29113e9", + "metadata": {}, + "outputs": [], + "source": [ + "import polars as pl\n", + "\n", + "pl.Config(\n", + " fmt_str_lengths=120,\n", + " fmt_table_cell_list_len=50,\n", + " set_tbl_rows=100,\n", + " tbl_cols=-1,\n", + ")\n", + "\n", + "for methode_dividendes in (\"flat\", \"progressif\"):\n", + " df: pl.DataFrame = pl.DataFrame()\n", + "\n", + " for i in range(0, 11):\n", + " benefice_reel = 3000\n", + " ca = 50000\n", + " depenses_pro = 5000\n", + " remuneration_plus_is = ca - depenses_pro - benefice_reel\n", + " is_minimal = benefice_reel * 0.15\n", + "\n", + " salaires_brut = (remuneration_plus_is - is_minimal) * i / 10\n", + "\n", + " def taux_dividendes():\n", + " taux = {}\n", + " if methode_dividendes == \"flat\":\n", + " taux = {\"retenue_source\": 0.7, \"abattement\": 0}\n", + " elif methode_dividendes == \"progressif\":\n", + " taux = {\"retenue_source\": 0.828, \"abattement\": 0.6}\n", + " return taux\n", + "\n", + " def calcul_cout_dividendes():\n", + " return remuneration_plus_is - salaires_brut - is_minimal\n", + "\n", + " def calcul_dividendes_bruts():\n", + " return calcul_cout_dividendes() / 1.15\n", + "\n", + " def calcul_cout_remuneration():\n", + " return calcul_dividendes_bruts() * 1.15 + salaires_brut\n", + "\n", + " def calcul_dividendes_net():\n", + " dividendes_bruts = calcul_dividendes_bruts()\n", + " dividendes_net = dividendes_bruts * taux_dividendes()[\"retenue_source\"]\n", + " return dividendes_net\n", + "\n", + " def calcul_salaires_net():\n", + " return salaires_brut * 0.563\n", + "\n", + " def calcul_revenu_imposable():\n", + " return (calcul_salaires_net() * 0.9) + (\n", + " calcul_dividendes_net() * taux_dividendes()[\"abattement\"]\n", + " )\n", + "\n", + " def calcul_revenu_net():\n", + " return calcul_salaires_net() + calcul_dividendes_net()\n", + "\n", + " def calcul_benefice():\n", + " return ca - depenses_pro - salaires_brut\n", + "\n", + " def calcul_is():\n", + " return (calcul_benefice() - is_minimal) * 0.15\n", + "\n", + " def calcul_ir(revenu):\n", + " tranches_ir = [[11488, 0], [29315, 0.11], [83283, 0.3]]\n", + " revenu_restant = revenu\n", + " ir = 0\n", + " for tranche in tranches_ir:\n", + " if revenu_restant > 0:\n", + " ir_tranche = min(revenu_restant, tranche[0]) * tranche[1]\n", + " ir = +ir_tranche\n", + " revenu_restant = revenu_restant - tranche[0]\n", + "\n", + " return ir\n", + "\n", + " def calcul_revenu_ae():\n", + " apres_cotisations = calcul_cout_remuneration() * 0.76 - depenses_pro\n", + " impots = calcul_ir(apres_cotisations * 0.66)\n", + " return apres_cotisations - impots\n", + "\n", + " def fmt(value):\n", + " return str(int(value))\n", + "\n", + " # print(salaires_brut)\n", + " # print(calcul_cout_dividendes())\n", + " # print(calcul_dividendes_bruts())\n", + " # print(calcul_is())\n", + " # print(\"\")\n", + " # print(\"\")\n", + " #\n", + " # print(salaires_brut + calcul_dividendes_bruts() + calcul_is(), \" == \", remuneration_plus_is)\n", + "\n", + " # assert salaires_brut + calcul_dividendes_bruts() + calcul_is() == remuneration_plus_is\n", + "\n", + " row: dict = {\n", + " \"Salaires bruts\": [fmt(salaires_brut / 12)],\n", + " \"Dividendes bruts\": [fmt(calcul_cout_dividendes() / 12)],\n", + " \"Benefice\": [fmt(calcul_benefice())],\n", + " \"Benefice réél\": [\n", + " fmt(calcul_benefice() - calcul_is() - calcul_dividendes_bruts())\n", + " ],\n", + " \"IS\": [fmt(calcul_is())],\n", + " \"IR\": [fmt(calcul_ir(calcul_revenu_imposable()) / 12)],\n", + " \"IS + IR\": [fmt(calcul_is() + calcul_ir(calcul_revenu_imposable()))],\n", + " \"Salaires net\": [fmt(calcul_salaires_net() / 12)],\n", + " \"Dividendes net\": [fmt(calcul_dividendes_net() / 12)],\n", + " \"Revenu net ap. impôts\": [\n", + " fmt((calcul_revenu_net() - calcul_ir(calcul_revenu_imposable())) / 12)\n", + " ],\n", + " \"Revenue AE ap. impôts\": [fmt(calcul_revenu_ae() / 12)],\n", + " }\n", + "\n", + " df = pl.concat([df, pl.from_dict(row)])\n", + "\n", + " print(df)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "initial_id", + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "from dash import ALL, Dash, Input, Output, Patch, State, callback, dcc, html\n", + "\n", + "app = Dash()\n", + "\n", + "app.layout = html.Div(\n", + " [\n", + " html.Button(\"Add Filter\", id=\"add-filter-btn\", n_clicks=0),\n", + " html.Div(id=\"dropdown-container-div\", children=[]),\n", + " html.Div(id=\"dropdown-container-output-div\"),\n", + " ]\n", + ")\n", + "\n", + "\n", + "@callback(\n", + " Output(\"dropdown-container-div\", \"children\"), Input(\"add-filter-btn\", \"n_clicks\")\n", + ")\n", + "def display_dropdowns(n_clicks):\n", + " patched_children = Patch()\n", + " new_dropdown = dcc.Dropdown(\n", + " [\"NYC\", \"MTL\", \"LA\", \"TOKYO\"],\n", + " id={\"type\": \"city-filter-dropdown\", \"index\": n_clicks},\n", + " )\n", + " patched_children.append(new_dropdown)\n", + " return patched_children\n", + "\n", + "\n", + "@callback(\n", + " Output(\"dropdown-container-output-div\", \"children\"),\n", + " Input({\"type\": \"city-filter-dropdown\", \"index\": ALL}, \"value\"),\n", + " State({\"type\": \"city-dynamic-dropdown\", \"index\": ALL}, \"id\"),\n", + ")\n", + "def display_output(values, ids):\n", + " return html.Div(\n", + " [html.Div(f\"Dropdown {i + 1} = {value}\") for (i, value) in enumerate(values)]\n", + " + [html.P(ids)],\n", + " )\n", + "\n", + "\n", + "if __name__ == \"__main__\":\n", + " app.run(debug=True)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/src/assets/colibre_text_no_shade.png b/src/assets/colibre_text_no_shade.png new file mode 100644 index 0000000..6f4e790 Binary files /dev/null and b/src/assets/colibre_text_no_shade.png differ diff --git a/src/assets/decp.info _ présentation et perspectives pdf b/src/assets/decp.info _ présentation et perspectives pdf new file mode 100644 index 0000000..b8c107a Binary files /dev/null and b/src/assets/decp.info _ présentation et perspectives pdf differ diff --git a/src/assets/logo.xcf b/src/assets/logo.xcf new file mode 100644 index 0000000..fb66d57 Binary files /dev/null and b/src/assets/logo.xcf differ diff --git a/src/assets/logo_bicolor.xcf b/src/assets/logo_bicolor.xcf new file mode 100644 index 0000000..f017d64 Binary files /dev/null and b/src/assets/logo_bicolor.xcf differ diff --git a/src/backup/service.py b/src/backup/service.py index c88e3ca..08ee401 100644 --- a/src/backup/service.py +++ b/src/backup/service.py @@ -45,15 +45,18 @@ def restore(config: BackupConfig, storage: Storage, key: str, now: datetime) -> with tempfile.NamedTemporaryFile(suffix=".sqlite", delete=False) as tmp: tmp_path = Path(tmp.name) - snapshot.write_snapshot(data, tmp_path) - if not snapshot.verify_integrity(tmp_path): - tmp_path.unlink(missing_ok=True) - raise ValueError(f"Sauvegarde corrompue, restauration annulée : {key}") + try: + snapshot.write_snapshot(data, tmp_path) + if not snapshot.verify_integrity(tmp_path): + raise ValueError(f"Sauvegarde corrompue, restauration annulée : {key}") - db_path = config.db_path - stamp = now.strftime("%Y%m%dT%H%M%SZ") - backup_copy = db_path.with_name(f"{db_path.name}.bak-{stamp}") - if db_path.exists(): - backup_copy.write_bytes(db_path.read_bytes()) - os.replace(tmp_path, db_path) + db_path = config.db_path + stamp = now.strftime("%Y%m%dT%H%M%SZ") + backup_copy = db_path.with_name(f"{db_path.name}.bak-{stamp}") + if db_path.exists(): + backup_copy.write_bytes(db_path.read_bytes()) + os.replace(tmp_path, db_path) + except Exception: + tmp_path.unlink(missing_ok=True) + raise return backup_copy diff --git a/test.ipynb b/test.ipynb new file mode 100644 index 0000000..4ef71ec --- /dev/null +++ b/test.ipynb @@ -0,0 +1,71 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "initial_id", + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "import duckdb\n", + "\n", + "conn = duckdb.connect(\"tests/\")\n", + "cursor = conn.cursor()\n", + "df = cursor.execute(\"select * from decp limit 1\").pl()\n", + "df.columns" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "859cd599b5befafb", + "metadata": { + "ExecuteTime": { + "end_time": "2026-04-22T15:00:32.430249Z", + "start_time": "2026-04-22T15:00:31.843470Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import polars as pl\n", + "\n", + "schema = pl.read_parquet(\"../decp-processing/decp_prod.parquet\").schema\n", + "schema[\"dureeMois\"].is_integer()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/test_aggrid.py b/test_aggrid.py new file mode 100644 index 0000000..7414e0a --- /dev/null +++ b/test_aggrid.py @@ -0,0 +1,26 @@ +import dash_ag_grid as dag +import pandas as pd +from dash import Dash, html + +df = pd.read_csv( + "https://raw.githubusercontent.com/plotly/datasets/master/gapminder2007.csv" +) + +app = Dash() + +columnDefs = [ + {"field": "country", "filter": True}, + {"field": "pop", "headerName": "Population"}, + {"field": "lifeExp", "headerName": "Life Expectancy", "filter": True}, +] + +grid = dag.AgGrid( + id="getting-started-filter", + rowData=df.to_dict("records"), + columnDefs=columnDefs, +) + +app.layout = html.Div([grid]) + +if __name__ == "__main__": + app.run(debug=True) diff --git a/tests/test.parquet b/tests/test.parquet new file mode 100644 index 0000000..d75858a Binary files /dev/null and b/tests/test.parquet differ diff --git a/tests/users.test.sqlite b/tests/users.test.sqlite new file mode 100644 index 0000000..8c70b44 Binary files /dev/null and b/tests/users.test.sqlite differ diff --git a/users.sqlite b/users.sqlite new file mode 100644 index 0000000..ef84536 Binary files /dev/null and b/users.sqlite differ diff --git a/uv.lock b/uv.lock index 3c6662a..83a3f6e 100644 --- a/uv.lock +++ b/uv.lock @@ -215,6 +215,34 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458, upload-time = "2024-11-08T17:25:46.184Z" }, ] +[[package]] +name = "boto3" +version = "1.43.36" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore" }, + { name = "jmespath" }, + { name = "s3transfer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ff/9f/897287e955db0f50b12fd69ef45956e4fd2c7ddb48c736872f7ea2314443/boto3-1.43.36.tar.gz", hash = "sha256:587d7ee92a12e440ad12b0e7f11f3358f0c4d65b19f64726efc94aaf194aff28", size = 112690, upload-time = "2026-06-23T02:47:14.561Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/f1/274303f52483ecf199eae6f8d9b6f5951670397ee4d72c06cfd4eb644612/boto3-1.43.36-py3-none-any.whl", hash = "sha256:42942dde254673abcbc9e6e60017c88341a4f49d99d24e1f2e290fb38138c26f", size = 140031, upload-time = "2026-06-23T02:47:13.178Z" }, +] + +[[package]] +name = "botocore" +version = "1.43.36" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jmespath" }, + { name = "python-dateutil" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7c/37/da9e7f6ca73ac73afd7f0bb7f238aa5daba35c081e98d7f48a7c399599c0/botocore-1.43.36.tar.gz", hash = "sha256:4cae47d1b2d426316b85a0087d9e69e048f13bc003b5177d74639fe9dfd28205", size = 15625488, upload-time = "2026-06-23T02:47:03.192Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/19/934f81592527a3f7f9b943c893e334c721a4644948642bc33885d584e9ec/botocore-1.43.36-py3-none-any.whl", hash = "sha256:3c65fdc39ed01d8dfde1e961b34038aed03c459f8ddf80717a12ac006475e49d", size = 15313630, upload-time = "2026-06-23T02:46:59.327Z" }, +] + [[package]] name = "brevo-python" version = "5.0.0rc1" @@ -826,7 +854,9 @@ name = "decp-info" version = "2.8.0" source = { virtual = "." } dependencies = [ + { name = "boto3" }, { name = "brevo-python" }, + { name = "cryptography" }, { name = "dash", extra = ["compress"] }, { name = "dash-bootstrap-components" }, { name = "dash-extensions", version = "1.0.20", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, @@ -857,6 +887,7 @@ dependencies = [ dev = [ { name = "dash", extra = ["testing"] }, { name = "fastexcel" }, + { name = "moto", extra = ["s3"] }, { name = "openpyxl" }, { name = "pre-commit" }, { name = "pytest" }, @@ -867,7 +898,9 @@ dev = [ [package.metadata] requires-dist = [ + { name = "boto3" }, { name = "brevo-python", specifier = "==5.0.0rc1" }, + { name = "cryptography" }, { name = "dash", specifier = "==3.4.0" }, { name = "dash", extras = ["compress"] }, { name = "dash-bootstrap-components" }, @@ -896,6 +929,7 @@ requires-dist = [ dev = [ { name = "dash", extras = ["testing"] }, { name = "fastexcel" }, + { name = "moto", extras = ["s3"] }, { name = "openpyxl" }, { name = "pre-commit" }, { name = "pytest" }, @@ -1273,6 +1307,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, ] +[[package]] +name = "jmespath" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/59/322338183ecda247fb5d1763a6cbe46eff7222eaeebafd9fa65d4bf5cb11/jmespath-1.1.0.tar.gz", hash = "sha256:472c87d80f36026ae83c6ddd0f1d05d4e510134ed462851fd5f754c8c3cbb88d", size = 27377, upload-time = "2026-01-22T16:35:26.279Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/2f/967ba146e6d58cf6a652da73885f52fc68001525b4197effc174321d70b4/jmespath-1.1.0-py3-none-any.whl", hash = "sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64", size = 20419, upload-time = "2026-01-22T16:35:24.919Z" }, +] + [[package]] name = "jsbeautifier" version = "1.15.4" @@ -1566,6 +1609,30 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e8/3d/1087453384dbde46a8c7f9356eead2c58be8a7bf156bca40243377c85715/more_itertools-11.1.0-py3-none-any.whl", hash = "sha256:4b65538ae22f6fed0ce4874efd317463a7489796a0939fa66824dd542125a192", size = 72226, upload-time = "2026-05-22T14:14:28.824Z" }, ] +[[package]] +name = "moto" +version = "5.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "boto3" }, + { name = "botocore" }, + { name = "cryptography" }, + { name = "requests" }, + { name = "responses" }, + { name = "werkzeug" }, + { name = "xmltodict" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/47/63/d944f387582cc53f53febbff2b3fa36a6d2ed7c1feef8990bf646cfa9cba/moto-5.2.2.tar.gz", hash = "sha256:aac8023a429e125e91c91f8f4730a67b54f518cda587352f7e67252fe3168f75", size = 8678761, upload-time = "2026-06-06T18:57:54.931Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/45/13cff46f4f617a6e97e1d497d75abd913e250bb4c823a4985668c6e593e4/moto-5.2.2-py3-none-any.whl", hash = "sha256:3817f1e39721ca833579b921e53e3b68547ace6a34d848c9486fbb5905808de9", size = 6698689, upload-time = "2026-06-06T18:57:51.435Z" }, +] + +[package.optional-dependencies] +s3 = [ + { name = "py-partiql-parser" }, + { name = "pyyaml" }, +] + [[package]] name = "multiprocess" version = "0.70.19" @@ -2136,6 +2203,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, ] +[[package]] +name = "py-partiql-parser" +version = "0.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/56/7a/a0f6bda783eb4df8e3dfd55973a1ac6d368a89178c300e1b5b91cd181e5e/py_partiql_parser-0.6.3.tar.gz", hash = "sha256:09cecf916ce6e3da2c050f0cb6106166de42c33d34a078ec2eb19377ea70389a", size = 17456, upload-time = "2025-10-18T13:56:13.441Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/33/a7cbfccc39056a5cf8126b7aab4c8bafbedd4f0ca68ae40ecb627a2d2cd3/py_partiql_parser-0.6.3-py2.py3-none-any.whl", hash = "sha256:deb0769c3346179d2f590dcbde556f708cdb929059fb654bad75f4cf6e07f582", size = 23752, upload-time = "2025-10-18T13:56:12.256Z" }, +] + [[package]] name = "pyarrow" version = "24.0.0" @@ -2518,6 +2594,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" }, ] +[[package]] +name = "responses" +version = "0.26.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyyaml" }, + { name = "requests" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c2/58/1fb6de3503428196df78638f991ec8095274f1ee9723e272ee4d9ff0092b/responses-0.26.1.tar.gz", hash = "sha256:2eb3218553cc8f79b57d257bac23af5e1bf381f5b9390b1767816f0843e01dc2", size = 83088, upload-time = "2026-05-21T19:56:39.747Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/31/6a620b4427d546b9e7cca8b3b8c5f0559d9cef2bb9eedcda7f73c1473c19/responses-0.26.1-py3-none-any.whl", hash = "sha256:8aacc4586eb08fb2208ef64a9eb4258d9b0c6e6f4260845f2f018ab847495345", size = 35502, upload-time = "2026-05-21T19:56:38.046Z" }, +] + [[package]] name = "retrying" version = "1.4.2" @@ -2527,6 +2617,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/67/f3/6cd296376653270ac1b423bb30bd70942d9916b6978c6f40472d6ac038e7/retrying-1.4.2-py3-none-any.whl", hash = "sha256:bbc004aeb542a74f3569aeddf42a2516efefcdaff90df0eb38fbfbf19f179f59", size = 10859, upload-time = "2025-08-03T03:35:23.829Z" }, ] +[[package]] +name = "s3transfer" +version = "0.19.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f6/94/dcdaeb1713cab9c84def276cfac7388b17c7d9855bbcfe88d77e4dbafd44/s3transfer-0.19.0.tar.gz", hash = "sha256:ce436931687addc4c1712d52d40b32f53e88315723f107ffa20ba82b05a0f685", size = 165171, upload-time = "2026-06-16T19:44:51.599Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/5f/4c174edad94f82de888ac00a5ddd8d07b35609b6c94f0bdf4d74af57703e/s3transfer-0.19.0-py3-none-any.whl", hash = "sha256:777cc2415536f1debadb5c2ef7779275d0fc0fe0e042411cdd6caebeb2685262", size = 90101, upload-time = "2026-06-16T19:44:50.439Z" }, +] + [[package]] name = "selenium" version = "4.2.0" @@ -2838,6 +2940,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3a/0c/3662f4a66880196a590b202f0db82d919dd2f89e99a27fadef91c4a33d41/xlsxwriter-3.2.9-py3-none-any.whl", hash = "sha256:9a5db42bc5dff014806c58a20b9eae7322a134abb6fce3c92c181bfb275ec5b3", size = 175315, upload-time = "2025-09-16T00:16:20.108Z" }, ] +[[package]] +name = "xmltodict" +version = "1.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/19/70/80f3b7c10d2630aa66414bf23d210386700aa390547278c789afa994fd7e/xmltodict-1.0.4.tar.gz", hash = "sha256:6d94c9f834dd9e44514162799d344d815a3a4faec913717a9ecbfa5be1bb8e61", size = 26124, upload-time = "2026-02-22T02:21:22.074Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/34/98a2f52245f4d47be93b580dae5f9861ef58977d73a79eb47c58f1ad1f3a/xmltodict-1.0.4-py3-none-any.whl", hash = "sha256:a4a00d300b0e1c59fc2bfccb53d7b2e88c32f200df138a0dd2229f842497026a", size = 13580, upload-time = "2026-02-22T02:21:21.039Z" }, +] + [[package]] name = "zipp" version = "4.1.0"