Skip to content

Commit 1cc554a

Browse files
authored
Merge pull request #164 from ColinMaudry/release/2.7.0
Release/2.7.0
2 parents f65bf64 + 550065a commit 1cc554a

20 files changed

Lines changed: 672 additions & 133 deletions

CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
1+
### 2.7.0
2+
3+
- Remplacement des guillemets simples par des apostrophes dans "objet"
4+
- Ajout des données de l'API DUME (code source `scrap_aife_dume`) ([#144](https://github.com/ColinMaudry/decp-processing/issues/144))
5+
- Ajout des données du profil d'acheteur Klekoon (code source `scrap_klekoon`) ([#71](https://github.com/ColinMaudry/decp-processing/issues/71))
6+
17
#### 2.6.4 2025-12-19
28

39
- Tri et numérotation des modifications après la concaténation plutôt que par ressource, pour réduire le nombre de doublons ([#156](https://github.com/ColinMaudry/decp-processing/issues/156))
4-
- Utilisation du logger de prefect plûtot que `log_prints=True`
10+
- Utilisation du logger de prefect plûtot que `log_prints=True` ([#94](https://github.com/ColinMaudry/decp-processing/issues/94))
511

612
#### 2.6.3 2025-12-16
713

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# DECP processing
22

3-
> version 2.6.3 ([notes de version](https://github.com/ColinMaudry/decp-processing/blob/main/CHANGELOG.md))
3+
> version 2.7.0 ([notes de version](https://github.com/ColinMaudry/decp-processing/blob/main/CHANGELOG.md))
44
55
Projet de traitement et de publication de meilleures données sur les marchés publics attribués en France. Vous pouvez consulter, filtrer et télécharger
66
ces données sur le site [decp.info](https://decp.info). Enfin la section [À propos](https://decp.info/a-propos) décrit les objectifs du projet et regroupe toutes les informations clés.
@@ -163,3 +163,4 @@ pytest tests/test_main.py
163163
- [FranckMaseo](https://github.com/frankmaseo)
164164
- [Thomas Louf](https://github.com/tlouf)
165165
- [vico4445](https://github.com/vico4445)
166+
- [imanuch](https://github.com/imanuch)

pyproject.toml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
[project]
22
name = "decp-processing"
33
description = "Traitement des données des marchés publics français."
4-
version = "2.6.3"
4+
version = "2.7.0"
55
requires-python = ">= 3.9"
66
authors = [
77
{ name = "Colin Maudry", email = "colin+decp@maudry.com" }
88
]
99
dependencies = [
1010
"python-dotenv",
1111
"pandas", # nécessaire pour l'écriture en base de données
12-
"polars==1.35.2",
12+
"polars==1.36.1",
1313
"pyarrow",
1414
"frictionless",
1515
"ipykernel",
@@ -23,7 +23,8 @@ dependencies = [
2323
"selenium",
2424
"polars_ds",
2525
"scikit-learn",
26-
"tenacity"
26+
"tenacity",
27+
"dume_api"
2728
]
2829

2930
[project.optional-dependencies]

reference/schema_base.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@
8181
"type": "string",
8282
"name": "procedure",
8383
"title": "Procédure",
84-
"description": "Le type de procédure utilisé pour le marché public. Prodcédure négociée ouverte, Procédure non négociée ouverte, Procédure négociée restreinte, Procédure non négociée restreinte.",
84+
"description": "Le type de procédure utilisé pour le marché public. Procédure négociée ouverte, Procédure non négociée ouverte, Procédure négociée restreinte, Procédure non négociée restreinte.",
8585
"short_title": null
8686
},
8787
{

reference/source_datasets.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,12 @@
257257
"owner_org_name": "Agence pour l'Informatique Financière de l'Etat",
258258
"code": "aife_dume"
259259
},
260+
{
261+
"id": "694ff7a98210456475f98aca",
262+
"name": "Données essentielles de la commande publique (DECP) de l'API DUME (AIFE)",
263+
"owner_org_name": "Colin Maudry",
264+
"code": "scrap_aife_dume"
265+
},
260266
{
261267
"id": "68ebb48dd708fdb2d7c15bff",
262268
"name": "Données des marchés publics de marches-securises.fr",
@@ -310,5 +316,11 @@
310316
"name": "Les Personnes placées sous main de justice - IDF 2024",
311317
"code": "ppsmj",
312318
"owner_org_name": "Yael Siksik"
319+
},
320+
{
321+
"id": "6952899077f982c9a2373ede",
322+
"name": "Données essentielles de la commande publique (DECP) de Klekoon",
323+
"code": "scrap_klekoon",
324+
"owner_org_name": "Colin Maudry"
313325
}
314326
]

run_flow.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import sys
22

3+
from src.config import SCRAPING_MODE, SCRAPING_MONTH, SCRAPING_TARGET, SCRAPING_YEAR
34
from src.flows.decp_processing import decp_processing
45
from src.flows.get_cog import get_cog
56
from src.flows.scrap import scrap
@@ -28,4 +29,9 @@
2829
if func_name != "scrap":
2930
FUNCTIONS[func_name]()
3031
else:
31-
scrap(target="aws", mode="all")
32+
scrap(
33+
mode=SCRAPING_MODE,
34+
target=SCRAPING_TARGET,
35+
month=SCRAPING_MONTH,
36+
year=SCRAPING_YEAR,
37+
)

src/config.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ def make_path_from_env(env: str, alternative_path: Path) -> Path:
3333

3434
# Niveau des logs
3535
LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO")
36+
ALL_CONFIG["LOG_LEVEL"] = LOG_LEVEL
3637

3738
# Nombre maximal de workers utilisables par Prefect. Défaut : 16
3839
MAX_PREFECT_WORKERS = int(os.getenv("MAX_PREFECT_WORKERS", 4))
@@ -132,6 +133,14 @@ def make_sirene_data_dir(sirene_data_parent_dir) -> Path:
132133
SCRAPING_TARGET = os.getenv("SCRAPING_TARGET")
133134
ALL_CONFIG["SCRAPING_TARGET"] = SCRAPING_TARGET
134135

136+
# Year (année cible pour le scraping)
137+
SCRAPING_YEAR = os.getenv("SCRAPING_YEAR")
138+
ALL_CONFIG["SCRAPING_YEAR"] = SCRAPING_YEAR
139+
140+
# Month (mois cible pour le scraping)
141+
SCRAPING_MONTH = os.getenv("SCRAPING_MONTH")
142+
ALL_CONFIG["SCRAPING_MONTH"] = SCRAPING_MONTH
143+
135144
# Lecture ou non des ressource en cache
136145
DECP_USE_CACHE = os.getenv("DECP_USE_CACHE", "false").lower() == "true"
137146

@@ -196,6 +205,13 @@ def make_sirene_data_dir(sirene_data_parent_dir) -> Path:
196205
SOLO_DATASET = os.getenv("SOLO_DATASET", "")
197206
ALL_CONFIG["SOLO_DATASET"] = SOLO_DATASET
198207

208+
# Acheteurs absents de la base SIRENE (pour raisons de sécurité ou autre)
209+
# Format: SIRET -> {"nom": "...", ...}
210+
# Ces données sont utilisées en fallback si l'acheteur n'est pas trouvé dans SIRENE
211+
ACHETEURS_NON_SIRENE = {
212+
"13001536500013": {"nom": "Ministère des Armées"},
213+
}
214+
199215
with open(
200216
make_path_from_env(
201217
"DATASETS_REFERENCE_FILEPATH", REFERENCE_DIR / "source_datasets.json"

src/deployments.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,28 @@
130130
},
131131
)
132132

133+
flow.from_source(
134+
source=GitRepository(
135+
url="https://github.com/ColinMaudry/decp-processing.git", branch="main"
136+
),
137+
entrypoint="src/flows/scrap.py:scrap",
138+
).deploy(
139+
name="scrap-dume",
140+
description="Scraping des données de l'API DUME.",
141+
ignore_warnings=True,
142+
work_pool_name="local",
143+
cron="0 0 * * 2",
144+
job_variables={
145+
"env": {
146+
"DECP_PROCESSING_PUBLISH": "True",
147+
"DECP_DIST_DIR": "/srv/shared/decp/prod/dist",
148+
"PREFECT_TASKS_REFRESH_CACHE": "False",
149+
"SCRAPING_MODE": "month",
150+
"SCRAPING_TARGET": "dume",
151+
}
152+
},
153+
)
154+
133155
flow.from_source(
134156
source=GitRepository(
135157
url="https://github.com/ColinMaudry/decp-processing.git", branch="main"

src/flows/decp_processing.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
from prefect_email import EmailServerCredentials, email_send_message
1111

1212
from src.config import (
13-
ALL_CONFIG,
1413
BASE_DF_COLUMNS,
1514
DATE_NOW,
1615
DECP_PROCESSING_PUBLISH,
@@ -20,6 +19,7 @@
2019
PREFECT_API_URL,
2120
RESOURCE_CACHE_DIR,
2221
SIRENE_DATA_DIR,
22+
SOLO_DATASET,
2323
TRACKED_DATASETS,
2424
)
2525
from src.flows.sirene_preprocess import sirene_preprocess
@@ -50,7 +50,7 @@ def decp_processing(enable_cache_removal: bool = True):
5050

5151
logger.info("🚀 Début du flow decp-processing")
5252

53-
print_all_config(ALL_CONFIG)
53+
print_all_config()
5454

5555
logger.info("Liste de toutes les ressources des datasets...")
5656
resources: list[dict] = list_resources(TRACKED_DATASETS)
@@ -79,7 +79,11 @@ def decp_processing(enable_cache_removal: bool = True):
7979
)
8080

8181
# Afin d'être sûr que je ne publie pas par erreur un jeu de données de test
82-
decp_publish = DECP_PROCESSING_PUBLISH and len(resources_to_process) > 5000
82+
decp_publish = (
83+
DECP_PROCESSING_PUBLISH
84+
and len(resources_to_process) > 5000
85+
and SOLO_DATASET in ["", None]
86+
)
8387

8488
if decp_publish:
8589
create_table_artifact(
@@ -146,7 +150,7 @@ def decp_processing(enable_cache_removal: bool = True):
146150
logger.info("☑️ Fin du flow principal decp_processing.")
147151

148152

149-
@task(retries=2)
153+
@task(retries=2, timeout_seconds=1800)
150154
def process_batch(
151155
available_parquet_files,
152156
batch_size,

src/flows/scrap.py

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,29 +11,39 @@
1111
SCRAPING_MODE,
1212
SCRAPING_TARGET,
1313
)
14-
from src.tasks.scrap import scrap_aws_month, scrap_marches_securises_month
15-
from src.tasks.utils import get_logger
14+
from src.tasks.utils import get_logger, print_all_config
15+
from tasks.scrap.aws import scrap_aws_month
16+
from tasks.scrap.dume import scrap_dume_month
17+
from tasks.scrap.klekoon import scrap_klekoon
18+
from tasks.scrap.marches_securises import scrap_marches_securises_month
1619

1720

1821
@flow(log_prints=True)
19-
def scrap(target: str = None, mode: str = None, month=None, year=None):
22+
def scrap(target: str, mode: str, month=None, year=None):
2023
logger = get_logger(level=LOG_LEVEL)
24+
25+
print_all_config()
26+
2127
# Remise à zéro du dossier dist
2228
dist_dir: Path = DIST_DIR / target
2329
if dist_dir.exists():
24-
logger.debug(f"Suppression de {dist_dir}...")
30+
logger.info(f"Suppression de {dist_dir}...")
2531
rmtree(dist_dir)
26-
else:
27-
dist_dir.mkdir(parents=True)
2832

29-
# Sélection du target
30-
target = target or SCRAPING_TARGET
33+
dist_dir.mkdir(parents=True, exist_ok=True)
3134

3235
# Sélection de la fonction de scraping en fonction de target
3336
if target == "aws":
3437
scrap_target_month = scrap_aws_month
3538
elif target == "marches-securises.fr":
3639
scrap_target_month = scrap_marches_securises_month
40+
elif target == "dume":
41+
scrap_target_month = scrap_dume_month
42+
elif target == "klekoon":
43+
# Klekoon présent ses données par acheteur et non de manière temporelle
44+
# donc on télécharge tout à chaque fois
45+
scrap_klekoon(dist_dir)
46+
return
3747
else:
3848
logger.error("Quel target ?")
3949
raise ValueError
@@ -43,8 +53,15 @@ def scrap(target: str = None, mode: str = None, month=None, year=None):
4353
month = month or current_month
4454
year = year or current_year
4555

46-
# Sélection du mode
47-
mode = mode or SCRAPING_MODE
56+
# Récapitulatif de la config
57+
# mode et target doivent être passés en paramètre
58+
# les éventuelles env sont injectées via /run_flow.py
59+
# en prod les paramètres sont spécifiées dans le deployment Prefect
60+
logger.info(f"""
61+
Target: {target} (env {SCRAPING_TARGET})
62+
Mode: {mode} (env {SCRAPING_MODE})
63+
Year: {year}
64+
Month: {month}""")
4865

4966
# Sélection de la plage temporelle
5067
if mode == "month":
@@ -57,7 +74,7 @@ def scrap(target: str = None, mode: str = None, month=None, year=None):
5774

5875
elif mode == "all":
5976
current_year = int(current_year)
60-
for year in reversed(range(2018, current_year + 2)):
77+
for year in reversed(range(2018, current_year + 1)):
6178
scrap(target=target, mode="year", year=str(year))
6279

6380
else:

0 commit comments

Comments
 (0)