Analyse af højforbrug i varmeregnskab — outlier-detektion og lejerrådgivning
Identificér højtforbrugende lejligheder i varmeregnskab: Z-score outlier-detektion, graddage-normalisering, sammenligning med nabo-lejligheder, årsager til højforbrug og kommunikation til lejere.
Af M-Bus Gateway
Lejligheder med usædvanligt højt varmeforbrug koster andre lejere penge via den faste 30%-del. Her er metoderne til identifikation og opfølgning.
Hvorfor højforbrug angår alle lejere
BEK 563 30/70-fordeling:
Fast del (30%): Pro-rata m² → Alle betaler (inkl. lavforbrugere)
Variabel del (70%): HCA-andel → Betaler for eget forbrug
Højforbrugerproblem:
Lejlighed 5A forbrug: 8.500 kWh (median i ejendommen: 3.200 kWh)
Årsag: Åbne termostatventiler + utæt vindue
Effekt på ANDRE lejeres regning:
→ Varmecentral bruger mere → Fjernvarmeregning stiger
→ Fast del 30%: ALLE lejere betaler mere (pro-rata)
→ Selvom andre lejere sparer → de har ikke sat termostaten ned (5A har)
Incitament-problem med 30% fast del:
→ Lejer 5A betaler kun 70% af sin ekstra varme direkte
→ 30% "eksternaliseres" til fællesskabet
→ Løsning: Tidlig identifikation + rådgivning til 5A
Z-score outlier-detektion
# server/src/analytics/outlier_detection.py
from statistics import mean, stdev
import math
def detect_heat_outliers(
readings_by_unit: dict[str, float], # unit_id → kWh
z_threshold: float = 2.0,
) -> list[dict]:
"""
Identificér enheder med usædvanligt forbrug.
Z-score > 2.0: Statistisk signifikant højforbrug (top 2.5%).
"""
values = list(readings_by_unit.values())
if len(values) < 3:
return []
mu = mean(values)
sigma = stdev(values)
if sigma == 0:
return []
outliers = []
for unit_id, kwh in readings_by_unit.items():
z = (kwh - mu) / sigma
if z > z_threshold:
outliers.append({
"unit_id": unit_id,
"kwh": kwh,
"z_score": round(z, 2),
"pct_above_median": round((kwh / mu - 1) * 100),
"severity": "critical" if z > 3.0 else "warning",
})
return sorted(outliers, key=lambda x: x["z_score"], reverse=True)
Graddage-normaliseret sammenligning
# Korriger for klima-variation inden sammenligning:
async def normalized_unit_consumption(
unit_id: uuid.UUID,
period_start: date,
period_end: date,
session: AsyncSession,
) -> dict:
"""
Beregn graddage-normaliseret forbrug for lejlighed.
Muliggør fair sammenligning år til år og på tværs af ejendomme.
"""
# Hent HCA-forbrug (rå):
hca_kwh = await get_unit_heat_consumption(unit_id, period_start, period_end, session)
# Hent graddage for perioden:
unit = await get_unit(session, unit_id)
degree_days = await get_degree_days(
unit.property.zip_code, period_start, period_end, session
)
# Normalår for regionen (langstidsgns.):
normal_year_hdd = await get_normal_hdd(unit.property.zip_code, session)
# Normaliseret forbrug:
if degree_days > 0:
normalized_kwh = hca_kwh * (normal_year_hdd / degree_days)
else:
normalized_kwh = hca_kwh
return {
"raw_kwh": hca_kwh,
"degree_days": degree_days,
"normal_year_hdd": normal_year_hdd,
"normalized_kwh": round(normalized_kwh, 1),
"kwh_per_m2": round(normalized_kwh / unit.area_m2, 1),
}
FastAPI outlier-endpoint
# server/src/analytics/router.py
@router.get("/properties/{property_id}/heat-outliers")
async def get_heat_outliers(
property_id: uuid.UUID,
period: str = Query("current", pattern="^(current|last_year)$"),
z_threshold: float = Query(2.0, ge=1.0, le=4.0),
session: AsyncSession = Depends(get_session),
current_user: User = Depends(require_role("landlord")),
) -> list[HeatOutlierResult]:
prop = await get_property_or_404(session, property_id, current_user.tenant_id)
if period == "current":
p_start, p_end = current_period(prop)
else:
p_start, p_end = last_year_period(prop)
# Hent forbrug pr. unit:
consumptions = await get_all_unit_consumptions(
session, property_id, p_start, p_end
)
outliers = detect_heat_outliers(consumptions, z_threshold)
# Berig med lejer-info:
results = []
for o in outliers:
unit = await get_unit_with_occupancy(session, uuid.UUID(o["unit_id"]))
results.append(HeatOutlierResult(
unit_number=unit.unit_number,
occupant_name=unit.current_occupancy.tenant_name if unit.current_occupancy else None,
occupant_email=unit.current_occupancy.tenant_email if unit.current_occupancy else None,
**o,
))
return results
Kommunikation til højtforbrugende lejer
Email-skabelon til lejer med Z-score > 2:
Emne: "Dit varmeforbrug — information fra din udlejer"
Kære [NAVN],
Vi har bemærket, at din lejligheds varmeforbrug er markant over
gennemsnittet for ejendommen i perioden [DATO] – [DATO]:
Dit forbrug: [KWH] kWh ([PCT]% over gennemsnit)
Ejendommens gns.: [GNS] kWh pr. lejlighed
Din estimerede andel: [BELOEB] kr. variabel del
Mulige årsager:
→ Termostater sat højere end nødvendigt (24°C+ giver højt forbrug)
→ Utætte vinduer/døre (trækker kold luft ind)
→ Møbler foran radiatorer (blokerer varmeudstråling)
→ Defekt termostatventil (sidder fast i åben position)
Anbefalinger:
→ Sænk termostaten til 20-22°C (1°C = 5-7% varmeforbrug)
→ Kontrollér tætningslister på vinduer og yderdøre
→ Kontakt os ved mistanke om defekt termostatventil (gratis udbedring)
Med venlig hilsen,
[UDLEJER]
Platform genererer og sender denne email automatisk ved Z > 2.
Årsager til højforbrug — fejlfindingsliste
Tekniske årsager (udlejers ansvar):
→ Defekt termostatventil (sidder fast åben) → 30-50% merforbrug
→ Utæt radiatorarmatur → Leaker varmt vand → Meget højt forbrug
→ Manglende radiatortermostat → Lejer kan ikke regulere
→ Defekt HCA-måler (fejlregistrerer lavt) → Naboen betaler mere
Adfærdsmæssige årsager (lejers ansvar):
→ Ventilation via åbne vinduer om vinteren (nordisk klima)
→ Termostat 26-28°C (2-3× normalt forbrug)
→ Tøjvask/tørring som fugtregulering via stærk opvarmning
Platform-detektion:
→ "Stuck" aflæsning: 5 identiske HCA-værdier i træk → defekt sensor
→ "Jump": HCA stiger >10× median over 1 uge → termostat-problem
→ "Backwards": HCA falder → måler-defekt eller udskiftet uden opdatering
Konklusion
Z-score outlier-detektion med tærskel 2.0 identificerer de øverste 2.5% forbrugere, som statistisk set har usædvanligt højt forbrug. Graddage-normalisering sikrer fair sammenligning år til år. Automatisk lejerrådgivning via email reducerer højforbrug og dermed alle lejeres faste-del-bidrag. Platform-alarmerne "stuck", "jump" og "backwards" identificerer tekniske årsager (defekte målere) separat fra adfærdsmæssige årsager.
Se aflæsnings-anomalier guide eller graddage normalisering guide.