M-Bus Gateway
← Tilbage til blog
· varmeregnskab· outlier-detektion· energianalyse· HCA· varmeforbrug· lejerbetjening· energibesparelse

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.