M-Bus Gateway
← Tilbage til blog
· BEK 563· § 13· estimat· manglende aflæsning· graddage· historisk gennemsnit· wM-Bus· varmeregnskab· IoT

BEK 563 § 13 — estimering ved manglende aflæsninger

BEK 563 § 13 om estimeret forbrug: hvornår må udlejer estimere, 3 estimeringsmetoder (historisk gennemsnit, graddage-normaliseret, nabointerpolering), platform-automatik og revisordokumentation.

Af M-Bus Gateway

Afregning skal udsendes selv når en måler er defekt eller gateway-signal mangler. BEK 563 § 13 angiver rammerne — og platform-automatik sikrer korrekt dokumentation.


§ 13 — lovgrundlaget

BEK 563 § 13 — Manglende aflæsninger:

"Kan forbrugsmåling ikke finde sted, fordi måleren er defekt,
bortkommen eller utilgængelig, kan udlejeren anslå forbruget
for den pågældende boligenhed på grundlag af:

1. Det tidligere forbrug i boligenheden
2. Forbruget i sammenlignelige boligenheder
3. En skønsmæssig vurdering"

Nøgleprincipper:
  → Estimat er LOVPLIGTIGT — afregning kan ikke udskydes pga. manglende data
  → 4-måneders-fristen (§ 9) gælder også ved estimat
  → Lejer skal orienteres om at aflæsning er estimeret (ikke faktisk)
  → Estimat-metode skal dokumenteres i regnskabet
  → Lejer kan bestride estimat (§ 18 indsigelsesret)

Hvornår bruges § 13:
  → Måler defekt eller stjålet
  → wM-Bus signal utilstrækkeligt (RSSI < -110 dBm)
  → Gateway offline i del af perioden
  → Måler-udskiftning uden aflæsning af gammel måler
  → HCA pludselig 0 ved ellers aktiv radiator

Metode 1 — Historisk gennemsnit

# server/src/distribution/estimation.py
from decimal import Decimal
from datetime import date, timedelta
from sqlalchemy import text
from sqlalchemy.ext.asyncio import AsyncSession


async def estimate_by_historical_average(
    meter_installation_id: str,
    period_start: date,
    period_end: date,
    db: AsyncSession,
) -> Decimal:
    """
    § 13 stk. 1: Estimér baseret på forrige periodes faktiske forbrug.
    Normaliserer pr. dag for at håndtere perioder af forskellig længde.
    """
    period_days = (period_end - period_start).days

    # Hent forbrug fra de seneste 3 regnskabsperioder
    result = await db.execute(
        text("""
            SELECT
                SUM(r.value_kwh) AS total_kwh,
                COUNT(*) AS reading_count,
                MIN(r.timestamp) AS first_ts,
                MAX(r.timestamp) AS last_ts
            FROM readings r
            WHERE r.meter_installation_id = :mi_id
                AND r.timestamp >= :lookback_start
                AND r.timestamp < :period_start
        """),
        {
            "mi_id": meter_installation_id,
            "lookback_start": period_start - timedelta(days=3 * 365),
            "period_start": period_start,
        },
    )
    row = result.fetchone()

    if not row or not row.total_kwh or row.reading_count < 10:
        return None    # Ikke nok historik — brug nabointerpolering

    # Historisk dagsforbrug × antal dage i estimeringsperiode
    historical_days = (row.last_ts.date() - row.first_ts.date()).days or 1
    daily_avg = Decimal(str(row.total_kwh)) / Decimal(historical_days)
    estimate = daily_avg * Decimal(period_days)

    return estimate.quantize(Decimal("0.1"))

Metode 2 — Graddage-normaliseret estimat

# server/src/distribution/estimation.py

async def estimate_by_degree_days(
    meter_installation_id: str,
    period_start: date,
    period_end: date,
    zip_code: str,
    db: AsyncSession,
) -> Decimal | None:
    """
    § 13 stk. 1 + DMI-graddage: Temperaturnormaliseret historisk forbrug.
    Mere præcist end simpelt gennemsnit ved milde/kolde perioder.
    """
    # Hent graddage for estimeringsperiode og historisk periode
    result = await db.execute(
        text("""
            WITH current_dd AS (
                SELECT COALESCE(SUM(heating_degree_days), 0) AS hdd
                FROM degree_days
                WHERE zip_code = :zip_code
                    AND date BETWEEN :period_start AND :period_end
            ),
            historical_dd AS (
                SELECT COALESCE(SUM(heating_degree_days), 1) AS hdd
                FROM degree_days
                WHERE zip_code = :zip_code
                    AND date BETWEEN :hist_start AND :period_start
            ),
            historical_kwh AS (
                SELECT COALESCE(SUM(value_kwh), 0) AS kwh
                FROM readings
                WHERE meter_installation_id = :mi_id
                    AND timestamp BETWEEN :hist_start AND :period_start
            )
            SELECT
                current_dd.hdd AS current_hdd,
                historical_dd.hdd AS hist_hdd,
                historical_kwh.kwh AS hist_kwh
            FROM current_dd, historical_dd, historical_kwh
        """),
        {
            "zip_code": zip_code,
            "period_start": period_start,
            "period_end": period_end,
            "hist_start": period_start - timedelta(days=3 * 365),
            "mi_id": meter_installation_id,
        },
    )
    row = result.fetchone()

    if not row or row.hist_hdd == 0 or row.hist_kwh == 0:
        return None

    # Estimer: historisk forbrug × (nuværende HDD / historisk HDD)
    correction = Decimal(str(row.current_hdd)) / Decimal(str(row.hist_hdd))
    estimate = Decimal(str(row.hist_kwh)) * correction

    return estimate.quantize(Decimal("0.1"))

