Major rework of the citizen-facing page: - Chart + sidebar layout (auth/vote/countdown in right sidebar) - DisplaySettings component (font size, chart density, color palettes) - Adaptive CSS with clamp() throughout, responsive breakpoints at 480/768/1024 - Baseline charts zoomed on first tier for small consumption detail - Marginal price chart with dual Y-axes (foyers left, €/m³ right) - Key metrics banner (5 columns: recettes, palier, prix palier, prix médian, mon prix) - Client-side p0/impacts computation, draggable median price bar - Household dots toggle, vote overlay curves - Auth returns volume_m3, vote captures submitted_at - Cleaned header nav (removed Accueil/Super Admin for public visitors) - Terminology: foyer for bills, électeur for votes - 600m³ added to impact reference volumes - Realistic seed votes (50 votes, 3 profiles) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
70 lines
1.7 KiB
Python
70 lines
1.7 KiB
Python
"""
|
|
Current (linear) pricing model.
|
|
|
|
Ported from eau.py:256-354 (CurrentModel).
|
|
Pure Python + numpy, no matplotlib.
|
|
"""
|
|
|
|
from dataclasses import dataclass
|
|
|
|
from app.engine.pricing import HouseholdData
|
|
|
|
|
|
@dataclass
|
|
class LinearTariffResult:
|
|
"""Result of the linear tariff computation."""
|
|
p0: float # flat price per m³
|
|
curve_volumes: list[float]
|
|
curve_bills_rp: list[float]
|
|
curve_bills_rs: list[float]
|
|
curve_price_m3_rp: list[float]
|
|
curve_price_m3_rs: list[float]
|
|
|
|
|
|
def compute_linear_tariff(
|
|
households: list[HouseholdData],
|
|
recettes: float,
|
|
abop: float,
|
|
abos: float,
|
|
vmax: float = 2100,
|
|
nbpts: int = 200,
|
|
) -> LinearTariffResult:
|
|
"""
|
|
Compute the linear (current) pricing model.
|
|
|
|
p0 = (recettes - Σ abo) / Σ volume
|
|
"""
|
|
total_abo = 0.0
|
|
total_volume = 0.0
|
|
|
|
for h in households:
|
|
abo = abos if h.status == "RS" else abop
|
|
total_abo += abo
|
|
total_volume += max(h.volume_m3, 1e-5)
|
|
|
|
if total_abo >= recettes or total_volume == 0:
|
|
p0 = 0.0
|
|
else:
|
|
p0 = (recettes - total_abo) / total_volume
|
|
|
|
# Generate curves
|
|
import numpy as np
|
|
vv = np.linspace(1e-5, vmax, nbpts)
|
|
|
|
bills_rp = abop + p0 * vv
|
|
bills_rs = abos + p0 * vv
|
|
price_m3_rp = abop / vv + p0
|
|
price_m3_rs = abos / vv + p0
|
|
|
|
def sanitize(arr):
|
|
return [0.0 if (x != x or x == float('inf') or x == float('-inf')) else float(x) for x in arr]
|
|
|
|
return LinearTariffResult(
|
|
p0=p0,
|
|
curve_volumes=sanitize(vv),
|
|
curve_bills_rp=sanitize(bills_rp),
|
|
curve_bills_rs=sanitize(bills_rs),
|
|
curve_price_m3_rp=sanitize(price_m3_rp),
|
|
curve_price_m3_rs=sanitize(price_m3_rs),
|
|
)
|