fix(energy): report real kWh in the cost Summary instead of mislabelled money
The Summary cards labelled `metered_import` / `metered_export` as "(kWh)", but both fields are monetary totals (Σ import_cost / Σ export_revenue). Today's page therefore showed "Import 1.339 kWh" when the meter had actually imported 4.188 kWh — the 1.339 was EUR. Cross-checked against the DSMR cumulative registers and Home Assistant: our energy figures were correct all along, only the label was wrong. summarize() now also aggregates the metered energy, reusing the already-fetched non-degraded rows so no extra query is issued: metered_import_kwh = Σ (d1_kwh + d2_kwh) metered_export_kwh = Σ (r1_kwh + r2_kwh) The Import/Export cards show kWh as the headline figure and keep the monetary equivalent as a sub-line, so the split between energy cost and standing charges/credits behind total_payable stays visible. The `_kwh` suffix is now the only thing separating energy from money in this payload, so the docstrings on both summarize() and SummaryResponse call that out explicitly. app/integrations/expose.py reads only the money keys, so the HA outbound sensors are unaffected by the additive fields.
This commit is contained in:
+18
-4
@@ -121,15 +121,29 @@ class CostsResponse(BaseModel):
|
|||||||
class SummaryResponse(BaseModel):
|
class SummaryResponse(BaseModel):
|
||||||
"""Response for GET /api/energy/costs/summary.
|
"""Response for GET /api/energy/costs/summary.
|
||||||
|
|
||||||
All monetary values are in ``currency``.
|
Monetary values are in ``currency``; the ``*_kwh`` fields are energy totals
|
||||||
|
in kWh. ``metered_import``/``metered_export`` are **money**, not energy —
|
||||||
|
only the ``_kwh``-suffixed fields carry kWh.
|
||||||
|
|
||||||
``total_payable = metered_net + fixed_costs − credits``
|
``total_payable = metered_net + fixed_costs − credits``
|
||||||
"""
|
"""
|
||||||
|
|
||||||
currency: str
|
currency: str
|
||||||
metered_import: float = Field(description="Σ import_cost for non-degraded periods.")
|
metered_import: float = Field(
|
||||||
metered_export: float = Field(description="Σ export_revenue for non-degraded periods.")
|
description="Σ import_cost for non-degraded periods (money, in `currency`)."
|
||||||
metered_net: float = Field(description="Σ net_cost for non-degraded periods.")
|
)
|
||||||
|
metered_export: float = Field(
|
||||||
|
description="Σ export_revenue for non-degraded periods (money, in `currency`)."
|
||||||
|
)
|
||||||
|
metered_net: float = Field(
|
||||||
|
description="Σ net_cost for non-degraded periods (money, in `currency`)."
|
||||||
|
)
|
||||||
|
metered_import_kwh: float = Field(
|
||||||
|
description="Σ (d1_kwh + d2_kwh) for non-degraded periods (energy imported, kWh)."
|
||||||
|
)
|
||||||
|
metered_export_kwh: float = Field(
|
||||||
|
description="Σ (r1_kwh + r2_kwh) for non-degraded periods (energy exported, kWh)."
|
||||||
|
)
|
||||||
fixed_costs: float = Field(
|
fixed_costs: float = Field(
|
||||||
description="Standing charges (network_fee + management_fee) apportioned over the interval."
|
description="Standing charges (network_fee + management_fee) apportioned over the interval."
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -18,6 +18,12 @@ M6 design document, extended in M7-T03 to be meter-aware:
|
|||||||
÷ 30 per day) and subtracts the energy-tax credit (heffingskorting,
|
÷ 30 per day) and subtracts the energy-tax credit (heffingskorting,
|
||||||
apportioned at EUR/year ÷ 365 per day).
|
apportioned at EUR/year ÷ 365 per day).
|
||||||
|
|
||||||
|
The summary reports **both** money and energy: ``metered_import`` /
|
||||||
|
``metered_export`` are monetary totals (Σ import_cost / Σ export_revenue),
|
||||||
|
while ``metered_import_kwh`` / ``metered_export_kwh`` are the corresponding
|
||||||
|
metered energy totals in kWh. The ``_kwh`` suffix is the only thing that
|
||||||
|
distinguishes them — always check it before labelling a value in a UI.
|
||||||
|
|
||||||
Design notes
|
Design notes
|
||||||
------------
|
------------
|
||||||
- **Decimal arithmetic throughout**: all monetary computations use
|
- **Decimal arithmetic throughout**: all monetary computations use
|
||||||
@@ -732,9 +738,11 @@ def summarize(session: Session, start: datetime, end: datetime) -> dict[str, Any
|
|||||||
dict with keys:
|
dict with keys:
|
||||||
|
|
||||||
currency str ISO 4217 currency (from contract, or "EUR" fallback)
|
currency str ISO 4217 currency (from contract, or "EUR" fallback)
|
||||||
metered_import float Σ import_cost from non-degraded periods
|
metered_import float Σ import_cost from non-degraded periods (money)
|
||||||
metered_export float Σ export_revenue from non-degraded periods
|
metered_export float Σ export_revenue from non-degraded periods (money)
|
||||||
metered_net float Σ net_cost from non-degraded periods
|
metered_net float Σ net_cost from non-degraded periods (money)
|
||||||
|
metered_import_kwh float Σ (d1_kwh + d2_kwh) from non-degraded periods (energy)
|
||||||
|
metered_export_kwh float Σ (r1_kwh + r2_kwh) from non-degraded periods (energy)
|
||||||
fixed_costs float standing charges for elapsed whole local days
|
fixed_costs float standing charges for elapsed whole local days
|
||||||
credits float energy-tax credit for elapsed whole local days
|
credits float energy-tax credit for elapsed whole local days
|
||||||
total_payable float metered_net + fixed_costs − credits
|
total_payable float metered_net + fixed_costs − credits
|
||||||
@@ -763,6 +771,15 @@ def summarize(session: Session, start: datetime, end: datetime) -> dict[str, Any
|
|||||||
sum_export = sum((_to_decimal(r.export_revenue) for r in good_rows), Decimal("0"))
|
sum_export = sum((_to_decimal(r.export_revenue) for r in good_rows), Decimal("0"))
|
||||||
sum_net = sum((_to_decimal(r.net_cost) for r in good_rows), Decimal("0"))
|
sum_net = sum((_to_decimal(r.net_cost) for r in good_rows), Decimal("0"))
|
||||||
|
|
||||||
|
# Σ metered energy (kWh), summed across both tariff registers. Reuses the
|
||||||
|
# already-fetched ``good_rows`` so no extra query is issued.
|
||||||
|
sum_import_kwh = sum(
|
||||||
|
(_to_decimal(r.d1_kwh) + _to_decimal(r.d2_kwh) for r in good_rows), Decimal("0")
|
||||||
|
)
|
||||||
|
sum_export_kwh = sum(
|
||||||
|
(_to_decimal(r.r1_kwh) + _to_decimal(r.r2_kwh) for r in good_rows), Decimal("0")
|
||||||
|
)
|
||||||
|
|
||||||
# --- Interval length in days (window, not elapsed — kept for API compat) ---
|
# --- Interval length in days (window, not elapsed — kept for API compat) ---
|
||||||
total_seconds = (end_utc - start_utc).total_seconds()
|
total_seconds = (end_utc - start_utc).total_seconds()
|
||||||
days = _to_decimal(str(total_seconds)) / _to_decimal("86400")
|
days = _to_decimal(str(total_seconds)) / _to_decimal("86400")
|
||||||
@@ -885,6 +902,8 @@ def summarize(session: Session, start: datetime, end: datetime) -> dict[str, Any
|
|||||||
"metered_import": float(sum_import),
|
"metered_import": float(sum_import),
|
||||||
"metered_export": float(sum_export),
|
"metered_export": float(sum_export),
|
||||||
"metered_net": float(sum_net),
|
"metered_net": float(sum_net),
|
||||||
|
"metered_import_kwh": float(sum_import_kwh),
|
||||||
|
"metered_export_kwh": float(sum_export_kwh),
|
||||||
"fixed_costs": float(fixed_dec),
|
"fixed_costs": float(fixed_dec),
|
||||||
"credits": float(credits_dec),
|
"credits": float(credits_dec),
|
||||||
"total_payable": float(total_payable),
|
"total_payable": float(total_payable),
|
||||||
|
|||||||
Vendored
+16
-4
@@ -2220,7 +2220,9 @@ export interface components {
|
|||||||
* SummaryResponse
|
* SummaryResponse
|
||||||
* @description Response for GET /api/energy/costs/summary.
|
* @description Response for GET /api/energy/costs/summary.
|
||||||
*
|
*
|
||||||
* All monetary values are in ``currency``.
|
* Monetary values are in ``currency``; the ``*_kwh`` fields are energy totals
|
||||||
|
* in kWh. ``metered_import``/``metered_export`` are **money**, not energy —
|
||||||
|
* only the ``_kwh``-suffixed fields carry kWh.
|
||||||
*
|
*
|
||||||
* ``total_payable = metered_net + fixed_costs − credits``
|
* ``total_payable = metered_net + fixed_costs − credits``
|
||||||
*/
|
*/
|
||||||
@@ -2229,19 +2231,29 @@ export interface components {
|
|||||||
currency: string;
|
currency: string;
|
||||||
/**
|
/**
|
||||||
* Metered Import
|
* Metered Import
|
||||||
* @description Σ import_cost for non-degraded periods.
|
* @description Σ import_cost for non-degraded periods (money, in `currency`).
|
||||||
*/
|
*/
|
||||||
metered_import: number;
|
metered_import: number;
|
||||||
/**
|
/**
|
||||||
* Metered Export
|
* Metered Export
|
||||||
* @description Σ export_revenue for non-degraded periods.
|
* @description Σ export_revenue for non-degraded periods (money, in `currency`).
|
||||||
*/
|
*/
|
||||||
metered_export: number;
|
metered_export: number;
|
||||||
/**
|
/**
|
||||||
* Metered Net
|
* Metered Net
|
||||||
* @description Σ net_cost for non-degraded periods.
|
* @description Σ net_cost for non-degraded periods (money, in `currency`).
|
||||||
*/
|
*/
|
||||||
metered_net: number;
|
metered_net: number;
|
||||||
|
/**
|
||||||
|
* Metered Import Kwh
|
||||||
|
* @description Σ (d1_kwh + d2_kwh) for non-degraded periods (energy imported, kWh).
|
||||||
|
*/
|
||||||
|
metered_import_kwh: number;
|
||||||
|
/**
|
||||||
|
* Metered Export Kwh
|
||||||
|
* @description Σ (r1_kwh + r2_kwh) for non-degraded periods (energy exported, kWh).
|
||||||
|
*/
|
||||||
|
metered_export_kwh: number;
|
||||||
/**
|
/**
|
||||||
* Fixed Costs
|
* Fixed Costs
|
||||||
* @description Standing charges (network_fee + management_fee) apportioned over the interval.
|
* @description Standing charges (network_fee + management_fee) apportioned over the interval.
|
||||||
|
|||||||
@@ -61,9 +61,13 @@ const COST_PERIOD = {
|
|||||||
|
|
||||||
const SUMMARY = {
|
const SUMMARY = {
|
||||||
currency: 'EUR',
|
currency: 'EUR',
|
||||||
|
// Money totals and kWh totals are deliberately distinct so the assertions
|
||||||
|
// below prove the cards read the *_kwh fields, not the monetary ones.
|
||||||
metered_import: 10.5,
|
metered_import: 10.5,
|
||||||
metered_export: 2.3,
|
metered_export: 2.3,
|
||||||
metered_net: 8.2,
|
metered_net: 8.2,
|
||||||
|
metered_import_kwh: 33.3,
|
||||||
|
metered_export_kwh: 44.4,
|
||||||
fixed_costs: 5.0,
|
fixed_costs: 5.0,
|
||||||
credits: 50.0,
|
credits: 50.0,
|
||||||
total_payable: 12.5,
|
total_payable: 12.5,
|
||||||
@@ -145,9 +149,14 @@ describe('CostView', () => {
|
|||||||
expect(screen.getByTestId('summary-import')).toBeInTheDocument()
|
expect(screen.getByTestId('summary-import')).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(screen.getByTestId('summary-import')).toHaveTextContent('10.500')
|
// Main figure is energy (kWh), taken from the *_kwh fields.
|
||||||
expect(screen.getByTestId('summary-export')).toHaveTextContent('2.300')
|
expect(screen.getByTestId('summary-import')).toHaveTextContent('33.300')
|
||||||
|
expect(screen.getByTestId('summary-export')).toHaveTextContent('44.400')
|
||||||
expect(screen.getByTestId('summary-total')).toHaveTextContent('12.50')
|
expect(screen.getByTestId('summary-total')).toHaveTextContent('12.50')
|
||||||
|
|
||||||
|
// Sub-line carries the monetary equivalent, so money is still visible.
|
||||||
|
expect(screen.getByTestId('summary-import-sub')).toHaveTextContent('10.50 EUR')
|
||||||
|
expect(screen.getByTestId('summary-export-sub')).toHaveTextContent('2.30 EUR')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('shows recompute confirmation modal on button click', async () => {
|
it('shows recompute confirmation modal on button click', async () => {
|
||||||
|
|||||||
@@ -77,10 +77,12 @@ function getThisMonthRange(): { start: string; end: string } {
|
|||||||
interface SummaryCardProps {
|
interface SummaryCardProps {
|
||||||
label: string
|
label: string
|
||||||
value: string
|
value: string
|
||||||
|
/** Optional secondary line, e.g. the monetary equivalent of an energy figure. */
|
||||||
|
sub?: string
|
||||||
testId?: string
|
testId?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
function SummaryCard({ label, value, testId }: SummaryCardProps) {
|
function SummaryCard({ label, value, sub, testId }: SummaryCardProps) {
|
||||||
return (
|
return (
|
||||||
<Paper withBorder p="sm" data-testid={testId}>
|
<Paper withBorder p="sm" data-testid={testId}>
|
||||||
<Stack gap={4}>
|
<Stack gap={4}>
|
||||||
@@ -90,6 +92,11 @@ function SummaryCard({ label, value, testId }: SummaryCardProps) {
|
|||||||
<Text fw={600} size="lg">
|
<Text fw={600} size="lg">
|
||||||
{value}
|
{value}
|
||||||
</Text>
|
</Text>
|
||||||
|
{sub !== undefined && (
|
||||||
|
<Text size="xs" c="dimmed" data-testid={testId ? `${testId}-sub` : undefined}>
|
||||||
|
{sub}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
)
|
)
|
||||||
@@ -207,12 +214,14 @@ export function CostView() {
|
|||||||
<SimpleGrid cols={{ base: 2, sm: 3 }} spacing="sm" data-testid="cost-summary">
|
<SimpleGrid cols={{ base: 2, sm: 3 }} spacing="sm" data-testid="cost-summary">
|
||||||
<SummaryCard
|
<SummaryCard
|
||||||
label="Import (kWh)"
|
label="Import (kWh)"
|
||||||
value={summaryQuery.data.metered_import.toFixed(3)}
|
value={summaryQuery.data.metered_import_kwh.toFixed(3)}
|
||||||
|
sub={`${summaryQuery.data.metered_import.toFixed(2)} ${currency}`}
|
||||||
testId="summary-import"
|
testId="summary-import"
|
||||||
/>
|
/>
|
||||||
<SummaryCard
|
<SummaryCard
|
||||||
label="Export (kWh)"
|
label="Export (kWh)"
|
||||||
value={summaryQuery.data.metered_export.toFixed(3)}
|
value={summaryQuery.data.metered_export_kwh.toFixed(3)}
|
||||||
|
sub={`${summaryQuery.data.metered_export.toFixed(2)} ${currency}`}
|
||||||
testId="summary-export"
|
testId="summary-export"
|
||||||
/>
|
/>
|
||||||
<SummaryCard
|
<SummaryCard
|
||||||
|
|||||||
@@ -83,6 +83,8 @@ const SUMMARY = {
|
|||||||
metered_import: 10.5,
|
metered_import: 10.5,
|
||||||
metered_export: 2.3,
|
metered_export: 2.3,
|
||||||
metered_net: 8.2,
|
metered_net: 8.2,
|
||||||
|
metered_import_kwh: 33.3,
|
||||||
|
metered_export_kwh: 44.4,
|
||||||
fixed_costs: 5.0,
|
fixed_costs: 5.0,
|
||||||
credits: 50.0,
|
credits: 50.0,
|
||||||
total_payable: 12.5,
|
total_payable: 12.5,
|
||||||
|
|||||||
+16
-4
@@ -4727,17 +4727,27 @@
|
|||||||
"metered_import": {
|
"metered_import": {
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"title": "Metered Import",
|
"title": "Metered Import",
|
||||||
"description": "Σ import_cost for non-degraded periods."
|
"description": "Σ import_cost for non-degraded periods (money, in `currency`)."
|
||||||
},
|
},
|
||||||
"metered_export": {
|
"metered_export": {
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"title": "Metered Export",
|
"title": "Metered Export",
|
||||||
"description": "Σ export_revenue for non-degraded periods."
|
"description": "Σ export_revenue for non-degraded periods (money, in `currency`)."
|
||||||
},
|
},
|
||||||
"metered_net": {
|
"metered_net": {
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"title": "Metered Net",
|
"title": "Metered Net",
|
||||||
"description": "Σ net_cost for non-degraded periods."
|
"description": "Σ net_cost for non-degraded periods (money, in `currency`)."
|
||||||
|
},
|
||||||
|
"metered_import_kwh": {
|
||||||
|
"type": "number",
|
||||||
|
"title": "Metered Import Kwh",
|
||||||
|
"description": "Σ (d1_kwh + d2_kwh) for non-degraded periods (energy imported, kWh)."
|
||||||
|
},
|
||||||
|
"metered_export_kwh": {
|
||||||
|
"type": "number",
|
||||||
|
"title": "Metered Export Kwh",
|
||||||
|
"description": "Σ (r1_kwh + r2_kwh) for non-degraded periods (energy exported, kWh)."
|
||||||
},
|
},
|
||||||
"fixed_costs": {
|
"fixed_costs": {
|
||||||
"type": "number",
|
"type": "number",
|
||||||
@@ -4776,6 +4786,8 @@
|
|||||||
"metered_import",
|
"metered_import",
|
||||||
"metered_export",
|
"metered_export",
|
||||||
"metered_net",
|
"metered_net",
|
||||||
|
"metered_import_kwh",
|
||||||
|
"metered_export_kwh",
|
||||||
"fixed_costs",
|
"fixed_costs",
|
||||||
"credits",
|
"credits",
|
||||||
"total_payable",
|
"total_payable",
|
||||||
@@ -4784,7 +4796,7 @@
|
|||||||
"days"
|
"days"
|
||||||
],
|
],
|
||||||
"title": "SummaryResponse",
|
"title": "SummaryResponse",
|
||||||
"description": "Response for GET /api/energy/costs/summary.\n\nAll monetary values are in ``currency``.\n\n``total_payable = metered_net + fixed_costs − credits``"
|
"description": "Response for GET /api/energy/costs/summary.\n\nMonetary values are in ``currency``; the ``*_kwh`` fields are energy totals\nin kWh. ``metered_import``/``metered_export`` are **money**, not energy —\nonly the ``_kwh``-suffixed fields carry kWh.\n\n``total_payable = metered_net + fixed_costs − credits``"
|
||||||
},
|
},
|
||||||
"TibberTestPriceSchema": {
|
"TibberTestPriceSchema": {
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
+20
-4
@@ -3659,15 +3659,25 @@ components:
|
|||||||
metered_import:
|
metered_import:
|
||||||
type: number
|
type: number
|
||||||
title: Metered Import
|
title: Metered Import
|
||||||
description: Σ import_cost for non-degraded periods.
|
description: Σ import_cost for non-degraded periods (money, in `currency`).
|
||||||
metered_export:
|
metered_export:
|
||||||
type: number
|
type: number
|
||||||
title: Metered Export
|
title: Metered Export
|
||||||
description: Σ export_revenue for non-degraded periods.
|
description: Σ export_revenue for non-degraded periods (money, in `currency`).
|
||||||
metered_net:
|
metered_net:
|
||||||
type: number
|
type: number
|
||||||
title: Metered Net
|
title: Metered Net
|
||||||
description: Σ net_cost for non-degraded periods.
|
description: Σ net_cost for non-degraded periods (money, in `currency`).
|
||||||
|
metered_import_kwh:
|
||||||
|
type: number
|
||||||
|
title: Metered Import Kwh
|
||||||
|
description: Σ (d1_kwh + d2_kwh) for non-degraded periods (energy imported,
|
||||||
|
kWh).
|
||||||
|
metered_export_kwh:
|
||||||
|
type: number
|
||||||
|
title: Metered Export Kwh
|
||||||
|
description: Σ (r1_kwh + r2_kwh) for non-degraded periods (energy exported,
|
||||||
|
kWh).
|
||||||
fixed_costs:
|
fixed_costs:
|
||||||
type: number
|
type: number
|
||||||
title: Fixed Costs
|
title: Fixed Costs
|
||||||
@@ -3699,6 +3709,8 @@ components:
|
|||||||
- metered_import
|
- metered_import
|
||||||
- metered_export
|
- metered_export
|
||||||
- metered_net
|
- metered_net
|
||||||
|
- metered_import_kwh
|
||||||
|
- metered_export_kwh
|
||||||
- fixed_costs
|
- fixed_costs
|
||||||
- credits
|
- credits
|
||||||
- total_payable
|
- total_payable
|
||||||
@@ -3709,7 +3721,11 @@ components:
|
|||||||
description: 'Response for GET /api/energy/costs/summary.
|
description: 'Response for GET /api/energy/costs/summary.
|
||||||
|
|
||||||
|
|
||||||
All monetary values are in ``currency``.
|
Monetary values are in ``currency``; the ``*_kwh`` fields are energy totals
|
||||||
|
|
||||||
|
in kWh. ``metered_import``/``metered_export`` are **money**, not energy —
|
||||||
|
|
||||||
|
only the ``_kwh``-suffixed fields carry kWh.
|
||||||
|
|
||||||
|
|
||||||
``total_payable = metered_net + fixed_costs − credits``'
|
``total_payable = metered_net + fixed_costs − credits``'
|
||||||
|
|||||||
@@ -990,6 +990,38 @@ class TestSummarize:
|
|||||||
# Σnet ≈ 2 × 0.4051 = 0.8102
|
# Σnet ≈ 2 × 0.4051 = 0.8102
|
||||||
assert abs(result["metered_net"] - 0.8102) < 1e-6
|
assert abs(result["metered_net"] - 0.8102) < 1e-6
|
||||||
|
|
||||||
|
def test_metered_kwh_sums(self, energy_db: Session) -> None:
|
||||||
|
"""The *_kwh totals sum both tariff registers and are distinct from the money totals."""
|
||||||
|
self._setup_two_periods(energy_db)
|
||||||
|
result = summarize(energy_db, _ts(10, 0), _ts(10, 30))
|
||||||
|
|
||||||
|
# Per period: d1=0.5, d2=1.2 → import 1.7 kWh; r1=0.0, r2=0.1 → export 0.1 kWh.
|
||||||
|
assert abs(result["metered_import_kwh"] - 3.4) < 1e-6, (
|
||||||
|
f"expected Σ(d1+d2) = 2 × 1.7 = 3.4 kWh, got {result['metered_import_kwh']}"
|
||||||
|
)
|
||||||
|
assert abs(result["metered_export_kwh"] - 0.2) < 1e-6, (
|
||||||
|
f"expected Σ(r1+r2) = 2 × 0.1 = 0.2 kWh, got {result['metered_export_kwh']}"
|
||||||
|
)
|
||||||
|
# Regression guard for the mislabelled-unit bug: energy and money totals
|
||||||
|
# must never be conflated (import 3.4 kWh vs 0.8202 EUR of import cost).
|
||||||
|
assert result["metered_import_kwh"] != result["metered_import"]
|
||||||
|
assert result["metered_export_kwh"] != result["metered_export"]
|
||||||
|
|
||||||
|
def test_metered_kwh_excludes_degraded_periods(self, energy_db: Session) -> None:
|
||||||
|
"""Degraded periods contribute no kWh, mirroring the money totals."""
|
||||||
|
self._setup_two_periods(energy_db)
|
||||||
|
# Degrade the first period; its kWh must drop out of the totals.
|
||||||
|
row = energy_db.execute(
|
||||||
|
select(EnergyCostPeriod).where(EnergyCostPeriod.period_start == _T0)
|
||||||
|
).scalar_one()
|
||||||
|
row.degraded = True
|
||||||
|
energy_db.commit()
|
||||||
|
|
||||||
|
result = summarize(energy_db, _ts(10, 0), _ts(10, 30))
|
||||||
|
assert result["degraded_count"] == 1
|
||||||
|
assert abs(result["metered_import_kwh"] - 1.7) < 1e-6
|
||||||
|
assert abs(result["metered_export_kwh"] - 0.1) < 1e-6
|
||||||
|
|
||||||
def test_period_count(self, energy_db: Session) -> None:
|
def test_period_count(self, energy_db: Session) -> None:
|
||||||
self._setup_two_periods(energy_db)
|
self._setup_two_periods(energy_db)
|
||||||
result = summarize(energy_db, _ts(10, 0), _ts(10, 30))
|
result = summarize(energy_db, _ts(10, 0), _ts(10, 30))
|
||||||
@@ -2708,6 +2740,7 @@ class TestSummarizeSettlementOffset:
|
|||||||
|
|
||||||
expected_keys = {
|
expected_keys = {
|
||||||
"currency", "metered_import", "metered_export", "metered_net",
|
"currency", "metered_import", "metered_export", "metered_net",
|
||||||
|
"metered_import_kwh", "metered_export_kwh",
|
||||||
"fixed_costs", "credits", "total_payable", "period_count",
|
"fixed_costs", "credits", "total_payable", "period_count",
|
||||||
"degraded_count", "days",
|
"degraded_count", "days",
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user