Metode 3 — Nabointerpolering

# server/src/distribution/estimation.py

async def estimate_by_neighbor_interpolation(
    unit_id: str,
    property_id: str,
    period_start: date,
    period_end: date,
    floor_area_m2: Decimal,
    db: AsyncSession,
) -> Decimal | None:
    """
    § 13 stk. 2: Sammenlignelige boligenheder i samme ejendom.
    Bruges når historik mangler (ny installation, ny lejer).
    Normaliserer pr. m² → estimér for defekt enhed.
    """
    result = await db.execute(
        text("""
            SELECT
                AVG(r.value_kwh / u.floor_area_m2) AS kwh_per_m2
            FROM readings r
            JOIN meter_installations mi ON mi.id = r.meter_installation_id
            JOIN units u ON u.id = mi.unit_id
            WHERE u.property_id = :property_id
                AND mi.unit_id != :unit_id              -- Ekskludér defekt enhed
                AND mi.removed_at IS NULL
                AND u.deleted_at IS NULL
                AND r.timestamp BETWEEN :period_start AND :period_end
                AND u.floor_area_m2 > 0
        """),
        {
            "property_id": property_id,
            "unit_id": unit_id,
            "period_start": period_start,
            "period_end": period_end,
        },
    )
    row = result.fetchone()

    if not row or not row.kwh_per_m2:
        return None

    estimate = Decimal(str(row.kwh_per_m2)) * floor_area_m2
    return estimate.quantize(Decimal("0.1"))

Platform-automatik og dokumentation

# server/src/distribution/estimation.py

async def auto_estimate(
    meter_installation_id: str,
    unit_id: str,
    property_id: str,
    zip_code: str,
    floor_area_m2: Decimal,
    period_start: date,
    period_end: date,
    db: AsyncSession,
) -> dict:
    """
    Prøv estimeringsmetoder i prioriteret rækkefølge:
    1. Graddage-normaliseret (mest præcis)
    2. Historisk gennemsnit (fallback)
    3. Nabointerpolering (ved manglende historik)
    Returnér estimat + metode-dokumentation til BEK 563 § 13 audit.
    """
    # Metode 1: Graddage
    estimate = await estimate_by_degree_days(
        meter_installation_id, period_start, period_end, zip_code, db
    )
    if estimate:
        return {"value_kwh": estimate, "method": "degree_day_normalized", "confidence": "high"}

    # Metode 2: Historisk gennemsnit
    estimate = await estimate_by_historical_average(
        meter_installation_id, period_start, period_end, db
    )
    if estimate:
        return {"value_kwh": estimate, "method": "historical_average", "confidence": "medium"}

    # Metode 3: Nabointerpolering
    estimate = await estimate_by_neighbor_interpolation(
        unit_id, property_id, period_start, period_end, floor_area_m2, db
    )
    if estimate:
        return {"value_kwh": estimate, "method": "neighbor_interpolation", "confidence": "low"}

    # Ingen data → manuel vurdering krævet
    return {"value_kwh": Decimal("0"), "method": "manual_required", "confidence": "none"}

Revisor-dokumentation i PDF

BEK 563 § 13 dokumentation i årsafregning:

Obligatorisk indhold ved estimat:
  → "Forbrug estimeret — måler defekt/signal manglende"
  → Estimeringsmetode: "Graddage-normaliseret historisk gennemsnit"
  → Estimeringsgrundlag: "Aflæsninger 2023-06-01 til 2025-05-31"
  → Estimeret forbrug: 1.234 kWh (± 15%)
  → Faktisk forbrug (øvrige enheder): Angivet separat

Platform PDF-noter pr. lejlighed:
  Unit.status = "estimated"
  DistributionResult.estimation_method = "degree_day_normalized"
  DistributionResult.estimation_confidence = "high"
  PDF: Notationseksempel i bilag A

Lejerrettigheder ved estimat:
  → § 18: 6 ugers indsigelsesret (som normalt)
  → Lejer kan kræve faktisk aflæsning hvis måler nu virker
  → Ved repareret måler: Korrektionsafregning mulig (aftale)
  → Huslejenævn: Kan kræve gentagen målbar data

Konklusion

BEK 563 § 13 giver tre lovlige estimeringsmetoder ved manglende aflæsning: historisk gennemsnit, graddage-normaliseret historik og nabointerpolering. Platformen prøver dem i prioriteret rækkefølge og dokumenterer metode + konfidensgrad i regnskabet. 4-måneders-fristen gælder fuldt ud — estimat er ikke undskyldning for forsinket afregning. Lejer skal orienteres om estimatet og har normal 6-ugers indsigelsesret. PDF-dokumentation med estimeringsmetode og grundlag er det stærkeste forsvar ved huslejenævnssag.

Se wM-Bus signal troubleshooting guide eller pro-rata guide.