You've already forked donnees_meteo
Réorganisation
This commit is contained in:
@@ -1,52 +0,0 @@
|
||||
# scripts/check_missing_values.py
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from meteo.dataset import load_raw_csv
|
||||
from meteo.quality import summarize_missing_values
|
||||
|
||||
|
||||
CSV_PATH = Path("data/weather_minutely.csv")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
if not CSV_PATH.exists():
|
||||
print(f"⚠ Fichier introuvable : {CSV_PATH}")
|
||||
print(" Assurez-vous d'avoir généré le dataset minuté.")
|
||||
return
|
||||
|
||||
df = load_raw_csv(CSV_PATH)
|
||||
print(f"Dataset chargé : {CSV_PATH}")
|
||||
print(f" Lignes : {len(df)}")
|
||||
print(f" Colonnes : {list(df.columns)}")
|
||||
|
||||
summary = summarize_missing_values(df)
|
||||
|
||||
print()
|
||||
print("=== Synthèse des valeurs manquantes ===")
|
||||
print(f"Total de cellules : {summary.total_cells}")
|
||||
print(f"Cellules manquantes : {summary.missing_cells}")
|
||||
print(f"Fraction manquante : {summary.fraction_missing:.6f}")
|
||||
print(f"Lignes complètes : {summary.rows_fully_complete}")
|
||||
print(f"Lignes avec des trous : {summary.rows_with_missing}")
|
||||
print(f"Fraction lignes complètes : {summary.fraction_rows_complete:.6f}")
|
||||
|
||||
print()
|
||||
print("Valeurs manquantes par colonne :")
|
||||
for col, n_missing in summary.missing_by_column.items():
|
||||
print(f" - {col:13s} : {n_missing}")
|
||||
|
||||
if summary.missing_cells == 0:
|
||||
print()
|
||||
print("✔ Aucune valeur manquante dans le dataset minuté.")
|
||||
else:
|
||||
print()
|
||||
print("⚠ Il reste des valeurs manquantes.")
|
||||
print(" Exemple de lignes concernées :")
|
||||
rows_with_missing = df[df.isna().any(axis=1)]
|
||||
print(rows_with_missing.head(10))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,61 +0,0 @@
|
||||
# scripts/describe_minutely_dataset.py
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pandas as pd
|
||||
|
||||
from meteo.dataset import load_raw_csv
|
||||
|
||||
|
||||
CSV_PATH = Path("data/weather_minutely.csv")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
if not CSV_PATH.exists():
|
||||
print(f"⚠ Fichier introuvable : {CSV_PATH}")
|
||||
print(" Assurez-vous d'avoir généré le dataset minuté.")
|
||||
return
|
||||
|
||||
df = load_raw_csv(CSV_PATH)
|
||||
print(f"Dataset minuté chargé : {CSV_PATH}")
|
||||
print(f" Lignes : {len(df)}")
|
||||
print(f" Colonnes : {list(df.columns)}")
|
||||
print(f" Période : {df.index[0]} → {df.index[-1]}")
|
||||
print()
|
||||
|
||||
# 1. Résumé statistique classique
|
||||
print("=== describe() ===")
|
||||
print(df.describe())
|
||||
print()
|
||||
|
||||
# 2. Min / max par variable avec leurs dates
|
||||
print("=== Min / max avec dates ===")
|
||||
for col in df.columns:
|
||||
series = df[col]
|
||||
|
||||
min_val = series.min()
|
||||
max_val = series.max()
|
||||
min_ts = series.idxmin()
|
||||
max_ts = series.idxmax()
|
||||
|
||||
print(f"- {col}:")
|
||||
print(f" min = {min_val} à {min_ts}")
|
||||
print(f" max = {max_val} à {max_ts}")
|
||||
print()
|
||||
|
||||
# 3. Vérification rapide de la continuité temporelle
|
||||
print("=== Vérification de la continuité temporelle ===")
|
||||
diffs = df.index.to_series().diff().dropna()
|
||||
counts = diffs.value_counts().sort_index()
|
||||
|
||||
print("Différences d'intervalle (top 5):")
|
||||
print(counts.head())
|
||||
print()
|
||||
|
||||
nb_not_60s = (diffs != pd.Timedelta(minutes=1)).sum()
|
||||
print(f"Nombre d'intervalles ≠ 60s : {nb_not_60s}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,44 +0,0 @@
|
||||
# tests/export_station_data.py
|
||||
from __future__ import annotations
|
||||
|
||||
from contextlib import closing
|
||||
|
||||
from meteo.config import InfluxSettings
|
||||
from meteo.influx_client import create_influx_client
|
||||
from meteo.station_config import default_station_config
|
||||
from meteo.export import export_station_data
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""
|
||||
Exporte les données de la station météo vers un fichier CSV brut.
|
||||
|
||||
Par défaut, on exporte les 7 derniers jours dans `data/weather_raw_7d.csv`.
|
||||
"""
|
||||
settings = InfluxSettings.from_env()
|
||||
station_config = default_station_config()
|
||||
|
||||
print("Configuration InfluxDB :")
|
||||
print(f" URL : {settings.url}")
|
||||
print(f" Org : {settings.org}")
|
||||
print(f" Bucket : {settings.bucket}")
|
||||
print()
|
||||
|
||||
with closing(create_influx_client(settings)) as client:
|
||||
print("→ Export des 7 derniers jours…")
|
||||
output_path = export_station_data(
|
||||
client=client,
|
||||
bucket=settings.bucket,
|
||||
config=station_config,
|
||||
start="-7d", # à ajuster plus tard si besoin
|
||||
stop=None, # now()
|
||||
output_path="data/weather_raw_7d.csv",
|
||||
file_format="csv",
|
||||
)
|
||||
|
||||
print()
|
||||
print(f"✔ Export terminé : {output_path}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,49 +0,0 @@
|
||||
# tests/export_station_data_full.py
|
||||
from __future__ import annotations
|
||||
|
||||
from contextlib import closing
|
||||
|
||||
from meteo.config import InfluxSettings
|
||||
from meteo.influx_client import create_influx_client
|
||||
from meteo.station_config import default_station_config
|
||||
from meteo.export import export_station_data
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""
|
||||
Exporte l'historique complet de la station météo vers un fichier CSV.
|
||||
|
||||
On utilise `start=0`, ce qui signifie "depuis le début des données"
|
||||
(en pratique depuis l'epoch, donc tout ce que le bucket contient).
|
||||
"""
|
||||
settings = InfluxSettings.from_env()
|
||||
station_config = default_station_config()
|
||||
|
||||
print("Configuration InfluxDB :")
|
||||
print(f" URL : {settings.url}")
|
||||
print(f" Org : {settings.org}")
|
||||
print(f" Bucket : {settings.bucket}")
|
||||
print()
|
||||
|
||||
print("⚠ Attention : un export complet peut produire un fichier volumineux "
|
||||
"et prendre un certain temps si l'historique est long.")
|
||||
print()
|
||||
|
||||
with closing(create_influx_client(settings)) as client:
|
||||
print("→ Export de l'historique complet…")
|
||||
output_path = export_station_data(
|
||||
client=client,
|
||||
bucket=settings.bucket,
|
||||
config=station_config,
|
||||
start="0", # depuis le début des données
|
||||
stop=None, # jusqu'à maintenant
|
||||
output_path="data/weather_raw_full.csv",
|
||||
file_format="csv", # vous pouvez mettre "parquet" si vous préférez
|
||||
)
|
||||
|
||||
print()
|
||||
print(f"✔ Export terminé : {output_path}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,32 +0,0 @@
|
||||
# scripts/fill_formatted_1s.py
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from meteo.dataset import load_raw_csv, fill_missing_with_previous
|
||||
|
||||
|
||||
INPUT_CSV_PATH = Path("data/weather_formatted_1s.csv")
|
||||
OUTPUT_CSV_PATH = Path("data/weather_filled_1s.csv")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
if not INPUT_CSV_PATH.exists():
|
||||
print(f"⚠ Fichier introuvable : {INPUT_CSV_PATH}")
|
||||
print(" Lancez d'abord : python -m scripts.format_raw_csv")
|
||||
return
|
||||
|
||||
df_1s = load_raw_csv(INPUT_CSV_PATH)
|
||||
print(f"Fichier 1s formaté chargé : {INPUT_CSV_PATH}")
|
||||
print(f" Lignes : {len(df_1s)}, colonnes : {list(df_1s.columns)}")
|
||||
|
||||
df_filled = fill_missing_with_previous(df_1s)
|
||||
print(f"Après propagation des dernières valeurs connues : {len(df_filled)} lignes")
|
||||
|
||||
OUTPUT_CSV_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||
df_filled.to_csv(OUTPUT_CSV_PATH, index_label="time")
|
||||
print(f"✔ Fichier 1s 'complet' écrit dans : {OUTPUT_CSV_PATH.resolve()}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,31 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from meteo.dataset import load_raw_csv, combine_close_observations
|
||||
|
||||
|
||||
RAW_CSV_PATH = Path("data/weather_raw_full.csv")
|
||||
OUTPUT_CSV_PATH = Path("data/weather_formatted_1s.csv")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
if not RAW_CSV_PATH.exists():
|
||||
print(f"⚠ Fichier brut introuvable : {RAW_CSV_PATH}")
|
||||
return
|
||||
|
||||
df_raw = load_raw_csv(RAW_CSV_PATH)
|
||||
print(f"Fichier brut chargé : {RAW_CSV_PATH}")
|
||||
print(f" Lignes : {len(df_raw)}, colonnes : {list(df_raw.columns)}")
|
||||
print(f" Type d'index : {type(df_raw.index)}")
|
||||
|
||||
df_fmt = combine_close_observations(df_raw, freq="1s", agg="mean")
|
||||
print(f"Après combinaison (1s) : {len(df_fmt)} lignes")
|
||||
|
||||
OUTPUT_CSV_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||
df_fmt.to_csv(OUTPUT_CSV_PATH, index_label="time")
|
||||
print(f"✔ Fichier formaté écrit dans : {OUTPUT_CSV_PATH.resolve()}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,46 +0,0 @@
|
||||
# scripts/list_time_gaps.py
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from meteo.dataset import load_raw_csv
|
||||
from meteo.gaps import find_time_gaps
|
||||
|
||||
|
||||
CSV_PATH = Path("data/weather_minutely.csv")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
if not CSV_PATH.exists():
|
||||
print(f"⚠ Fichier introuvable : {CSV_PATH}")
|
||||
return
|
||||
|
||||
df = load_raw_csv(CSV_PATH)
|
||||
print(f"Dataset minuté chargé : {CSV_PATH}")
|
||||
print(f" Lignes : {len(df)}")
|
||||
|
||||
gaps = find_time_gaps(df)
|
||||
total_missing = sum(g.missing_intervals for g in gaps)
|
||||
|
||||
print()
|
||||
print("=== Gaps temporels détectés ===")
|
||||
print(f"Nombre de gaps : {len(gaps)}")
|
||||
print(f"Total minutes manquantes (théoriques) : {total_missing}")
|
||||
print()
|
||||
|
||||
if not gaps:
|
||||
print("✔ Aucun gap détecté, la série est parfaitement régulière.")
|
||||
return
|
||||
|
||||
print("Top 10 des gaps les plus longs :")
|
||||
gaps_sorted = sorted(gaps, key=lambda g: g.missing_intervals, reverse=True)[:10]
|
||||
for g in gaps_sorted:
|
||||
print(
|
||||
f"- De {g.before} à {g.after} "
|
||||
f"(durée: {g.duration}, manquants: {g.missing_intervals}, "
|
||||
f"de {g.missing_start} à {g.missing_end})"
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,59 +0,0 @@
|
||||
# scripts/make_minutely_dataset.py
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from meteo.dataset import load_raw_csv, resample_to_minutes
|
||||
from meteo.config import StationLocation
|
||||
from meteo.solar import add_solar_elevation_column
|
||||
from meteo.season import add_season_column
|
||||
|
||||
|
||||
FORMATTED_CSV_PATH = Path("data/weather_filled_1s.csv")
|
||||
OUTPUT_CSV_PATH = Path("data/weather_minutely.csv")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
if not FORMATTED_CSV_PATH.exists():
|
||||
print(f"⚠ Fichier formaté introuvable : {FORMATTED_CSV_PATH}")
|
||||
print(" Lancez d'abord : python -m scripts.format_raw_csv")
|
||||
return
|
||||
|
||||
df_1s = load_raw_csv(FORMATTED_CSV_PATH)
|
||||
print(f"Fichier 1s chargé : {FORMATTED_CSV_PATH}")
|
||||
print(f" Lignes : {len(df_1s)}, colonnes : {list(df_1s.columns)}")
|
||||
|
||||
df_min = resample_to_minutes(df_1s)
|
||||
print(f"Après resampling 60s : {len(df_min)} lignes")
|
||||
|
||||
hemisphere = "north"
|
||||
location = StationLocation.from_env(optional=True)
|
||||
|
||||
if location is not None:
|
||||
hemisphere = "south" if location.latitude < 0 else "north"
|
||||
print(
|
||||
f"Ajout de l'élévation solaire (lat={location.latitude}, lon={location.longitude}, "
|
||||
f"alt={location.elevation_m} m)..."
|
||||
)
|
||||
add_solar_elevation_column(
|
||||
df_min,
|
||||
latitude=location.latitude,
|
||||
longitude=location.longitude,
|
||||
elevation_m=location.elevation_m,
|
||||
)
|
||||
else:
|
||||
print(
|
||||
"ℹ Coordonnées GPS non définies (STATION_LATITUDE / STATION_LONGITUDE). "
|
||||
"La colonne sun_elevation ne sera pas ajoutée."
|
||||
)
|
||||
print("ℹ Saison : hypothèse par défaut = hémisphère nord. Définissez STATION_LATITUDE pour adapter.")
|
||||
|
||||
add_season_column(df_min, hemisphere=hemisphere)
|
||||
|
||||
OUTPUT_CSV_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||
df_min.to_csv(OUTPUT_CSV_PATH, index_label="time")
|
||||
print(f"✔ Dataset minuté écrit dans : {OUTPUT_CSV_PATH.resolve()}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,45 +0,0 @@
|
||||
# scripts/plot_all_pairwise_scatter.py
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from meteo.dataset import load_raw_csv
|
||||
from meteo.variables import iter_variable_pairs
|
||||
from meteo.plots import plot_scatter_pair
|
||||
|
||||
|
||||
CSV_PATH = Path("data/weather_minutely.csv")
|
||||
OUTPUT_DIR = Path("figures/pairwise_scatter")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
if not CSV_PATH.exists():
|
||||
print(f"⚠ Fichier introuvable : {CSV_PATH}")
|
||||
return
|
||||
|
||||
df = load_raw_csv(CSV_PATH)
|
||||
print(f"Dataset minuté chargé : {CSV_PATH}")
|
||||
print(f" Lignes : {len(df)}")
|
||||
print(f" Colonnes : {list(df.columns)}")
|
||||
|
||||
pairs = iter_variable_pairs()
|
||||
print(f"Nombre de paires de variables : {len(pairs)}")
|
||||
|
||||
for var_x, var_y in pairs:
|
||||
filename = f"scatter_{var_x.key}_vs_{var_y.key}.png"
|
||||
output_path = OUTPUT_DIR / filename
|
||||
|
||||
print(f"→ Trace {var_y.key} en fonction de {var_x.key} → {output_path}")
|
||||
plot_scatter_pair(
|
||||
df=df,
|
||||
var_x=var_x,
|
||||
var_y=var_y,
|
||||
output_path=output_path,
|
||||
sample_step=10, # un point sur 10 : ≈ 32k points au lieu de 320k
|
||||
)
|
||||
|
||||
print("✔ Tous les graphiques de nuages de points ont été générés.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,106 +0,0 @@
|
||||
# scripts/plot_basic_variables.py
|
||||
"""Génère des séries temporelles simples (7 jours) pour chaque variable météo."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
import pandas as pd
|
||||
|
||||
from meteo.dataset import load_raw_csv
|
||||
from meteo.plots import export_plot_dataset
|
||||
from meteo.variables import Variable, VARIABLES
|
||||
|
||||
|
||||
CSV_PATH = Path("data/weather_minutely.csv")
|
||||
DEFAULT_OUTPUT_DIR = Path("figures/basic")
|
||||
|
||||
|
||||
def _prepare_slice(df: pd.DataFrame, *, last_days: int) -> pd.DataFrame:
|
||||
"""Extrait la fenêtre temporelle souhaitée et applique une moyenne horaire pour lisser la courbe."""
|
||||
|
||||
end = df.index.max()
|
||||
start = end - pd.Timedelta(days=last_days)
|
||||
df_slice = df.loc[start:end]
|
||||
numeric_slice = df_slice.select_dtypes(include="number")
|
||||
if numeric_slice.empty:
|
||||
raise RuntimeError("Aucune colonne numérique disponible pour les moyennes horaires.")
|
||||
return numeric_slice.resample("1h").mean()
|
||||
|
||||
|
||||
def _plot_variable(df_hourly: pd.DataFrame, var: Variable, output_dir: Path) -> Path | None:
|
||||
"""Trace la série pour une variable et retourne le chemin de l'image générée."""
|
||||
|
||||
if var.column not in df_hourly.columns:
|
||||
print(f"⚠ Colonne absente pour {var.key} ({var.column}).")
|
||||
return None
|
||||
|
||||
series = df_hourly[var.column].dropna()
|
||||
if series.empty:
|
||||
print(f"⚠ Aucun point valide pour {var.key} dans l'intervalle choisi.")
|
||||
return None
|
||||
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
output_path = output_dir / f"{var.key}_last_7_days.png"
|
||||
|
||||
export_plot_dataset(series.to_frame(name=var.column), output_path)
|
||||
|
||||
plt.figure()
|
||||
plt.plot(series.index, series)
|
||||
plt.xlabel("Temps (UTC)")
|
||||
unit_text = f" ({var.unit})" if var.unit else ""
|
||||
plt.ylabel(f"{var.label}{unit_text}")
|
||||
plt.title(f"{var.label} - Moyenne horaire sur les 7 derniers jours")
|
||||
plt.grid(True)
|
||||
plt.tight_layout()
|
||||
plt.savefig(output_path, dpi=150)
|
||||
plt.close()
|
||||
print(f"✔ Graphique généré : {output_path}")
|
||||
return output_path
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> None:
|
||||
parser = argparse.ArgumentParser(description="Trace les séries simples pour chaque variable météo.")
|
||||
parser.add_argument(
|
||||
"--only",
|
||||
nargs="*",
|
||||
help="Clés de variables à tracer (par défaut : toutes).",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--days",
|
||||
type=int,
|
||||
default=7,
|
||||
help="Nombre de jours à afficher (par défaut : 7).",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--output-dir",
|
||||
type=Path,
|
||||
default=DEFAULT_OUTPUT_DIR,
|
||||
help="Dossier où stocker les figures.",
|
||||
)
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
if not CSV_PATH.exists():
|
||||
raise FileNotFoundError(f"Dataset introuvable : {CSV_PATH}")
|
||||
|
||||
df = load_raw_csv(CSV_PATH)
|
||||
df_hourly = _prepare_slice(df, last_days=args.days)
|
||||
|
||||
selected: list[Variable]
|
||||
if args.only:
|
||||
keys = set(args.only)
|
||||
selected = [var for var in VARIABLES if var.key in keys]
|
||||
missing = keys - {var.key for var in selected}
|
||||
if missing:
|
||||
raise KeyError(f"Variables inconnues : {sorted(missing)}")
|
||||
else:
|
||||
selected = list(VARIABLES)
|
||||
|
||||
for variable in selected:
|
||||
_plot_variable(df_hourly, variable, args.output_dir)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,45 +0,0 @@
|
||||
# scripts/plot_correlation_heatmap.py
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from meteo.dataset import load_raw_csv
|
||||
from meteo.variables import VARIABLES
|
||||
from meteo.analysis import compute_correlation_matrix_for_variables
|
||||
from meteo.plots import plot_correlation_heatmap
|
||||
|
||||
|
||||
CSV_PATH = Path("data/weather_minutely.csv")
|
||||
OUTPUT_PATH = Path("figures/correlation_heatmap.png")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
if not CSV_PATH.exists():
|
||||
print(f"⚠ Fichier introuvable : {CSV_PATH}")
|
||||
print(" Assurez-vous d'avoir généré le dataset minuté.")
|
||||
return
|
||||
|
||||
df = load_raw_csv(CSV_PATH)
|
||||
print(f"Dataset minuté chargé : {CSV_PATH}")
|
||||
print(f" Lignes : {len(df)}")
|
||||
print(f" Colonnes : {list(df.columns)}")
|
||||
print()
|
||||
|
||||
corr = compute_correlation_matrix_for_variables(df, VARIABLES, method="pearson")
|
||||
|
||||
print("Matrice de corrélation (aperçu) :")
|
||||
print(corr)
|
||||
print()
|
||||
|
||||
output_path = plot_correlation_heatmap(
|
||||
corr=corr,
|
||||
variables=VARIABLES,
|
||||
output_path=OUTPUT_PATH,
|
||||
annotate=True,
|
||||
)
|
||||
|
||||
print(f"✔ Heatmap de corrélation sauvegardée dans : {output_path}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,128 +0,0 @@
|
||||
# scripts/plot_hexbin_explorations.py
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Callable
|
||||
|
||||
import numpy as np
|
||||
|
||||
from meteo.dataset import load_raw_csv
|
||||
from meteo.variables import VARIABLES_BY_KEY
|
||||
from meteo.plots import plot_hexbin_with_third_variable
|
||||
|
||||
|
||||
CSV_PATH = Path("data/weather_minutely.csv")
|
||||
OUTPUT_DIR = Path("figures/hexbin_explorations")
|
||||
|
||||
|
||||
REDUCE_FUNCTIONS: dict[str, Callable[[np.ndarray], float]] = {
|
||||
"mean": np.mean,
|
||||
"median": np.median,
|
||||
"max": np.max,
|
||||
}
|
||||
|
||||
REDUCE_LABEL_FR: dict[str, str] = {
|
||||
"mean": "moyenne",
|
||||
"median": "médiane",
|
||||
"max": "maximum",
|
||||
}
|
||||
|
||||
# Chaque scénario illustre soit une corrélation bien connue,
|
||||
# soit l'absence de structure entre variables.
|
||||
HEXBIN_SCENARIOS: list[dict[str, object]] = [
|
||||
{
|
||||
"x": "temperature",
|
||||
"y": "humidity",
|
||||
"color": "rain_rate",
|
||||
"filename": "hexbin_temp_humidity_color_rain.png",
|
||||
"description": (
|
||||
"Mettre en évidence comment l'humidité relative plafonne lorsque la température chute "
|
||||
"et comment les épisodes de pluie se situent dans une bande restreinte."
|
||||
),
|
||||
"reduce": "max",
|
||||
"gridsize": 50,
|
||||
"mincnt": 8,
|
||||
},
|
||||
{
|
||||
"x": "pressure",
|
||||
"y": "rain_rate",
|
||||
"color": "wind_speed",
|
||||
"filename": "hexbin_pressure_rain_color_wind.png",
|
||||
"description": (
|
||||
"Vérifier si des rafales accompagnent vraiment les chutes de pression. "
|
||||
"On s'attend à voir beaucoup de cases vides : la corrélation est loin d'être systématique."
|
||||
),
|
||||
"reduce": "median",
|
||||
"gridsize": 45,
|
||||
"mincnt": 5,
|
||||
},
|
||||
{
|
||||
"x": "illuminance",
|
||||
"y": "humidity",
|
||||
"color": "temperature",
|
||||
"filename": "hexbin_lux_humidity_color_temp.png",
|
||||
"description": (
|
||||
"Explorer le cycle jour/nuit : l'humidité monte quand l'illuminance chute, "
|
||||
"mais cela n'implique pas toujours une baisse rapide de température."
|
||||
),
|
||||
"reduce": "mean",
|
||||
"gridsize": 55,
|
||||
"mincnt": 6,
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def main() -> None:
|
||||
if not CSV_PATH.exists():
|
||||
print(f"⚠ Fichier introuvable : {CSV_PATH}")
|
||||
return
|
||||
|
||||
df = load_raw_csv(CSV_PATH)
|
||||
print(f"Dataset minuté chargé : {CSV_PATH}")
|
||||
print(f" Lignes : {len(df)}")
|
||||
print(f" Colonnes : {list(df.columns)}")
|
||||
print()
|
||||
|
||||
for scenario in HEXBIN_SCENARIOS:
|
||||
key_x = scenario["x"]
|
||||
key_y = scenario["y"]
|
||||
key_color = scenario["color"]
|
||||
|
||||
var_x = VARIABLES_BY_KEY[key_x]
|
||||
var_y = VARIABLES_BY_KEY[key_y]
|
||||
var_color = VARIABLES_BY_KEY[key_color]
|
||||
|
||||
filename = scenario["filename"]
|
||||
output_path = OUTPUT_DIR / filename
|
||||
|
||||
reduce_name = scenario.get("reduce", "mean")
|
||||
reduce_func = REDUCE_FUNCTIONS.get(reduce_name, np.mean)
|
||||
reduce_label = REDUCE_LABEL_FR.get(reduce_name, reduce_name)
|
||||
|
||||
gridsize = int(scenario.get("gridsize", 60))
|
||||
mincnt = int(scenario.get("mincnt", 5))
|
||||
|
||||
description = scenario["description"]
|
||||
print(f"→ Hexbin {var_y.key} vs {var_x.key} (couleur = {var_color.key})")
|
||||
print(f" {description}")
|
||||
|
||||
plot_hexbin_with_third_variable(
|
||||
df=df,
|
||||
var_x=var_x,
|
||||
var_y=var_y,
|
||||
var_color=var_color,
|
||||
output_path=output_path,
|
||||
gridsize=gridsize,
|
||||
mincnt=mincnt,
|
||||
reduce_func=reduce_func,
|
||||
reduce_func_label=reduce_label,
|
||||
cmap="magma",
|
||||
)
|
||||
print(f" ✔ Graphique enregistré : {output_path}")
|
||||
print()
|
||||
|
||||
print("✔ Tous les graphiques hexbin ont été générés.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,69 +0,0 @@
|
||||
# scripts/plot_lagged_correlations.py
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from meteo.dataset import load_raw_csv
|
||||
from meteo.variables import VARIABLES_BY_KEY
|
||||
from meteo.analysis import compute_lagged_correlation
|
||||
from meteo.plots import plot_lagged_correlation
|
||||
|
||||
|
||||
CSV_PATH = Path("data/weather_minutely.csv")
|
||||
OUTPUT_DIR = Path("figures/lagged_correlations")
|
||||
|
||||
|
||||
# Paires à analyser (clé de variable X, clé de variable Y)
|
||||
# Convention : X précède potentiellement Y
|
||||
INTERESTING_PAIRS: list[tuple[str, str]] = [
|
||||
("temperature", "humidity"),
|
||||
("temperature", "rain_rate"),
|
||||
("pressure", "rain_rate"),
|
||||
("pressure", "wind_speed"),
|
||||
("pressure", "illuminance"),
|
||||
("illuminance", "temperature"),
|
||||
("humidity", "rain_rate"),
|
||||
]
|
||||
|
||||
|
||||
def main() -> None:
|
||||
if not CSV_PATH.exists():
|
||||
print(f"⚠ Fichier introuvable : {CSV_PATH}")
|
||||
return
|
||||
|
||||
df = load_raw_csv(CSV_PATH)
|
||||
print(f"Dataset minuté chargé : {CSV_PATH}")
|
||||
print(f" Lignes : {len(df)}")
|
||||
print(f" Colonnes : {list(df.columns)}")
|
||||
print()
|
||||
|
||||
for key_x, key_y in INTERESTING_PAIRS:
|
||||
var_x = VARIABLES_BY_KEY[key_x]
|
||||
var_y = VARIABLES_BY_KEY[key_y]
|
||||
|
||||
print(f"→ Corrélation décalée : {var_x.key} → {var_y.key}")
|
||||
|
||||
lag_df = compute_lagged_correlation(
|
||||
df=df,
|
||||
var_x=var_x,
|
||||
var_y=var_y,
|
||||
max_lag_minutes=360, # ± 6 heures
|
||||
step_minutes=10, # pas de 10 minutes
|
||||
method="pearson",
|
||||
)
|
||||
|
||||
filename = f"lagcorr_{var_x.key}_to_{var_y.key}.png"
|
||||
output_path = OUTPUT_DIR / filename
|
||||
|
||||
plot_lagged_correlation(
|
||||
lag_df=lag_df,
|
||||
var_x=var_x,
|
||||
var_y=var_y,
|
||||
output_path=output_path,
|
||||
)
|
||||
|
||||
print("✔ Graphiques de corrélation décalée générés.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,65 +0,0 @@
|
||||
# scripts/plot_rolling_correlation_heatmap.py
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from meteo.dataset import load_raw_csv
|
||||
from meteo.variables import VARIABLES_BY_KEY
|
||||
from meteo.analysis import compute_rolling_correlations_for_pairs
|
||||
from meteo.plots import plot_rolling_correlation_heatmap
|
||||
|
||||
|
||||
CSV_PATH = Path("data/weather_minutely.csv")
|
||||
OUTPUT_PATH = Path("figures/rolling_correlations/rolling_correlation_heatmap.png")
|
||||
|
||||
ROLLING_PAIRS: list[tuple[str, str]] = [
|
||||
("temperature", "humidity"),
|
||||
("pressure", "rain_rate"),
|
||||
("pressure", "wind_speed"),
|
||||
("illuminance", "temperature"),
|
||||
("humidity", "rain_rate"),
|
||||
]
|
||||
|
||||
WINDOW_MINUTES = 180 # 3 heures pour observer les tendances synoptiques
|
||||
STEP_MINUTES = 30 # on n'échantillonne qu'un point sur 30 minutes
|
||||
|
||||
|
||||
def main() -> None:
|
||||
if not CSV_PATH.exists():
|
||||
print(f"⚠ Fichier introuvable : {CSV_PATH}")
|
||||
return
|
||||
|
||||
df = load_raw_csv(CSV_PATH)
|
||||
print(f"Dataset minuté chargé : {CSV_PATH}")
|
||||
print(f" Lignes : {len(df)}")
|
||||
print(f" Colonnes : {list(df.columns)}")
|
||||
print()
|
||||
|
||||
pairs = [(VARIABLES_BY_KEY[a], VARIABLES_BY_KEY[b]) for a, b in ROLLING_PAIRS]
|
||||
|
||||
rolling_df = compute_rolling_correlations_for_pairs(
|
||||
df=df,
|
||||
pairs=pairs,
|
||||
window_minutes=WINDOW_MINUTES,
|
||||
min_valid_fraction=0.7,
|
||||
step_minutes=STEP_MINUTES,
|
||||
method="pearson",
|
||||
)
|
||||
|
||||
if rolling_df.empty:
|
||||
print("⚠ Impossible de calculer les corrélations glissantes (données insuffisantes).")
|
||||
return
|
||||
|
||||
output_path = plot_rolling_correlation_heatmap(
|
||||
rolling_corr=rolling_df,
|
||||
output_path=OUTPUT_PATH,
|
||||
cmap="coolwarm",
|
||||
vmin=-1.0,
|
||||
vmax=1.0,
|
||||
)
|
||||
|
||||
print(f"✔ Heatmap de corrélations glissantes enregistrée : {output_path}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -36,7 +36,7 @@ def main() -> None:
|
||||
print()
|
||||
|
||||
if "season" not in df.columns:
|
||||
print("⚠ La colonne 'season' est absente. Relancez scripts.make_minutely_dataset.")
|
||||
print('⚠ La colonne "season" est absente. Relancez python "docs/02 - Préparation des données/scripts/make_minutely_dataset.py".')
|
||||
return
|
||||
|
||||
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
@@ -4,78 +4,124 @@ from __future__ import annotations
|
||||
import argparse
|
||||
import subprocess
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Iterable
|
||||
|
||||
|
||||
PLOT_MODULES: tuple[str, ...] = (
|
||||
"scripts.plot_basic_variables",
|
||||
"scripts.plot_calendar_overview",
|
||||
"scripts.plot_all_pairwise_scatter",
|
||||
"scripts.plot_correlation_heatmap",
|
||||
"scripts.plot_diurnal_cycle",
|
||||
"scripts.plot_hexbin_explorations",
|
||||
"scripts.plot_illuminance_focus",
|
||||
"scripts.plot_lagged_correlations",
|
||||
"scripts.plot_monthly_patterns",
|
||||
"scripts.plot_rain_event_composites",
|
||||
"scripts.plot_rain_hyetograph",
|
||||
"scripts.plot_rolling_correlation_heatmap",
|
||||
"scripts.plot_seasonal_overview",
|
||||
"scripts.plot_sun_elevation_relationships",
|
||||
"scripts.plot_wind_conditionals",
|
||||
"scripts.plot_wind_rose",
|
||||
"scripts.plot_wind_rose_rain",
|
||||
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class PlotScript:
|
||||
key: str
|
||||
path: Path
|
||||
|
||||
def display_path(self) -> str:
|
||||
try:
|
||||
return str(self.path.relative_to(PROJECT_ROOT))
|
||||
except ValueError:
|
||||
return str(self.path)
|
||||
|
||||
|
||||
PLOT_SCRIPTS: tuple[PlotScript, ...] = (
|
||||
PlotScript(
|
||||
"plot_basic_variables",
|
||||
PROJECT_ROOT / "docs" / "03 - Premiers graphiques" / "scripts" / "plot_basic_variables.py",
|
||||
),
|
||||
PlotScript("plot_calendar_overview", PROJECT_ROOT / "scripts" / "plot_calendar_overview.py"),
|
||||
PlotScript(
|
||||
"plot_all_pairwise_scatter",
|
||||
PROJECT_ROOT / "docs" / "04 - Corrélations binaires" / "scripts" / "plot_all_pairwise_scatter.py",
|
||||
),
|
||||
PlotScript(
|
||||
"plot_correlation_heatmap",
|
||||
PROJECT_ROOT / "docs" / "04 - Corrélations binaires" / "scripts" / "plot_correlation_heatmap.py",
|
||||
),
|
||||
PlotScript("plot_diurnal_cycle", PROJECT_ROOT / "scripts" / "plot_diurnal_cycle.py"),
|
||||
PlotScript("plot_illuminance_focus", PROJECT_ROOT / "scripts" / "plot_illuminance_focus.py"),
|
||||
PlotScript(
|
||||
"plot_hexbin_explorations",
|
||||
PROJECT_ROOT / "docs" / "06 - Corrélations multiples" / "scripts" / "plot_hexbin_explorations.py",
|
||||
),
|
||||
PlotScript("plot_monthly_patterns", PROJECT_ROOT / "scripts" / "plot_monthly_patterns.py"),
|
||||
PlotScript("plot_rain_event_composites", PROJECT_ROOT / "scripts" / "plot_rain_event_composites.py"),
|
||||
PlotScript("plot_rain_hyetograph", PROJECT_ROOT / "scripts" / "plot_rain_hyetograph.py"),
|
||||
PlotScript(
|
||||
"plot_lagged_correlations",
|
||||
PROJECT_ROOT / "docs" / "05 - Corrélations binaires avancées" / "scripts" / "plot_lagged_correlations.py",
|
||||
),
|
||||
PlotScript(
|
||||
"plot_rolling_correlation_heatmap",
|
||||
PROJECT_ROOT / "docs" / "05 - Corrélations binaires avancées" / "scripts" / "plot_rolling_correlation_heatmap.py",
|
||||
),
|
||||
PlotScript("plot_seasonal_overview", PROJECT_ROOT / "scripts" / "plot_seasonal_overview.py"),
|
||||
PlotScript(
|
||||
"plot_sun_elevation_relationships",
|
||||
PROJECT_ROOT / "scripts" / "plot_sun_elevation_relationships.py",
|
||||
),
|
||||
PlotScript("plot_wind_conditionals", PROJECT_ROOT / "scripts" / "plot_wind_conditionals.py"),
|
||||
PlotScript("plot_wind_rose", PROJECT_ROOT / "scripts" / "plot_wind_rose.py"),
|
||||
PlotScript("plot_wind_rose_rain", PROJECT_ROOT / "scripts" / "plot_wind_rose_rain.py"),
|
||||
)
|
||||
|
||||
|
||||
def _normalize_module(name: str) -> str:
|
||||
name = name.strip()
|
||||
if not name:
|
||||
raise ValueError("Nom de module vide.")
|
||||
return name if name.startswith("scripts.") else f"scripts.{name}"
|
||||
def _normalize_key(name: str) -> str:
|
||||
normalized = name.strip()
|
||||
if not normalized:
|
||||
raise ValueError("Nom de script vide.")
|
||||
if normalized.startswith("scripts."):
|
||||
normalized = normalized.split(".", 1)[1]
|
||||
return normalized
|
||||
|
||||
|
||||
def iter_modules(selected: Iterable[str] | None) -> list[str]:
|
||||
def iter_scripts(selected: Iterable[str] | None) -> list[PlotScript]:
|
||||
if not selected:
|
||||
return list(PLOT_MODULES)
|
||||
return list(PLOT_SCRIPTS)
|
||||
|
||||
normalized = [_normalize_module(name) for name in selected]
|
||||
modules: list[str] = []
|
||||
mapping = {script.key: script for script in PLOT_SCRIPTS}
|
||||
normalized = [_normalize_key(name) for name in selected]
|
||||
scripts: list[PlotScript] = []
|
||||
missing: list[str] = []
|
||||
for module in normalized:
|
||||
if module in PLOT_MODULES:
|
||||
modules.append(module)
|
||||
for key in normalized:
|
||||
script = mapping.get(key)
|
||||
if script is None:
|
||||
missing.append(key)
|
||||
else:
|
||||
missing.append(module)
|
||||
scripts.append(script)
|
||||
|
||||
if missing:
|
||||
available = "\n - ".join(("",) + PLOT_MODULES)
|
||||
available = "\n - ".join(("",) + tuple(script.key for script in PLOT_SCRIPTS))
|
||||
missing_list = "\n - ".join(("",) + tuple(missing))
|
||||
raise SystemExit(
|
||||
"\n".join(
|
||||
[
|
||||
"Les modules suivants ne sont pas reconnus :",
|
||||
"Les scripts suivants ne sont pas reconnus :",
|
||||
missing_list,
|
||||
"",
|
||||
"Modules disponibles :",
|
||||
"Scripts disponibles :",
|
||||
available,
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
return modules
|
||||
return scripts
|
||||
|
||||
|
||||
def run_module(module: str) -> bool:
|
||||
cmd = [sys.executable, "-m", module]
|
||||
print(f"\n=== {module} ===")
|
||||
def run_script(script: PlotScript) -> bool:
|
||||
if not script.path.exists():
|
||||
print(f"✘ Script introuvable : {script.display_path()}")
|
||||
return False
|
||||
|
||||
cmd = [sys.executable, str(script.path)]
|
||||
print(f"\n=== {script.key} ({script.display_path()}) ===")
|
||||
result = subprocess.run(cmd, check=False)
|
||||
|
||||
if result.returncode == 0:
|
||||
print(f"✔ {module} terminé avec succès.")
|
||||
print(f"✔ {script.key} terminé avec succès.")
|
||||
return True
|
||||
|
||||
print(f"✘ {module} a échoué (code {result.returncode}).")
|
||||
print(f"✘ {script.key} a échoué (code {result.returncode}).")
|
||||
return False
|
||||
|
||||
|
||||
@@ -84,21 +130,21 @@ def main(argv: list[str] | None = None) -> int:
|
||||
parser.add_argument(
|
||||
"--only",
|
||||
nargs="*",
|
||||
help="Limiter l'exécution à certains modules (ex: plot_temperature).",
|
||||
help="Limiter l'exécution à certains scripts (ex: plot_temperature).",
|
||||
)
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
modules = iter_modules(args.only)
|
||||
print(f"Exécution de {len(modules)} script(s) de tracé…")
|
||||
scripts = iter_scripts(args.only)
|
||||
print(f"Exécution de {len(scripts)} script(s) de tracé…")
|
||||
|
||||
success_count = 0
|
||||
for module in modules:
|
||||
if run_module(module):
|
||||
for script in scripts:
|
||||
if run_script(script):
|
||||
success_count += 1
|
||||
|
||||
print()
|
||||
print(f"Scripts réussis : {success_count}/{len(modules)}")
|
||||
failed = len(modules) - success_count
|
||||
print(f"Scripts réussis : {success_count}/{len(scripts)}")
|
||||
failed = len(scripts) - success_count
|
||||
if failed:
|
||||
print(f"⚠ {failed} script(s) ont échoué. Consultez les messages ci-dessus.")
|
||||
return 1
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
# tests/test_influx_connection.py
|
||||
from __future__ import annotations
|
||||
|
||||
from contextlib import closing
|
||||
|
||||
from influxdb_client.client.exceptions import InfluxDBError
|
||||
|
||||
from meteo.config import InfluxSettings
|
||||
from meteo.influx_client import create_influx_client, test_basic_query
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""
|
||||
Teste la communication avec le serveur InfluxDB :
|
||||
|
||||
1. Chargement de la configuration depuis l'environnement.
|
||||
2. Ping du serveur.
|
||||
3. Exécution d'une requête simple sur le bucket configuré.
|
||||
"""
|
||||
settings = InfluxSettings.from_env()
|
||||
|
||||
print("Configuration InfluxDB chargée :")
|
||||
print(f" URL : {settings.url}")
|
||||
print(f" Org : {settings.org}")
|
||||
print(f" Bucket : {settings.bucket}")
|
||||
print()
|
||||
|
||||
# Utilisation de `closing` pour garantir la fermeture du client.
|
||||
with closing(create_influx_client(settings)) as client:
|
||||
print("→ Ping du serveur InfluxDB…")
|
||||
if not client.ping():
|
||||
raise SystemExit("Échec du ping InfluxDB. Vérifiez l'URL et l'état du serveur.")
|
||||
|
||||
print("✔ Ping OK")
|
||||
print("→ Requête de test sur le bucket…")
|
||||
|
||||
tables = test_basic_query(client, settings.bucket)
|
||||
|
||||
# On fait un retour synthétique
|
||||
nb_tables = len(tables)
|
||||
nb_records = sum(len(table.records) for table in tables)
|
||||
print(f"✔ Requête de test réussie : {nb_tables} table(s), {nb_records} enregistrement(s) trouvés.")
|
||||
|
||||
if nb_records == 0:
|
||||
print("⚠ Le bucket est accessible, mais aucune donnée sur la dernière heure.")
|
||||
else:
|
||||
# Affiche un aperçu de la première table / premier record
|
||||
first_table = tables[0]
|
||||
first_record = first_table.records[0]
|
||||
print("Exemple de point :")
|
||||
print(f" time : {first_record.get_time()}")
|
||||
print(f" measurement : {first_record.get_measurement()}")
|
||||
print(f" field : {first_record.get_field()}")
|
||||
print(f" value : {first_record.get_value()}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,61 +0,0 @@
|
||||
# tests/test_influx_entities.py
|
||||
from __future__ import annotations
|
||||
|
||||
from contextlib import closing
|
||||
|
||||
from meteo.config import InfluxSettings
|
||||
from meteo.influx_client import create_influx_client
|
||||
from meteo.schema import (
|
||||
list_measurements,
|
||||
list_measurement_tag_keys,
|
||||
list_measurement_tag_values,
|
||||
)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""
|
||||
Explore les tags des measurements du bucket :
|
||||
|
||||
- affiche les keys de tags pour chaque measurement
|
||||
- si un tag `entity_id` est présent, affiche la liste de ses valeurs
|
||||
"""
|
||||
settings = InfluxSettings.from_env()
|
||||
|
||||
print(f"Bucket InfluxDB : {settings.bucket}")
|
||||
print()
|
||||
|
||||
with closing(create_influx_client(settings)) as client:
|
||||
measurements = list_measurements(client, settings.bucket)
|
||||
|
||||
if not measurements:
|
||||
print("⚠ Aucun measurement trouvé dans ce bucket.")
|
||||
return
|
||||
|
||||
for meas in measurements:
|
||||
print(f"Measurement « {meas} »")
|
||||
tag_keys = list_measurement_tag_keys(client, settings.bucket, meas)
|
||||
if not tag_keys:
|
||||
print(" (aucun tag trouvé)")
|
||||
print()
|
||||
continue
|
||||
|
||||
print(" Tag keys :")
|
||||
for key in tag_keys:
|
||||
print(f" - {key}")
|
||||
|
||||
if "entity_id" in tag_keys:
|
||||
entity_ids = list_measurement_tag_values(
|
||||
client,
|
||||
settings.bucket,
|
||||
meas,
|
||||
tag="entity_id",
|
||||
)
|
||||
print(" entity_id possibles :")
|
||||
for eid in entity_ids:
|
||||
print(f" - {eid}")
|
||||
|
||||
print()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,47 +0,0 @@
|
||||
# tests/test_influx_schema.py
|
||||
from __future__ import annotations
|
||||
|
||||
from contextlib import closing
|
||||
|
||||
from meteo.config import InfluxSettings
|
||||
from meteo.influx_client import create_influx_client
|
||||
from meteo.schema import list_measurements, list_measurement_fields
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""
|
||||
Explore le schéma du bucket InfluxDB configuré :
|
||||
|
||||
- liste des measurements disponibles
|
||||
- pour chacun, liste des champs (_field) et de leur type
|
||||
"""
|
||||
settings = InfluxSettings.from_env()
|
||||
|
||||
print(f"Bucket InfluxDB : {settings.bucket}")
|
||||
print()
|
||||
|
||||
with closing(create_influx_client(settings)) as client:
|
||||
measurements = list_measurements(client, settings.bucket)
|
||||
|
||||
if not measurements:
|
||||
print("⚠ Aucun measurement trouvé dans ce bucket.")
|
||||
return
|
||||
|
||||
print("Measurements disponibles :")
|
||||
for name in measurements:
|
||||
print(f" - {name}")
|
||||
print()
|
||||
|
||||
for name in measurements:
|
||||
print(f"Champs pour measurement « {name} » :")
|
||||
fields = list_measurement_fields(client, settings.bucket, name)
|
||||
if not fields:
|
||||
print(" (aucun champ trouvé)")
|
||||
else:
|
||||
for field in fields:
|
||||
print(f" - {field.name} (type: {field.type})")
|
||||
print()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user