Files
tableau-de-bord/app/routes/reports.py
Antoine Van Elstraete bd5bc496eb doc: document configuration and application routes
Co-authored-by: OpenAI/GPT-5.6-Terra <vibecoder@antoineve.me>
Co-authored-by: Google/Gemini-3.5-Flash <vibecoder@antoineve.me>
Co-authored-by: DeepSeek/DeepSeek-v4-Flash <vibecoder@antoineve.me>
2026-08-13 11:05:10 +02:00

138 lines
5.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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,
)