File size: 3,648 Bytes
71c08ae
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4ccb3e1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71c08ae
 
4ccb3e1
71c08ae
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
"""Fetch real French car models with their official fuel consumption.

Source: ADEME "Car Labelling" open dataset (combined-cycle WLTP consumption in
L/100 km for every model commercialised in France). We map each model's energy
type to the matching pump fuel and dedupe to one entry per commercial model.
"""
from __future__ import annotations

import json
import urllib.parse
import urllib.request

ADEME_URL = "https://data.ademe.fr/data-fair/api/v1/datasets/ademe-car-labelling/lines"
SELECT = "Marque,Description_Commerciale,Energie,Conso_vitesse_mixte_Min,Conso_vitesse_mixte_Max"

# ADEME "Energie" -> our pump-fuel label (None = no liquid fuel, e.g. pure electric).
ENERGIE_FUEL = {
    "ESSENCE": "E10",
    "GAZOLE": "Gazole",
    "GAZ+ELEC HNR": "Gazole",      # diesel hybrid
    "ESS+ELEC HNR": "E10",         # petrol hybrid
    "ELEC+ESSENC HR": "E10",       # plug-in petrol hybrid
    "ELEC+GAZOLE HR": "Gazole",    # plug-in diesel hybrid
    "SUPERETHANOL": "E85",
    "ESS+G.P.L.": "GPLc",
    "ELECTRIC": None,
}

# Suffix appended to the model name so hybrids are explicit in the picker.
HYBRID_TAG = {
    "ESS+ELEC HNR": " — hybride",
    "GAZ+ELEC HNR": " — hybride",
    "ELEC+ESSENC HR": " — hybride rechargeable",
    "ELEC+GAZOLE HR": " — hybride rechargeable",
}


def _fetch_all_rows(timeout: int, page_size: int = 10000) -> list[dict]:
    """Page through every line of the dataset via data-fair's `after` cursor.

    The API caps a single response, so we follow the server-provided `next` link
    (which carries the `after=<cursor>`) until the whole dataset is collected.
    """
    url = ADEME_URL + f"?size={page_size}&select=" + urllib.parse.quote(SELECT)
    rows: list[dict] = []
    for _ in range(200):  # hard safety cap on the number of pages
        req = urllib.request.Request(url, headers={"User-Agent": "petrol-map/1.0"})
        with urllib.request.urlopen(req, timeout=timeout) as resp:
            data = json.load(resp)
        page = data.get("results", [])
        rows.extend(page)
        total = data.get("total")
        nxt = data.get("next")
        if not page or not nxt or (isinstance(total, int) and len(rows) >= total):
            break
        url = nxt
    return rows


def fetch_cars(timeout: int = 90) -> list[list]:
    """Return [[label, fuel, consumption_L_per_100km], ...], sorted by label."""
    rows = _fetch_all_rows(timeout)

    agg: dict[tuple, list[float]] = {}  # (label, fuel) -> [sum_cons, n]
    for r in rows:
        energie = (r.get("Energie") or "").strip()
        fuel = ENERGIE_FUEL.get(energie)
        if not fuel:
            continue
        marque = (r.get("Marque") or "").strip()
        desc = (r.get("Description_Commerciale") or "").strip()
        if not marque or not desc:
            continue
        vals = [v for v in (r.get("Conso_vitesse_mixte_Min"), r.get("Conso_vitesse_mixte_Max"))
                if isinstance(v, (int, float)) and v > 0]
        if not vals:
            continue
        cons = sum(vals) / len(vals)
        label = f"{marque} {desc}{HYBRID_TAG.get(energie, '')}"
        key = (label, fuel)
        if key in agg:
            agg[key][0] += cons
            agg[key][1] += 1
        else:
            agg[key] = [cons, 1]

    cars = [[label, fuel, round(s / n, 1)] for (label, fuel), (s, n) in agg.items()]
    cars.sort(key=lambda c: c[0])
    return cars


if __name__ == "__main__":
    cars = fetch_cars()
    blob = json.dumps(cars, ensure_ascii=False, separators=(",", ":"))
    print(f"{len(cars)} models, embedded size ~{len(blob) / 1024:.0f} KB")
    for c in cars[:5]:
        print("  ", c)