M7-T05: add meter CRUD API + retroactive recompute + OpenAPI regen
This commit is contained in:
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user