M7-T05: add meter CRUD API + retroactive recompute + OpenAPI regen

This commit is contained in:
2026-06-25 17:01:54 +02:00
parent 82bb329500
commit 839faaf95b
6 changed files with 1746 additions and 0 deletions
+308
View File
@@ -1077,6 +1077,138 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
/api/energy/meters:
get:
tags:
- api-energy-meters
summary: List Energy Meters
description: 'List all meter epochs in ascending ``started_at`` order.
Returns the full historical sequence of meter installations across all
commodities. The active meter (``ended_at=null``) appears last because it
has the latest ``started_at``.'
operationId: list_energy_meters_api_energy_meters_get
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/MeterListResponse'
post:
tags:
- api-energy-meters
summary: Declare Energy Meter
description: 'Declare a new meter epoch (swap, home move, or initial declaration).
Closes the current active meter for the given commodity at ``started_at``
and opens a new active meter. If no active meter exists, the new meter is
simply created without closing anything.
**Validation**: ``started_at`` must be **≥** the current active meter''s
own ``started_at`` (no chronological backdate below the active epoch''s
start). Equal timestamps are allowed (replaces the current meter at the
same logical moment). Violation → 422.
**Retroactive recompute**: if ``started_at`` is in the past, billing
records from that point forward are re-judged via ``recompute_range`` to
reflect the new meter attribution. The response includes the count of
recomputed periods in ``recomputed_periods`` (not part of ``MeterResponse``
— the recompute is transparent; callers should re-fetch costs if needed).'
operationId: declare_energy_meter_api_energy_meters_post
parameters:
- name: X-CSRF-Token
in: header
required: false
schema:
anyOf:
- type: string
- type: 'null'
title: X-Csrf-Token
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/MeterDeclareRequest'
responses:
'201':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/MeterResponse'
'422':
description: Validation Error
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
/api/energy/meters/{meter_id}:
patch:
tags:
- api-energy-meters
summary: Patch Energy Meter
description: "Partially update a meter epoch: rename, edit note, or correct\
\ started_at.\n\n- ``label``: updates the human-readable label.\n- ``note``:\
\ updates the free-form note.\n- ``started_at``: **retroactive correction**\
\ — shifts this meter's start\n boundary. The service layer maintains timeline\
\ continuity by also\n updating the preceding meter's ``ended_at``. Validation:\n\
\ * Must be strictly after the previous meter's own ``started_at``.\n \
\ * Must be strictly before this meter's ``ended_at`` (if closed).\n Violation\
\ → 422.\n\n**Retroactive recompute when ``started_at`` changes**: billing\
\ records in\nthe window ``[min(old, new), now)`` are re-judged to reflect\
\ the corrected\nmeter attribution.\n\nNot found → 404."
operationId: patch_energy_meter_api_energy_meters__meter_id__patch
parameters:
- name: meter_id
in: path
required: true
schema:
type: integer
title: Meter Id
- name: X-CSRF-Token
in: header
required: false
schema:
anyOf:
- type: string
- type: 'null'
title: X-Csrf-Token
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/MeterPatchRequest'
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/MeterResponse'
'422':
description: Validation Error
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
/api/expose:
get:
tags:
@@ -2595,6 +2727,182 @@ components:
Prices are the *effective* buy prices as used by the billing engine
(energy_buy_x + energy_tax + ode) and the raw sell prices.'
MeterDeclareRequest:
properties:
label:
type: string
maxLength: 255
minLength: 1
title: Label
started_at:
type: string
format: date-time
title: Started At
description: UTC (or server-local naive) datetime from which this meter
epoch starts. May be in the past (retroactive declaration).
reason:
$ref: '#/components/schemas/MeterReason'
description: 'Why this epoch was created. One of: initial, meter_swap,
home_move, other.'
note:
anyOf:
- type: string
maxLength: 1024
- type: 'null'
title: Note
commodity:
type: string
maxLength: 32
minLength: 1
title: Commodity
description: Energy commodity this meter measures. Defaults to 'electricity'.
default: electricity
type: object
required:
- label
- started_at
- reason
title: MeterDeclareRequest
description: 'Request body for POST /api/energy/meters.
Declares a new meter epoch (swap, home move, or initial declaration). The
service layer closes the current active meter for the given commodity at
``started_at`` and opens a new one.
``started_at`` follows the Principle-A localisation convention: a
timezone-naive value is interpreted as the **server''s local wall-clock time**
(e.g. CEST midnight → stored as UTC the night before); a timezone-aware
value is converted to UTC as-is. Omitting ``started_at`` is not allowed —
every meter declaration must carry an explicit start timestamp.
``commodity`` defaults to ``"electricity"``; the field is available for
future use with ``gas`` or ``heating``.'
MeterListResponse:
properties:
items:
items:
$ref: '#/components/schemas/MeterResponse'
type: array
title: Items
total:
type: integer
title: Total
type: object
required:
- items
- total
title: MeterListResponse
description: 'Response schema for GET /api/energy/meters.
Meters are returned in ascending ``started_at`` order so the caller sees
the historical installation sequence.'
MeterPatchRequest:
properties:
label:
anyOf:
- type: string
maxLength: 255
minLength: 1
- type: 'null'
title: Label
note:
anyOf:
- type: string
maxLength: 1024
- type: 'null'
title: Note
started_at:
anyOf:
- type: string
format: date-time
- type: 'null'
title: Started At
description: Retroactive correction of the meter epoch start timestamp. Triggers
billing recompute over the affected window.
type: object
title: MeterPatchRequest
description: 'Request body for PATCH /api/energy/meters/{id}.
All fields are optional. Only non-``None`` values are applied.
Updating ``started_at`` is a **retroactive correction**: the service layer
maintains timeline continuity (adjusting the preceding meter''s ``ended_at``)
and the API layer triggers ``recompute_range`` over the affected window so
that billing attribution is re-judged.'
MeterReason:
type: string
enum:
- initial
- meter_swap
- home_move
- other
title: MeterReason
description: Allowed values for the meter epoch creation reason.
MeterResponse:
properties:
id:
type: integer
title: Id
label:
type: string
title: Label
commodity:
type: string
title: Commodity
started_at:
type: string
format: date-time
title: Started At
ended_at:
anyOf:
- type: string
format: date-time
- type: 'null'
title: Ended At
reason:
type: string
title: Reason
note:
anyOf:
- type: string
- type: 'null'
title: Note
created_at:
type: string
format: date-time
title: Created At
type: object
required:
- id
- label
- commodity
- started_at
- ended_at
- reason
- note
- created_at
title: MeterResponse
description: 'Response schema for a single Meter epoch row.
``ended_at`` is ``null`` for the currently active meter.'
MetricInfo:
properties:
key: