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>
138 lines
5.1 KiB
Python
138 lines
5.1 KiB
Python
"""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,
|
||
)
|