"""Blueprint des routes de génération des rapports et statistiques. Ce module gère l'affichage des rapports annuels et mensuels. Il calcule les distances cumulées, les émissions de CO2, les frais réels selon le barème kilométrique officiel, et compile les statistiques de temps de travail (médianes quotidiennes et hebdomadaires) pour chaque mois de l'année sélectionnée. """ from collections import defaultdict from datetime import date import sqlalchemy as sa from flask import Blueprint, render_template, request from app import db from app.business.time_calc import count_day_types, minutes_to_str, monthly_stats from app.business.travel_calc import compute_co2_grams, compute_frais_reels, compute_km_for_entry from app.config_loader import get_bareme, get_journeys, get_vehicles from app.models import WorkEntry bp = Blueprint("reports", __name__, url_prefix="/reports") MONTHS_FR = { 1: "Janvier", 2: "Février", 3: "Mars", 4: "Avril", 5: "Mai", 6: "Juin", 7: "Juillet", 8: "Août", 9: "Septembre", 10: "Octobre", 11: "Novembre", 12: "Décembre", } @bp.route("/") def index(): """Génère et affiche le rapport d'activité annuel et mensuel. Cette route effectue les opérations suivantes : 1. Récupère l'année cible depuis les paramètres de requête HTTP GET (`year`). Par défaut, utilise l'année en cours. 2. Récupère toutes les entrées de temps (`WorkEntry`) de cette année civile. 3. Récupère la configuration des véhicules et des trajets depuis le fichier TOML. 4. Calcule les statistiques annuelles cumulées : - Distances parcourues par véhicule (`total_km`). - Émissions de CO2 totales en grammes (`total_co2`), converties ensuite en kilogrammes. - Frais réels remboursables par véhicule (`frais_reels`) en appliquant le barème kilométrique officiel de l'année correspondante (avec une majoration de +20% pour les véhicules électriques). - Nombre de jours par type de journée (`day_type_counts`). 5. Regroupe les entrées par mois pour calculer les statistiques mensuelles : - Nom du mois en français. - Nombre de jours saisis. - Distances parcourues par véhicule et distance totale du mois. - Durée quotidienne médiane de travail et durée hebdomadaire médiane de travail (formatées en chaînes "HHhMM"). 6. Rend le template `reports.html` avec l'ensemble de ces données de contexte. Méthode HTTP : GET Paramètres de requête (Query Params) : year (int, optionnel) : L'année civile pour laquelle générer le rapport (ex: `?year=2026`). Par défaut, l'année courante. Retourne: str: Le rendu HTML de la page des rapports (`reports.html`). """ year = request.args.get("year", date.today().year, type=int) start = date(year, 1, 1) end = date(year, 12, 31) entries = db.session.scalars( sa.select(WorkEntry).where(WorkEntry.date.between(start, end)) ).all() vehicles = get_vehicles() journeys = get_journeys() # --- Stats annuelles (inchangées) --- total_km = {} total_co2 = 0.0 for entry in entries: km = compute_km_for_entry(entry.journey_profile_id, journeys, entry.motor_vehicle_id) for v, d in km.items(): total_km[v] = total_km.get(v, 0) + d total_co2 += compute_co2_grams(km, vehicles) frais_reels = {} for vehicle_id, km in total_km.items(): vehicle = vehicles.get(vehicle_id, {}) cv = vehicle.get("cv") if cv: tranches = get_bareme(year, cv) electric = vehicle.get("fuel") == "electric" frais_reels[vehicle_id] = round(compute_frais_reels(km, tranches, electric=electric), 2) day_type_counts = count_day_types(entries) # --- Stats mensuelles --- entries_by_month: dict[int, list] = defaultdict(list) for entry in entries: entries_by_month[entry.date.month].append(entry) monthly_data = {} for month in range(1, 13): month_entries = entries_by_month.get(month, []) month_km: dict[str, int] = {} for entry in month_entries: km = compute_km_for_entry(entry.journey_profile_id, journeys, entry.motor_vehicle_id) for v, d in km.items(): month_km[v] = month_km.get(v, 0) + d stats = monthly_stats(month_entries) monthly_data[month] = { "month_name": MONTHS_FR[month], "entry_count": len(month_entries), "km_by_vehicle": month_km, "km_total": sum(month_km.values()), "median_daily_str": minutes_to_str(stats["median_daily_min"]) if month_entries else "–", "median_weekly_str": minutes_to_str(stats["median_weekly_min"]) if month_entries else "–", } return render_template( "reports.html", year=year, total_km=total_km, total_co2_kg=round(total_co2 / 1000, 2), frais_reels=frais_reels, vehicles=vehicles, day_type_counts=day_type_counts, monthly_data=monthly_data, )