stats : robustesse aux prix NULL (top villes, familles, records) — corrige le 500 Internal report error des 5 rapports PDF
1 changed file +12 −5
modified
immoka/stats.py
+12 −5
@@ -104,6 +104,8 @@ def _fold(s: str) -> str: | ||
| 104 | 104 | |
| 105 | 105 | |
| 106 | 106 | def _median(vals: list[float]) -> float | None: |
| 107 | + # défensif : la DB peut contenir des prix NULL — on les écarte | |
| 108 | + vals = [v for v in vals if isinstance(v, (int, float))] | |
| 107 | 109 | return statistics.median(vals) if vals else None |
| 108 | 110 | |
| 109 | 111 | |
@@ -758,9 +760,11 @@ def _compute_con(con, frm_q, to_q, period) -> dict: | ||
| 758 | 760 | n_prev = new_city_prev.get(city, 0) |
| 759 | 761 | net = n_new - gone_city.get(city, 0) |
| 760 | 762 | d = _fmt_pct(n_new, n_prev) if prev_ok and n_prev else None |
| 763 | + pn = [v for v in ps if isinstance(v, (int, float))] # prix NULL écartés | |
| 761 | 764 | top_rows.append([ |
| 762 | − city, len(ps), _fmt_money(sum(ps) / len(ps)), | |
| 763 | − _fmt_money(statistics.median(ps)), n_new, | |
| 765 | + city, len(ps), | |
| 766 | + _fmt_money(sum(pn) / len(pn)) if pn else "—", | |
| 767 | + _fmt_money(statistics.median(pn)) if pn else "—", n_new, | |
| 764 | 768 | f"{'+' if net >= 0 else ''}{net}", |
| 765 | 769 | (f"{'+' if d >= 0 else ''}{str(d).replace('.', ',')} %" |
| 766 | 770 | if d is not None else "—"), |
@@ -889,7 +893,9 @@ def _compute_con(con, frm_q, to_q, period) -> dict: | ||
| 889 | 893 | key=lambda kv: -len(kv[1]["prices"]))[:30]: |
| 890 | 894 | fam_rows.append([ |
| 891 | 895 | fam, len(e["srcs"]), len(e["prices"]), e["new"], |
| 892 | − _fmt_money(statistics.median(e["prices"])) if e["prices"] else "—", | |
| 896 | + (_fmt_money(statistics.median(pn)) | |
| 897 | + if (pn := [v for v in e["prices"] | |
| 898 | + if isinstance(v, (int, float))]) else "—"), | |
| 893 | 899 | (datetime.fromtimestamp(e["sync"], TZ).strftime("%Y-%m-%d %H:%M") |
| 894 | 900 | if e["sync"] else "—")]) |
| 895 | 901 | tables.append({ |
@@ -950,8 +956,9 @@ def _compute_con(con, frm_q, to_q, period) -> dict: | ||
| 950 | 956 | "value": _fmt_money(top_price["price"]) + |
| 951 | 957 | (f" · {top_price['city']}" |
| 952 | 958 | if top_price["city"] else "")}) |
| 953 | − med_cities = {c: statistics.median(ps) for c, ps in city_prices.items() | |
| 954 | − if len(ps) >= 30} | |
| 959 | + med_cities = {c: statistics.median(pn) for c, ps in city_prices.items() | |
| 960 | + if len(pn := [v for v in ps | |
| 961 | + if isinstance(v, (int, float))]) >= 30} | |
| 955 | 962 | if med_cities: |
| 956 | 963 | c_hi = max(med_cities, key=med_cities.get) |
| 957 | 964 | c_lo = min(med_cities, key=med_cities.get) |
| 958 | 965 | |