kadipy 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- kadipy-1.0.0/LICENSE +21 -0
- kadipy-1.0.0/PKG-INFO +156 -0
- kadipy-1.0.0/README.md +84 -0
- kadipy-1.0.0/kadi/__init__.py +83 -0
- kadipy-1.0.0/kadi/_sources/chirps.py +37 -0
- kadipy-1.0.0/kadi/_sources/open_meteo.py +141 -0
- kadipy-1.0.0/kadi/_sources/soilgrids.py +53 -0
- kadipy-1.0.0/kadi/_utils/coordinates.py +46 -0
- kadipy-1.0.0/kadi/_utils/network.py +54 -0
- kadipy-1.0.0/kadi/cache.py +232 -0
- kadipy-1.0.0/kadi/config.py +215 -0
- kadipy-1.0.0/kadi/exceptions.py +113 -0
- kadipy-1.0.0/kadi/kidas/__init__.py +98 -0
- kadipy-1.0.0/kadi/kidas/cache.py +554 -0
- kadipy-1.0.0/kadi/kidas/cleaner.py +561 -0
- kadipy-1.0.0/kadi/kidas/normalizer.py +564 -0
- kadipy-1.0.0/kadi/kidas/pipeline.py +505 -0
- kadipy-1.0.0/kadi/kidas/sources/__init__.py +7 -0
- kadipy-1.0.0/kadi/kidas/sources/api_source.py +416 -0
- kadipy-1.0.0/kadi/kidas/sources/base.py +155 -0
- kadipy-1.0.0/kadi/kidas/sources/csv_source.py +423 -0
- kadipy-1.0.0/kadi/kidas/sources/excel_source.py +381 -0
- kadipy-1.0.0/kadi/kidas/sources/json_source.py +320 -0
- kadipy-1.0.0/kadi/kidas/sources/netcdf_source.py +346 -0
- kadipy-1.0.0/kadi/kidas/validator.py +504 -0
- kadipy-1.0.0/kadi/market/__init__.py +501 -0
- kadipy-1.0.0/kadi/market/_cache.py +603 -0
- kadipy-1.0.0/kadi/market/_normalization.py +285 -0
- kadipy-1.0.0/kadi/market/backtesting.py +373 -0
- kadipy-1.0.0/kadi/market/data_ingestion.py +660 -0
- kadipy-1.0.0/kadi/market/decision_support.py +585 -0
- kadipy-1.0.0/kadi/market/forecasting.py +387 -0
- kadipy-1.0.0/kadi/market/logistics.py +570 -0
- kadipy-1.0.0/kadi/market/pricing.py +443 -0
- kadipy-1.0.0/kadi/weather/__init__.py +22 -0
- kadipy-1.0.0/kadi/weather/data.py +309 -0
- kadipy-1.0.0/kadi/weather/hydrology.py +264 -0
- kadipy-1.0.0/kadi/weather/location.py +95 -0
- kadipy-1.0.0/kadi/weather/phenology.py +414 -0
- kadipy-1.0.0/kadi/weather/risk.py +295 -0
- kadipy-1.0.0/kadi/weather/session.py +197 -0
- kadipy-1.0.0/kadipy.egg-info/PKG-INFO +156 -0
- kadipy-1.0.0/kadipy.egg-info/SOURCES.txt +46 -0
- kadipy-1.0.0/kadipy.egg-info/dependency_links.txt +1 -0
- kadipy-1.0.0/kadipy.egg-info/requires.txt +22 -0
- kadipy-1.0.0/kadipy.egg-info/top_level.txt +1 -0
- kadipy-1.0.0/pyproject.toml +135 -0
- kadipy-1.0.0/setup.cfg +4 -0
kadipy-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Dels Marcel Dinla
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
kadipy-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kadipy
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Le "pandas" de l'agriculture africaine. Interface Python pour les données météo, de marché et de récoltes au Bénin.
|
|
5
|
+
Author-email: Dels Dinla <delsdenla.dev@gmail.com>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 Dels Marcel Dinla
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://github.com/delsDin/kadipy
|
|
29
|
+
Project-URL: Documentation, https://delsDin.github.io/kadipy/
|
|
30
|
+
Project-URL: Repository, https://github.com/delsDin/kadipy
|
|
31
|
+
Project-URL: Bug Tracker, https://github.com/delsDin/kadipy/issues
|
|
32
|
+
Project-URL: Changelog, https://github.com/delsDin/kadipy/releases
|
|
33
|
+
Keywords: agriculture,benin,afrique-ouest,meteo,marche-agricole,prix,prevision,pandas,pipeline-donnees,analyse-donnees,donnees-collectes,agritech,decision-support
|
|
34
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
35
|
+
Classifier: Intended Audience :: Science/Research
|
|
36
|
+
Classifier: Intended Audience :: Developers
|
|
37
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
38
|
+
Classifier: Programming Language :: Python :: 3
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
42
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
43
|
+
Classifier: Topic :: Scientific/Engineering :: Atmospheric Science
|
|
44
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
45
|
+
Classifier: Natural Language :: French
|
|
46
|
+
Classifier: Operating System :: OS Independent
|
|
47
|
+
Requires-Python: >=3.9
|
|
48
|
+
Description-Content-Type: text/markdown
|
|
49
|
+
License-File: LICENSE
|
|
50
|
+
Requires-Dist: requests>=2.25
|
|
51
|
+
Requires-Dist: pandas>=1.3
|
|
52
|
+
Requires-Dist: numpy>=1.21
|
|
53
|
+
Requires-Dist: openpyxl>=3.0
|
|
54
|
+
Requires-Dist: geopy>=2.2
|
|
55
|
+
Requires-Dist: scikit-learn>=1.0
|
|
56
|
+
Requires-Dist: joblib>=1.1
|
|
57
|
+
Requires-Dist: xarray>=2023.1
|
|
58
|
+
Requires-Dist: netCDF4>=1.6
|
|
59
|
+
Requires-Dist: chardet>=5.0
|
|
60
|
+
Requires-Dist: scipy>=1.9
|
|
61
|
+
Requires-Dist: shapely>=2.0
|
|
62
|
+
Requires-Dist: Unidecode>=1.3
|
|
63
|
+
Provides-Extra: dev
|
|
64
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
65
|
+
Requires-Dist: pytest-mock>=3.7; extra == "dev"
|
|
66
|
+
Requires-Dist: responses>=0.21; extra == "dev"
|
|
67
|
+
Requires-Dist: requests-mock>=1.9; extra == "dev"
|
|
68
|
+
Requires-Dist: mkdocs>=1.3; extra == "dev"
|
|
69
|
+
Requires-Dist: mkdocs-material>=8.0; extra == "dev"
|
|
70
|
+
Requires-Dist: mkdocstrings[python]>=0.20; extra == "dev"
|
|
71
|
+
Dynamic: license-file
|
|
72
|
+
|
|
73
|
+
# KadiPy
|
|
74
|
+
|
|
75
|
+
**Le "pandas" de l'agriculture africaine.**
|
|
76
|
+
|
|
77
|
+
KadiPy est une bibliothèque Python conçue pour les agronomes, chercheurs
|
|
78
|
+
et développeurs travaillant sur les données agricoles au Bénin et en
|
|
79
|
+
Afrique de l'Ouest. Elle fournit une interface unifiée pour traiter les
|
|
80
|
+
données météorologiques, les prix de marché et les données de récoltes,
|
|
81
|
+
avec une approche hors ligne en premier (offline-first).
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Installation
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
pip install kadipy
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Nécessite Python 3.9 ou supérieur.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Modules disponibles
|
|
96
|
+
|
|
97
|
+
**kadi.market** : analyse économique des marchés agricoles béninois.
|
|
98
|
+
- Récupération des prix via l'API WFP DataBridges.
|
|
99
|
+
- Prévisions de prix par régression linéaire saisonnière.
|
|
100
|
+
- Calcul des coûts de transport avec données routières réelles (OSRM).
|
|
101
|
+
- Aide à la décision : arbitrage spatial, stockage stratégique, score de confiance.
|
|
102
|
+
- Backtesting des prévisions (MAE, RMSE, MAPE, précision directionnelle).
|
|
103
|
+
|
|
104
|
+
**kadi.weather** : données météorologiques et indicateurs agronomiques.
|
|
105
|
+
- Prévisions et historiques via Open-Meteo et CHIRPS.
|
|
106
|
+
- Phénologie : détection de la saison des pluies (Sivakumar, Walter-Anyadike), GDD.
|
|
107
|
+
- Hydrologie : évapotranspiration ET0 (Hargreaves-Samani), bilan hydrique FAO-56.
|
|
108
|
+
- Risques climatiques : indice de sécheresse (SPI), probabilité de pluie.
|
|
109
|
+
|
|
110
|
+
**kadi.kidas** : pipeline de traitement des données agricoles.
|
|
111
|
+
- Nettoyage : valeurs manquantes, doublons, valeurs aberrantes.
|
|
112
|
+
- Validation : types, plages de valeurs, rapport d'erreurs.
|
|
113
|
+
- Normalisation : noms de cultures, marchés, unités et coordonnées GPS.
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Démarrage rapide
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
from kadi.weather import WeatherSession
|
|
121
|
+
from kadi.market import Market
|
|
122
|
+
|
|
123
|
+
# Données météo pour Parakou
|
|
124
|
+
session = WeatherSession(latitude=9.33, longitude=2.63, name="Parakou")
|
|
125
|
+
previsions = session.forecast(days=3)
|
|
126
|
+
print(previsions["data"])
|
|
127
|
+
|
|
128
|
+
# Analyse de marché
|
|
129
|
+
marche = Market(latitude=9.30, longitude=2.08, location="Parakou",
|
|
130
|
+
weather_session=session)
|
|
131
|
+
|
|
132
|
+
# Recommandation d'arbitrage
|
|
133
|
+
resultat = marche.decision_support.arbitrage_decision(
|
|
134
|
+
crop="maize",
|
|
135
|
+
origine="Parakou",
|
|
136
|
+
destination="Cotonou",
|
|
137
|
+
qty_tons=10.0,
|
|
138
|
+
)
|
|
139
|
+
print(resultat["recommandation"])
|
|
140
|
+
print(f"Score de confiance : {resultat['confidence_score']:.0%}")
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Documentation
|
|
146
|
+
|
|
147
|
+
La documentation complète est disponible sur :
|
|
148
|
+
**https://delsDin.github.io/kadipy/**
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Contribution et support
|
|
153
|
+
|
|
154
|
+
- Dépôt GitHub : https://github.com/delsDin/kadipy
|
|
155
|
+
- Licence : MIT
|
|
156
|
+
- Python : >= 3.9
|
kadipy-1.0.0/README.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# KadiPy
|
|
2
|
+
|
|
3
|
+
**Le "pandas" de l'agriculture africaine.**
|
|
4
|
+
|
|
5
|
+
KadiPy est une bibliothèque Python conçue pour les agronomes, chercheurs
|
|
6
|
+
et développeurs travaillant sur les données agricoles au Bénin et en
|
|
7
|
+
Afrique de l'Ouest. Elle fournit une interface unifiée pour traiter les
|
|
8
|
+
données météorologiques, les prix de marché et les données de récoltes,
|
|
9
|
+
avec une approche hors ligne en premier (offline-first).
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install kadipy
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Nécessite Python 3.9 ou supérieur.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Modules disponibles
|
|
24
|
+
|
|
25
|
+
**kadi.market** : analyse économique des marchés agricoles béninois.
|
|
26
|
+
- Récupération des prix via l'API WFP DataBridges.
|
|
27
|
+
- Prévisions de prix par régression linéaire saisonnière.
|
|
28
|
+
- Calcul des coûts de transport avec données routières réelles (OSRM).
|
|
29
|
+
- Aide à la décision : arbitrage spatial, stockage stratégique, score de confiance.
|
|
30
|
+
- Backtesting des prévisions (MAE, RMSE, MAPE, précision directionnelle).
|
|
31
|
+
|
|
32
|
+
**kadi.weather** : données météorologiques et indicateurs agronomiques.
|
|
33
|
+
- Prévisions et historiques via Open-Meteo et CHIRPS.
|
|
34
|
+
- Phénologie : détection de la saison des pluies (Sivakumar, Walter-Anyadike), GDD.
|
|
35
|
+
- Hydrologie : évapotranspiration ET0 (Hargreaves-Samani), bilan hydrique FAO-56.
|
|
36
|
+
- Risques climatiques : indice de sécheresse (SPI), probabilité de pluie.
|
|
37
|
+
|
|
38
|
+
**kadi.kidas** : pipeline de traitement des données agricoles.
|
|
39
|
+
- Nettoyage : valeurs manquantes, doublons, valeurs aberrantes.
|
|
40
|
+
- Validation : types, plages de valeurs, rapport d'erreurs.
|
|
41
|
+
- Normalisation : noms de cultures, marchés, unités et coordonnées GPS.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Démarrage rapide
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from kadi.weather import WeatherSession
|
|
49
|
+
from kadi.market import Market
|
|
50
|
+
|
|
51
|
+
# Données météo pour Parakou
|
|
52
|
+
session = WeatherSession(latitude=9.33, longitude=2.63, name="Parakou")
|
|
53
|
+
previsions = session.forecast(days=3)
|
|
54
|
+
print(previsions["data"])
|
|
55
|
+
|
|
56
|
+
# Analyse de marché
|
|
57
|
+
marche = Market(latitude=9.30, longitude=2.08, location="Parakou",
|
|
58
|
+
weather_session=session)
|
|
59
|
+
|
|
60
|
+
# Recommandation d'arbitrage
|
|
61
|
+
resultat = marche.decision_support.arbitrage_decision(
|
|
62
|
+
crop="maize",
|
|
63
|
+
origine="Parakou",
|
|
64
|
+
destination="Cotonou",
|
|
65
|
+
qty_tons=10.0,
|
|
66
|
+
)
|
|
67
|
+
print(resultat["recommandation"])
|
|
68
|
+
print(f"Score de confiance : {resultat['confidence_score']:.0%}")
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Documentation
|
|
74
|
+
|
|
75
|
+
La documentation complète est disponible sur :
|
|
76
|
+
**https://delsDin.github.io/kadipy/**
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Contribution et support
|
|
81
|
+
|
|
82
|
+
- Dépôt GitHub : https://github.com/delsDin/kadipy
|
|
83
|
+
- Licence : MIT
|
|
84
|
+
- Python : >= 3.9
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Package principal de KadiPy.
|
|
3
|
+
|
|
4
|
+
KadiPy est le "pandas" de l'agriculture africaine, facilitant le traitement
|
|
5
|
+
et l'analyse des données météorologiques, de marché et de récoltes locales
|
|
6
|
+
avec une approche "offline-first".
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
|
|
11
|
+
# ------------------------------------------------------------------
|
|
12
|
+
# Version du package
|
|
13
|
+
# ------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
__version__ = "1.0.0"
|
|
16
|
+
|
|
17
|
+
# ------------------------------------------------------------------
|
|
18
|
+
# Configuration du logger racine de KadiPy
|
|
19
|
+
#
|
|
20
|
+
# Tous les sous-modules utilisent logging.getLogger(__name__), ce qui
|
|
21
|
+
# crée des loggers de la forme "kadi.market.pricing", "kadi.weather.risk",
|
|
22
|
+
# etc. Ces loggers héritent automatiquement du niveau défini ici sur
|
|
23
|
+
# le logger racine "kadi". Il suffit donc de configurer ce seul logger
|
|
24
|
+
# pour contrôler l'ensemble de la bibliothèque.
|
|
25
|
+
# ------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
# Logger racine du package (silence par défaut : WARNING)
|
|
28
|
+
_logger_kadi = logging.getLogger("kadi")
|
|
29
|
+
_logger_kadi.setLevel(logging.WARNING)
|
|
30
|
+
|
|
31
|
+
# Handler console minimal (affiché uniquement si aucun handler n'est
|
|
32
|
+
# configuré en amont dans l'application de l'utilisateur)
|
|
33
|
+
if not _logger_kadi.handlers:
|
|
34
|
+
_handler = logging.StreamHandler()
|
|
35
|
+
_handler.setFormatter(
|
|
36
|
+
logging.Formatter("%(levelname)s - %(name)s - %(message)s")
|
|
37
|
+
)
|
|
38
|
+
_logger_kadi.addHandler(_handler)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def set_verbosity(level: str = "WARNING") -> None:
|
|
42
|
+
"""Configure le niveau de log pour tous les sous-modules de KadiPy.
|
|
43
|
+
|
|
44
|
+
Cette fonction est le point d'entrée unique pour contrôler la
|
|
45
|
+
verbosité de la bibliothèque. Elle s'applique au logger racine
|
|
46
|
+
"kadi", dont héritent automatiquement tous les sous-loggers
|
|
47
|
+
(kadi.market, kadi.weather, kadi.kidas, etc.).
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
level (str): Niveau de log souhaité. Valeurs acceptées :
|
|
51
|
+
- "DEBUG" : logs détaillés (requêtes API, calculs internes).
|
|
52
|
+
- "INFO" : messages d'état du pipeline.
|
|
53
|
+
- "WARNING" : uniquement les avertissements (defaut).
|
|
54
|
+
- "ERROR" : uniquement les erreurs critiques.
|
|
55
|
+
|
|
56
|
+
Raises:
|
|
57
|
+
ValueError: Si le niveau fourni n'est pas reconnu par le module
|
|
58
|
+
standard logging.
|
|
59
|
+
|
|
60
|
+
Exemple:
|
|
61
|
+
>>> import kadi
|
|
62
|
+
>>> kadi.set_verbosity("DEBUG") # Active les logs détaillés
|
|
63
|
+
>>> kadi.set_verbosity("WARNING") # Remet en mode silencieux (défaut)
|
|
64
|
+
"""
|
|
65
|
+
# Conversion du niveau texte en constante numérique logging
|
|
66
|
+
niveau_numerique = getattr(logging, level.upper(), None)
|
|
67
|
+
|
|
68
|
+
# Vérification que le niveau est valide
|
|
69
|
+
if not isinstance(niveau_numerique, int):
|
|
70
|
+
niveaux_valides = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
|
|
71
|
+
raise ValueError(
|
|
72
|
+
f"Niveau de log invalide : '{level}'. "
|
|
73
|
+
f"Valeurs acceptées : {niveaux_valides}"
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
# Application du niveau sur le logger racine du package
|
|
77
|
+
_logger_kadi.setLevel(niveau_numerique)
|
|
78
|
+
|
|
79
|
+
# Journalisation du changement (visible uniquement si le niveau
|
|
80
|
+
# sélectionné est DEBUG ou INFO)
|
|
81
|
+
_logger_kadi.info(
|
|
82
|
+
"kadi : niveau de verbosité défini à '%s'.", level.upper()
|
|
83
|
+
)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Connecteur pour CHIRPS (Climate Hazards Group InfraRed Precipitation with Station data).
|
|
3
|
+
Permet de récupérer les données historiques de précipitation depuis un cache local.
|
|
4
|
+
|
|
5
|
+
Statut V1 : désactivé.
|
|
6
|
+
Le fichier CSV fourni (historical_rainfall_1981_2024.csv) est vide ou manquant.
|
|
7
|
+
Un filtrage spatial approprié des données CHIRPS nécessitera la transition vers
|
|
8
|
+
des fichiers NetCDF (.nc) dans la V2 du projet. En attendant, le système bascule
|
|
9
|
+
exclusivement sur l'historique d'Open-Meteo.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import pandas as pd
|
|
13
|
+
from typing import Optional
|
|
14
|
+
import logging
|
|
15
|
+
|
|
16
|
+
# Journaliseur pour ce module
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def fetch_historical_precipitation(lat: float, lon: float) -> Optional[pd.DataFrame]:
|
|
21
|
+
"""
|
|
22
|
+
Récupère l'historique des précipitations pour un point donné depuis le cache CHIRPS.
|
|
23
|
+
|
|
24
|
+
Cette fonction est désactivée pour la V1. Le fichier CSV global fourni est vide.
|
|
25
|
+
Le filtrage spatial par coordonnées GPS nécessitera des fichiers NetCDF en V2.
|
|
26
|
+
|
|
27
|
+
:param lat: Latitude du lieu (non utilisée en V1).
|
|
28
|
+
:param lon: Longitude du lieu (non utilisée en V1).
|
|
29
|
+
:return: Toujours None en V1. Retournera un DataFrame en V2.
|
|
30
|
+
"""
|
|
31
|
+
# Désactivé pour V1 : fichier CSV global vide, transition vers NetCDF prévue en V2.
|
|
32
|
+
logger.warning(
|
|
33
|
+
"Connecteur CHIRPS désactivé (V1) : fichier de données vide. "
|
|
34
|
+
"Le filtrage spatial des données NetCDF est prévu pour la V2. "
|
|
35
|
+
"Les données de précipitation proviennent exclusivement d'Open-Meteo."
|
|
36
|
+
)
|
|
37
|
+
return None
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Connecteur pour l'API Open-Meteo.
|
|
3
|
+
|
|
4
|
+
Permet de récupérer les prévisions et l'historique météo.
|
|
5
|
+
Gère le formatage des paramètres et l'extraction de la réponse JSON.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import requests
|
|
9
|
+
import datetime
|
|
10
|
+
from typing import Dict, Any, List
|
|
11
|
+
|
|
12
|
+
from kadi.config import OPENMETEO_API_URL
|
|
13
|
+
from kadi.exceptions import DataSourceError
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def fetch_forecast(lat: float, lon: float, days: int = 7) -> List[Dict[str, Any]]:
|
|
17
|
+
"""
|
|
18
|
+
Récupère les prévisions météo journalières via Open-Meteo.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
lat (float): Latitude du lieu.
|
|
22
|
+
lon (float): Longitude du lieu.
|
|
23
|
+
days (int): Nombre de jours à récupérer.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
List[Dict]: Une liste de dictionnaires représentant les données par jour.
|
|
27
|
+
|
|
28
|
+
Raises:
|
|
29
|
+
DataSourceError: En cas d'erreur de requête HTTP.
|
|
30
|
+
"""
|
|
31
|
+
url = f"{OPENMETEO_API_URL}/forecast"
|
|
32
|
+
params = {
|
|
33
|
+
"latitude": lat,
|
|
34
|
+
"longitude": lon,
|
|
35
|
+
"daily": "temperature_2m_max,temperature_2m_min,precipitation_sum",
|
|
36
|
+
"forecast_days": days,
|
|
37
|
+
"timezone": "auto"
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
response = requests.get(url, params=params, timeout=10)
|
|
42
|
+
response.raise_for_status()
|
|
43
|
+
data = response.json()
|
|
44
|
+
|
|
45
|
+
return _parse_daily_data(data, "forecast", "open-meteo", confidence=0.95)
|
|
46
|
+
except requests.RequestException as e:
|
|
47
|
+
raise DataSourceError(f"Erreur de connexion Open-Meteo (forecast): {e}")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def fetch_historical(lat: float, lon: float, months_back: int = 12) -> List[Dict[str, Any]]:
|
|
51
|
+
"""
|
|
52
|
+
Récupère l'historique météo (Archive) via Open-Meteo.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
lat (float): Latitude du lieu.
|
|
56
|
+
lon (float): Longitude du lieu.
|
|
57
|
+
months_back (int): Nombre de mois en arrière à récupérer.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
List[Dict]: Une liste de dictionnaires représentant les données passées par jour.
|
|
61
|
+
|
|
62
|
+
Raises:
|
|
63
|
+
DataSourceError: En cas d'erreur de requête HTTP.
|
|
64
|
+
"""
|
|
65
|
+
url = "https://archive-api.open-meteo.com/v1/archive"
|
|
66
|
+
|
|
67
|
+
# Calcul des dates de début et fin
|
|
68
|
+
end_date = datetime.date.today() - datetime.timedelta(days=2) # L'archive a environ 2 jours de délai
|
|
69
|
+
start_date = end_date - datetime.timedelta(days=30 * months_back)
|
|
70
|
+
|
|
71
|
+
params = {
|
|
72
|
+
"latitude": lat,
|
|
73
|
+
"longitude": lon,
|
|
74
|
+
"start_date": start_date.strftime("%Y-%m-%d"),
|
|
75
|
+
"end_date": end_date.strftime("%Y-%m-%d"),
|
|
76
|
+
"daily": "temperature_2m_max,temperature_2m_min,precipitation_sum",
|
|
77
|
+
"timezone": "auto"
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
try:
|
|
81
|
+
response = requests.get(url, params=params, timeout=15)
|
|
82
|
+
response.raise_for_status()
|
|
83
|
+
data = response.json()
|
|
84
|
+
|
|
85
|
+
return _parse_daily_data(data, "historical", "open-meteo-archive", confidence=1.0)
|
|
86
|
+
except requests.RequestException as e:
|
|
87
|
+
raise DataSourceError(f"Erreur de connexion Open-Meteo (historical): {e}")
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _parse_daily_data(
|
|
91
|
+
data: Dict[str, Any],
|
|
92
|
+
data_type: str,
|
|
93
|
+
source: str,
|
|
94
|
+
confidence: float
|
|
95
|
+
) -> List[Dict[str, Any]]:
|
|
96
|
+
"""
|
|
97
|
+
Parse la structure JSON retournée par Open-Meteo (daily).
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
data: Le dictionnaire JSON renvoyé par l'API.
|
|
101
|
+
data_type: Le type ('forecast' ou 'historical').
|
|
102
|
+
source: Le nom de la source.
|
|
103
|
+
confidence: L'indice de confiance.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
Une liste de lignes prêtes à être insérées dans le cache ou traitées.
|
|
107
|
+
"""
|
|
108
|
+
daily = data.get("daily", {})
|
|
109
|
+
if not daily:
|
|
110
|
+
return []
|
|
111
|
+
|
|
112
|
+
dates = daily.get("time", [])
|
|
113
|
+
t_max = daily.get("temperature_2m_max", [])
|
|
114
|
+
t_min = daily.get("temperature_2m_min", [])
|
|
115
|
+
precip = daily.get("precipitation_sum", [])
|
|
116
|
+
|
|
117
|
+
results = []
|
|
118
|
+
|
|
119
|
+
for i, date_str in enumerate(dates):
|
|
120
|
+
# On calcule la température moyenne si min et max sont disponibles
|
|
121
|
+
max_val = t_max[i] if i < len(t_max) else None
|
|
122
|
+
min_val = t_min[i] if i < len(t_min) else None
|
|
123
|
+
|
|
124
|
+
avg_val = None
|
|
125
|
+
if max_val is not None and min_val is not None:
|
|
126
|
+
avg_val = round((max_val + min_val) / 2.0, 2)
|
|
127
|
+
|
|
128
|
+
row = {
|
|
129
|
+
"date": date_str,
|
|
130
|
+
"hour": None,
|
|
131
|
+
"temperature_min": min_val,
|
|
132
|
+
"temperature_max": max_val,
|
|
133
|
+
"temperature_avg": avg_val,
|
|
134
|
+
"precipitation": precip[i] if i < len(precip) else None,
|
|
135
|
+
"data_type": data_type,
|
|
136
|
+
"data_source": source,
|
|
137
|
+
"confidence": confidence
|
|
138
|
+
}
|
|
139
|
+
results.append(row)
|
|
140
|
+
|
|
141
|
+
return results
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Connecteur pour SoilGrids.
|
|
3
|
+
Permet de récupérer les informations de sols (souvent via un cache local pour réduire la latence).
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import json
|
|
8
|
+
import math
|
|
9
|
+
import logging
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
def fetch_soil_type(lat: float, lon: float, default_soil: str = "ferrugineux") -> str:
|
|
14
|
+
"""
|
|
15
|
+
Détermine le type de sol depuis le cache local pré-téléchargé SoilGrids.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
lat (float): Latitude.
|
|
19
|
+
lon (float): Longitude.
|
|
20
|
+
default_soil (str): Type de sol par défaut si introuvable.
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
str: Le type de sol trouvé, ou la valeur par défaut.
|
|
24
|
+
"""
|
|
25
|
+
cache_file = os.path.expanduser("~/.kadipy_cache/soilgrids_cache.json")
|
|
26
|
+
|
|
27
|
+
if not os.path.exists(cache_file):
|
|
28
|
+
logger.debug(f"Cache SoilGrids introuvable, utilisation du défaut: {default_soil}")
|
|
29
|
+
return default_soil
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
with open(cache_file, "r") as f:
|
|
33
|
+
data = json.load(f)
|
|
34
|
+
|
|
35
|
+
if not data:
|
|
36
|
+
return default_soil
|
|
37
|
+
|
|
38
|
+
closest_point = None
|
|
39
|
+
min_dist = float('inf')
|
|
40
|
+
|
|
41
|
+
for pt in data:
|
|
42
|
+
dist = math.hypot(pt["lat"] - lat, pt["lon"] - lon)
|
|
43
|
+
if dist < min_dist:
|
|
44
|
+
min_dist = dist
|
|
45
|
+
closest_point = pt
|
|
46
|
+
|
|
47
|
+
if closest_point and "soil_type" in closest_point:
|
|
48
|
+
return closest_point["soil_type"]
|
|
49
|
+
|
|
50
|
+
except Exception as e:
|
|
51
|
+
logger.error(f"Erreur lors de la lecture du cache SoilGrids: {e}")
|
|
52
|
+
|
|
53
|
+
return default_soil
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Utilitaires de gestion des coordonnées GPS pour KadiPy.
|
|
3
|
+
|
|
4
|
+
Dans cette version V1, la résolution des villes se fait via un dictionnaire
|
|
5
|
+
statique pour les principales villes du Bénin, afin de permettre les tests
|
|
6
|
+
(mocking).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import Tuple
|
|
10
|
+
from kadi.exceptions import LocationNotFound
|
|
11
|
+
|
|
12
|
+
# Dictionnaire mocké pour la résolution des localisations (Villes du Bénin)
|
|
13
|
+
BENIN_CITIES = {
|
|
14
|
+
"cotonou": (6.36536, 2.41833),
|
|
15
|
+
"porto-novo": (6.49722, 2.605),
|
|
16
|
+
"parakou": (9.33716, 2.63031),
|
|
17
|
+
"abomey": (7.18286, 1.99119),
|
|
18
|
+
"natitingou": (10.30416, 1.37962),
|
|
19
|
+
"djougou": (9.70853, 1.66598),
|
|
20
|
+
"bohicon": (7.17826, 2.0667),
|
|
21
|
+
"ouidah": (6.36408, 2.08383),
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def normalize_location(location: str) -> Tuple[float, float]:
|
|
26
|
+
"""
|
|
27
|
+
Transforme un nom de ville en un tuple de coordonnées (latitude, longitude).
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
location: Le nom de la ville (ex: "Abomey", "Cotonou").
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
Tuple[float, float]: (latitude, longitude).
|
|
34
|
+
|
|
35
|
+
Raises:
|
|
36
|
+
LocationNotFound: Si la ville n'est pas dans notre dictionnaire mocké.
|
|
37
|
+
"""
|
|
38
|
+
# Nettoyage de la chaîne : passage en minuscules et suppression des espaces
|
|
39
|
+
loc_clean = location.strip().lower()
|
|
40
|
+
|
|
41
|
+
if loc_clean in BENIN_CITIES:
|
|
42
|
+
return BENIN_CITIES[loc_clean]
|
|
43
|
+
|
|
44
|
+
raise LocationNotFound(
|
|
45
|
+
f"La localisation '{location}' n'est pas reconnue dans la base du Bénin."
|
|
46
|
+
)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Utilitaires réseau pour KadiPy.
|
|
3
|
+
|
|
4
|
+
Fournit des fonctions pour exécuter des requêtes réseau avec
|
|
5
|
+
mécanismes de retry (tentatives) et de fallback (repli).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import time
|
|
9
|
+
import logging
|
|
10
|
+
from typing import Callable, Any
|
|
11
|
+
|
|
12
|
+
from kadi.exceptions import DataSourceError
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def fetch_with_retry(
|
|
18
|
+
fetch_func: Callable,
|
|
19
|
+
attempts: int = 3,
|
|
20
|
+
backoff_sec: int = 5,
|
|
21
|
+
*args,
|
|
22
|
+
**kwargs
|
|
23
|
+
) -> Any:
|
|
24
|
+
"""
|
|
25
|
+
Exécute une fonction de récupération réseau avec un mécanisme de tentatives multiples.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
fetch_func: La fonction à exécuter.
|
|
29
|
+
attempts: Nombre maximum de tentatives (défaut: 3).
|
|
30
|
+
backoff_sec: Temps d'attente (en secondes) entre deux tentatives (défaut: 5).
|
|
31
|
+
*args, **kwargs: Arguments passés à fetch_func.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Any: Les données retournées par fetch_func.
|
|
35
|
+
|
|
36
|
+
Raises:
|
|
37
|
+
DataSourceError: Si toutes les tentatives échouent.
|
|
38
|
+
"""
|
|
39
|
+
last_exception = None
|
|
40
|
+
|
|
41
|
+
for attempt in range(1, attempts + 1):
|
|
42
|
+
try:
|
|
43
|
+
return fetch_func(*args, **kwargs)
|
|
44
|
+
except Exception as e:
|
|
45
|
+
last_exception = e
|
|
46
|
+
logger.warning(
|
|
47
|
+
f"Échec de la récupération (Tentative {attempt}/{attempts}) : {e}"
|
|
48
|
+
)
|
|
49
|
+
if attempt < attempts:
|
|
50
|
+
time.sleep(backoff_sec)
|
|
51
|
+
|
|
52
|
+
# Si on sort de la boucle, c'est que toutes les tentatives ont échoué
|
|
53
|
+
logger.error(f"Toutes les tentatives ({attempts}) ont échoué.")
|
|
54
|
+
raise DataSourceError(f"Échec définitif de récupération : {last_exception}")
|