M7-T06: add meter management UI (timeline, declare swap, edit) to Energy view

This commit is contained in:
2026-06-25 17:01:54 +02:00
parent 839faaf95b
commit 33f5de0591
6 changed files with 1244 additions and 0 deletions
+278
View File
@@ -559,6 +559,84 @@ export interface paths {
patch?: never;
trace?: never;
};
"/api/energy/meters": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/**
* 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``.
*/
get: operations["list_energy_meters_api_energy_meters_get"];
put?: never;
/**
* 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).
*/
post: operations["declare_energy_meter_api_energy_meters_post"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/energy/meters/{meter_id}": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
/**
* Patch Energy Meter
* @description Partially update a meter epoch: rename, edit note, or correct started_at.
*
* - ``label``: updates the human-readable label.
* - ``note``: updates the free-form note.
* - ``started_at``: **retroactive correction** — shifts this meter's start
* boundary. The service layer maintains timeline continuity by also
* updating the preceding meter's ``ended_at``. Validation:
* * Must be strictly after the previous meter's own ``started_at``.
* * Must be strictly before this meter's ``ended_at`` (if closed).
* Violation → 422.
*
* **Retroactive recompute when ``started_at`` changes**: billing records in
* the window ``[min(old, new), now)`` are re-judged to reflect the corrected
* meter attribution.
*
* Not found → 404.
*/
patch: operations["patch_energy_meter_api_energy_meters__meter_id__patch"];
trace?: never;
};
"/api/expose": {
parameters: {
query?: never;
@@ -1569,6 +1647,114 @@ export interface components {
*/
sell_normal: number;
};
/**
* 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``.
*/
MeterDeclareRequest: {
/** Label */
label: string;
/**
* Started At
* Format: date-time
* @description UTC (or server-local naive) datetime from which this meter epoch starts. May be in the past (retroactive declaration).
*/
started_at: string;
/** @description Why this epoch was created. One of: initial, meter_swap, home_move, other. */
reason: components["schemas"]["MeterReason"];
/** Note */
note?: string | null;
/**
* Commodity
* @description Energy commodity this meter measures. Defaults to 'electricity'.
* @default electricity
*/
commodity: string;
};
/**
* 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.
*/
MeterListResponse: {
/** Items */
items: components["schemas"]["MeterResponse"][];
/** Total */
total: number;
};
/**
* 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.
*/
MeterPatchRequest: {
/** Label */
label?: string | null;
/** Note */
note?: string | null;
/**
* Started At
* @description Retroactive correction of the meter epoch start timestamp. Triggers billing recompute over the affected window.
*/
started_at?: string | null;
};
/**
* MeterReason
* @description Allowed values for the meter epoch creation reason.
* @enum {string}
*/
MeterReason: "initial" | "meter_swap" | "home_move" | "other";
/**
* MeterResponse
* @description Response schema for a single Meter epoch row.
*
* ``ended_at`` is ``null`` for the currently active meter.
*/
MeterResponse: {
/** Id */
id: number;
/** Label */
label: string;
/** Commodity */
commodity: string;
/**
* Started At
* Format: date-time
*/
started_at: string;
/** Ended At */
ended_at: string | null;
/** Reason */
reason: string;
/** Note */
note: string | null;
/**
* Created At
* Format: date-time
*/
created_at: string;
};
/**
* MetricInfo
* @description Metadata for a single measurable quantity in a device's profile.
@@ -3007,6 +3193,98 @@ export interface operations {
};
};
};
list_energy_meters_api_energy_meters_get: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["MeterListResponse"];
};
};
};
};
declare_energy_meter_api_energy_meters_post: {
parameters: {
query?: never;
header?: {
"X-CSRF-Token"?: string | null;
};
path?: never;
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["MeterDeclareRequest"];
};
};
responses: {
/** @description Successful Response */
201: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["MeterResponse"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
patch_energy_meter_api_energy_meters__meter_id__patch: {
parameters: {
query?: never;
header?: {
"X-CSRF-Token"?: string | null;
};
path: {
meter_id: number;
};
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["MeterPatchRequest"];
};
};
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["MeterResponse"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
get_expose_api_expose_get: {
parameters: {
query?: never;