Slice, Unit->Interval, Today+Yesterday
This commit is contained in:
parent
f76b6cdec8
commit
fe8afcad29
@ -14,9 +14,9 @@ Run `ng generate component component-name` to generate a new component. You can
|
||||
|
||||
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.
|
||||
|
||||
## Running unit tests
|
||||
## Running interval tests
|
||||
|
||||
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
|
||||
Run `ng test` to execute the interval tests via [Karma](https://karma-runner.github.io).
|
||||
|
||||
## Running end-to-end tests
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import {Period} from "./consumption/period/Period";
|
||||
import {validateDate, validateNumber, validateString} from "../validators";
|
||||
import {Value} from "../Value/Value";
|
||||
import {Value} from "../value/Value";
|
||||
|
||||
export class Series extends Value {
|
||||
|
||||
|
||||
27
src/main/angular/src/app/api/series/constants.ts
Normal file
27
src/main/angular/src/app/api/series/constants.ts
Normal file
@ -0,0 +1,27 @@
|
||||
export const ELECTRICITY_GRID_PURCHASED_ENERGY = 'electricity.grid.purchase.energy';
|
||||
export const ELECTRICITY_GRID_DELIVERED_ENERGY = 'electricity.grid.delivery.energy';
|
||||
export const ELECTRICITY_GRID_POWER = 'electricity.grid.power';
|
||||
|
||||
export const ELECTRICITY_PHOTOVOLTAIC_PRODUCED = 'electricity.photovoltaic.energy';
|
||||
export const ELECTRICITY_PHOTOVOLTAIC_POWER = 'electricity.photovoltaic.power';
|
||||
|
||||
export const SCHLAFZIMMER_TEMPERATURE = 'schlafzimmer.temperature';
|
||||
export const SCHLAFZIMMER_HUMIDITY_RELATIVE = 'schlafzimmer.humidity_relative';
|
||||
export const SCHLAFZIMMER_HUMIDITY_ABSOLUTE = 'schlafzimmer.humidity_absolute';
|
||||
|
||||
export const GARTEN_TEMPERATURE = 'garten.temperature';
|
||||
export const GARTEN_HUMIDITY_RELATIVE = 'garten.humidity_relative';
|
||||
export const GARTEN_HUMIDITY_ABSOLUTE = 'garten.humidity_absolute';
|
||||
|
||||
export const HEATING_ROOM_TEMPERATURE = 'heating.room.temperature';
|
||||
export const HEATING_ROOM_HUMIDITY_RELATIVE = 'heating.room.humidity_relative';
|
||||
export const HEATING_ROOM_HUMIDITY_ABSOLUTE = 'heating.room.humidity_absolute';
|
||||
export const HEATING_EXHAUST_TEMPERATURE = 'heating.exhaust.temperature';
|
||||
export const HEATING_BUFFER_SUPPLY_TEMPERATURE = 'heating.buffer.supply.temperature';
|
||||
export const HEATING_BUFFER_RETURN_TEMPERATURE = 'heating.buffer.return.temperature';
|
||||
export const HEATING_BUFFER_COLD_TEMPERATURE = 'heating.buffer.cold.temperature';
|
||||
export const HEATING_BUFFER_INNER_TEMPERATURE = 'heating.buffer.inner.temperature';
|
||||
export const HEATING_BUFFER_HOT_TEMPERATURE = 'heating.buffer.hot.temperature';
|
||||
export const HEATING_BUFFER_CIRCULATION_TEMPERATURE = 'heating.buffer.circulation.temperature';
|
||||
export const HEATING_LOOP_SUPPLY_TEMPERATURE = 'heating.loop.supply.temperature';
|
||||
export const HEATING_LOOP_RETURN_TEMPERATURE = 'heating.loop.return.temperature';
|
||||
@ -0,0 +1,8 @@
|
||||
export enum Interval {
|
||||
Quarterhour = 'Quarterhour',
|
||||
Hour = 'Hour',
|
||||
Day = 'Day',
|
||||
Week = 'Week',
|
||||
Month = 'Month',
|
||||
Year = 'Year',
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
import {validateDate, validateNumber, validateString} from "../../../validators";
|
||||
import {Value} from "../../../value/Value";
|
||||
|
||||
export class Slice extends Value {
|
||||
|
||||
static readonly EMPTY: Slice = new Slice(null, null, '');
|
||||
|
||||
constructor(
|
||||
date: Date | null,
|
||||
amount: number | null,
|
||||
unit: string,
|
||||
) {
|
||||
super(date, amount, unit);
|
||||
}
|
||||
|
||||
static fromJson(json: any): Slice {
|
||||
if (json === null) {
|
||||
return new Slice(null, null, '');
|
||||
}
|
||||
return new Slice(
|
||||
validateDate(json['date']),
|
||||
validateNumber(json['amount']),
|
||||
validateString(json['unit']),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {ApiService} from "../../../api.service";
|
||||
import {Next} from "../../../types";
|
||||
import {Slice} from "./Slice";
|
||||
|
||||
import {Interval} from "../interval/Interval";
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class SliceService {
|
||||
|
||||
constructor(
|
||||
private readonly api: ApiService,
|
||||
) {
|
||||
// -
|
||||
}
|
||||
|
||||
at(seriesName: string, interval: Interval, offset: number, next: Next<Slice>): void {
|
||||
this.api.getSingle(['Slice', 'seriesName', seriesName, 'interval', interval, 'offset', offset], Slice.fromJson, next);
|
||||
}
|
||||
|
||||
}
|
||||
@ -2,6 +2,7 @@ import {Injectable} from '@angular/core';
|
||||
import {Series} from "./Series";
|
||||
import {ApiService} from "../api.service";
|
||||
import {SeriesService} from "./series.service";
|
||||
import {ELECTRICITY_GRID_DELIVERED_ENERGY, ELECTRICITY_GRID_POWER, ELECTRICITY_GRID_PURCHASED_ENERGY, ELECTRICITY_PHOTOVOLTAIC_POWER, ELECTRICITY_PHOTOVOLTAIC_PRODUCED, GARTEN_HUMIDITY_ABSOLUTE, GARTEN_HUMIDITY_RELATIVE, GARTEN_TEMPERATURE, HEATING_BUFFER_CIRCULATION_TEMPERATURE, HEATING_BUFFER_COLD_TEMPERATURE, HEATING_BUFFER_HOT_TEMPERATURE, HEATING_BUFFER_INNER_TEMPERATURE, HEATING_BUFFER_RETURN_TEMPERATURE, HEATING_BUFFER_SUPPLY_TEMPERATURE, HEATING_EXHAUST_TEMPERATURE, HEATING_LOOP_RETURN_TEMPERATURE, HEATING_LOOP_SUPPLY_TEMPERATURE, HEATING_ROOM_HUMIDITY_ABSOLUTE, HEATING_ROOM_HUMIDITY_RELATIVE, HEATING_ROOM_TEMPERATURE, SCHLAFZIMMER_HUMIDITY_ABSOLUTE, SCHLAFZIMMER_HUMIDITY_RELATIVE, SCHLAFZIMMER_TEMPERATURE} from "./constants";
|
||||
|
||||
export function returnUpdatedSeriesIfNameAndNewer(fresh: Series, old: Series, name: string): Series {
|
||||
if (fresh.name !== name) {
|
||||
@ -80,34 +81,34 @@ export class SeriesCacheService {
|
||||
}
|
||||
|
||||
private seriesUpdate(series: Series) {
|
||||
this.gridPurchased = returnUpdatedSeriesIfNameAndNewer(series, this.gridPurchased, 'electricity.grid.purchase.energy');
|
||||
this.gridDelivered = returnUpdatedSeriesIfNameAndNewer(series, this.gridDelivered, 'electricity.grid.delivery.energy');
|
||||
this.gridPower = returnUpdatedSeriesIfNameAndNewer(series, this.gridPower, 'electricity.grid.power');
|
||||
this.gridPurchased = returnUpdatedSeriesIfNameAndNewer(series, this.gridPurchased, ELECTRICITY_GRID_PURCHASED_ENERGY);
|
||||
this.gridDelivered = returnUpdatedSeriesIfNameAndNewer(series, this.gridDelivered, ELECTRICITY_GRID_DELIVERED_ENERGY);
|
||||
this.gridPower = returnUpdatedSeriesIfNameAndNewer(series, this.gridPower, ELECTRICITY_GRID_POWER);
|
||||
|
||||
this.photovoltaicProduced = returnUpdatedSeriesIfNameAndNewer(series, this.photovoltaicProduced, 'electricity.photovoltaic.energy');
|
||||
this.photovoltaicPower = returnUpdatedSeriesIfNameAndNewer(series, this.photovoltaicPower, 'electricity.photovoltaic.power');
|
||||
this.photovoltaicProduced = returnUpdatedSeriesIfNameAndNewer(series, this.photovoltaicProduced, ELECTRICITY_PHOTOVOLTAIC_PRODUCED);
|
||||
this.photovoltaicPower = returnUpdatedSeriesIfNameAndNewer(series, this.photovoltaicPower, ELECTRICITY_PHOTOVOLTAIC_POWER);
|
||||
|
||||
this.schlafzimmerTemperature = returnUpdatedSeriesIfNameAndNewer(series, this.schlafzimmerTemperature, 'schlafzimmer.temperature');
|
||||
this.schlafzimmerHumidityRelative = returnUpdatedSeriesIfNameAndNewer(series, this.schlafzimmerHumidityRelative, 'schlafzimmer.humidity_relative');
|
||||
this.schlafzimmerHumidityAbsolute = returnUpdatedSeriesIfNameAndNewer(series, this.schlafzimmerHumidityAbsolute, 'schlafzimmer.humidity_absolute');
|
||||
this.schlafzimmerTemperature = returnUpdatedSeriesIfNameAndNewer(series, this.schlafzimmerTemperature, SCHLAFZIMMER_TEMPERATURE);
|
||||
this.schlafzimmerHumidityRelative = returnUpdatedSeriesIfNameAndNewer(series, this.schlafzimmerHumidityRelative, SCHLAFZIMMER_HUMIDITY_RELATIVE);
|
||||
this.schlafzimmerHumidityAbsolute = returnUpdatedSeriesIfNameAndNewer(series, this.schlafzimmerHumidityAbsolute, SCHLAFZIMMER_HUMIDITY_ABSOLUTE);
|
||||
|
||||
this.outdoorTemperature = returnUpdatedSeriesIfNameAndNewer(series, this.outdoorTemperature, 'garten.temperature');
|
||||
this.outdoorHumidityRelative = returnUpdatedSeriesIfNameAndNewer(series, this.outdoorHumidityRelative, 'garten.humidity_relative');
|
||||
this.outdoorHumidityAbsolute = returnUpdatedSeriesIfNameAndNewer(series, this.outdoorHumidityAbsolute, 'garten.humidity_absolute');
|
||||
this.outdoorTemperature = returnUpdatedSeriesIfNameAndNewer(series, this.outdoorTemperature, GARTEN_TEMPERATURE);
|
||||
this.outdoorHumidityRelative = returnUpdatedSeriesIfNameAndNewer(series, this.outdoorHumidityRelative, GARTEN_HUMIDITY_RELATIVE);
|
||||
this.outdoorHumidityAbsolute = returnUpdatedSeriesIfNameAndNewer(series, this.outdoorHumidityAbsolute, GARTEN_HUMIDITY_ABSOLUTE);
|
||||
|
||||
this.heatingRoomTemperature = returnUpdatedSeriesIfNameAndNewer(series, this.heatingRoomTemperature, 'heating.room.temperature');
|
||||
this.heatingRoomHumidityRelative = returnUpdatedSeriesIfNameAndNewer(series, this.heatingRoomHumidityRelative, 'heating.room.humidity_relative');
|
||||
this.heatingRoomHumidityAbsolute = returnUpdatedSeriesIfNameAndNewer(series, this.heatingRoomHumidityAbsolute, 'heating.room.humidity_absolute');
|
||||
this.heatingRoomTemperature = returnUpdatedSeriesIfNameAndNewer(series, this.heatingRoomTemperature, HEATING_ROOM_TEMPERATURE);
|
||||
this.heatingRoomHumidityRelative = returnUpdatedSeriesIfNameAndNewer(series, this.heatingRoomHumidityRelative, HEATING_ROOM_HUMIDITY_RELATIVE);
|
||||
this.heatingRoomHumidityAbsolute = returnUpdatedSeriesIfNameAndNewer(series, this.heatingRoomHumidityAbsolute, HEATING_ROOM_HUMIDITY_ABSOLUTE);
|
||||
|
||||
this.heatingExhaustTemperature = returnUpdatedSeriesIfNameAndNewer(series, this.heatingExhaustTemperature, 'heating.exhaust.temperature');
|
||||
this.heatingBufferSupplyTemperature = returnUpdatedSeriesIfNameAndNewer(series, this.heatingBufferSupplyTemperature, 'heating.buffer.supply.temperature');
|
||||
this.heatingBufferReturnTemperature = returnUpdatedSeriesIfNameAndNewer(series, this.heatingBufferReturnTemperature, 'heating.buffer.return.temperature');
|
||||
this.heatingBufferColdTemperature = returnUpdatedSeriesIfNameAndNewer(series, this.heatingBufferColdTemperature, 'heating.buffer.cold.temperature');
|
||||
this.heatingBufferInnerTemperature = returnUpdatedSeriesIfNameAndNewer(series, this.heatingBufferInnerTemperature, 'heating.buffer.inner.temperature');
|
||||
this.heatingBufferHotTemperature = returnUpdatedSeriesIfNameAndNewer(series, this.heatingBufferHotTemperature, 'heating.buffer.hot.temperature');
|
||||
this.heatingBufferCirculationTemperature = returnUpdatedSeriesIfNameAndNewer(series, this.heatingBufferCirculationTemperature, 'heating.buffer.circulation.temperature');
|
||||
this.heatingLoopSupplyTemperature = returnUpdatedSeriesIfNameAndNewer(series, this.heatingLoopSupplyTemperature, 'heating.loop.supply.temperature');
|
||||
this.heatingLoopReturnTemperature = returnUpdatedSeriesIfNameAndNewer(series, this.heatingLoopReturnTemperature, 'heating.loop.return.temperature');
|
||||
this.heatingExhaustTemperature = returnUpdatedSeriesIfNameAndNewer(series, this.heatingExhaustTemperature, HEATING_EXHAUST_TEMPERATURE);
|
||||
this.heatingBufferSupplyTemperature = returnUpdatedSeriesIfNameAndNewer(series, this.heatingBufferSupplyTemperature, HEATING_BUFFER_SUPPLY_TEMPERATURE);
|
||||
this.heatingBufferReturnTemperature = returnUpdatedSeriesIfNameAndNewer(series, this.heatingBufferReturnTemperature, HEATING_BUFFER_RETURN_TEMPERATURE);
|
||||
this.heatingBufferColdTemperature = returnUpdatedSeriesIfNameAndNewer(series, this.heatingBufferColdTemperature, HEATING_BUFFER_COLD_TEMPERATURE);
|
||||
this.heatingBufferInnerTemperature = returnUpdatedSeriesIfNameAndNewer(series, this.heatingBufferInnerTemperature, HEATING_BUFFER_INNER_TEMPERATURE);
|
||||
this.heatingBufferHotTemperature = returnUpdatedSeriesIfNameAndNewer(series, this.heatingBufferHotTemperature, HEATING_BUFFER_HOT_TEMPERATURE);
|
||||
this.heatingBufferCirculationTemperature = returnUpdatedSeriesIfNameAndNewer(series, this.heatingBufferCirculationTemperature, HEATING_BUFFER_CIRCULATION_TEMPERATURE);
|
||||
this.heatingLoopSupplyTemperature = returnUpdatedSeriesIfNameAndNewer(series, this.heatingLoopSupplyTemperature, HEATING_LOOP_SUPPLY_TEMPERATURE);
|
||||
this.heatingLoopReturnTemperature = returnUpdatedSeriesIfNameAndNewer(series, this.heatingLoopReturnTemperature, HEATING_LOOP_RETURN_TEMPERATURE);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -12,4 +12,4 @@ export class DisplayValue {
|
||||
|
||||
}
|
||||
|
||||
export type Display = DisplayValue | null;
|
||||
export type Display = DisplayValue | string | null;
|
||||
@ -5,7 +5,7 @@ export class Value {
|
||||
constructor(
|
||||
readonly date: Date | null,
|
||||
readonly value: number | null,
|
||||
readonly unit: string | null,
|
||||
readonly unit: string,
|
||||
) {
|
||||
// -
|
||||
}
|
||||
@ -56,17 +56,17 @@ export class Value {
|
||||
|
||||
unary(func: (v: number) => number): Value {
|
||||
if (this.value === null) {
|
||||
return new Value(null, null, null);
|
||||
return new Value(null, null, '');
|
||||
}
|
||||
const value = func(this.value);
|
||||
return new Value(this.date, value, this.unit);
|
||||
}
|
||||
|
||||
binary(other: Value, func: (a: number, b: number) => number): Value {
|
||||
if (this.date === null || this.value === null || this.unit === null) {
|
||||
return new Value(null, null, other.unit || null);
|
||||
if (this.date === null || this.value === null) {
|
||||
return new Value(null, null, other.unit);
|
||||
}
|
||||
if (other.date === null || other.value === null || other.unit === null) {
|
||||
if (other.date === null || other.value === null) {
|
||||
return new Value(null, null, this.unit);
|
||||
}
|
||||
const oldestDate = this.getOldestDate(other);
|
||||
@ -75,10 +75,10 @@ export class Value {
|
||||
}
|
||||
|
||||
compare(other: Value, comparing?: (a: number, b: number) => number): number {
|
||||
if (this.date === null || this.value === null || this.unit === null) {
|
||||
if (this.date === null || this.value === null) {
|
||||
return -1;
|
||||
}
|
||||
if (other.date === null || other.value === null || other.unit === null) {
|
||||
if (other.date === null || other.value === null) {
|
||||
return +1;
|
||||
}
|
||||
if (comparing === undefined) {
|
||||
@ -4,7 +4,7 @@ export class ValueConstant extends Value {
|
||||
|
||||
constructor(
|
||||
value: number | null,
|
||||
unit: string | null,
|
||||
unit: string,
|
||||
) {
|
||||
super(new Date(Date.now()), value, unit);
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
import {Component, Input} from '@angular/core';
|
||||
import {ValueListComponent} from "../../../shared/value-list/value-list.component";
|
||||
import {Display, DisplayValue} from "../../../api/Value/Display";
|
||||
import {Display, DisplayValue} from "../../../api/value/Display";
|
||||
import {SeriesCacheService} from "../../../api/series/series-cache.service";
|
||||
|
||||
@Component({
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<div class="tile">
|
||||
<app-dashboard-electricity-tile [now]="now"></app-dashboard-electricity-tile>
|
||||
<app-dashboard-electricity-tile [now]="now" [slowUpdate]="slowUpdate"></app-dashboard-electricity-tile>
|
||||
</div>
|
||||
|
||||
<div class="tile">
|
||||
|
||||
@ -7,6 +7,7 @@ import {Subscription, timer} from "rxjs";
|
||||
import {DashboardHeatingTileComponent} from "./heating/dashboard-heating-tile.component";
|
||||
|
||||
const UPDATE_INTERVAL_MILLIS = 1000;
|
||||
const SLOW_UPDATE_INTERVAL_MILLIS = 60 * 1000;
|
||||
|
||||
@Component({
|
||||
selector: 'app-dashboard',
|
||||
@ -26,10 +27,15 @@ export class DashboardComponent implements OnInit, OnDestroy {
|
||||
|
||||
protected now: Date = new Date();
|
||||
|
||||
protected slowUpdate: Date = new Date();
|
||||
|
||||
private timer?: Subscription;
|
||||
|
||||
private slowUpdateTimer?: Subscription;
|
||||
|
||||
ngOnInit(): void {
|
||||
this.timer = timer(0, UPDATE_INTERVAL_MILLIS).subscribe(() => this.now = new Date());
|
||||
this.slowUpdateTimer = timer(0, SLOW_UPDATE_INTERVAL_MILLIS).subscribe(() => this.slowUpdate = new Date());
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
|
||||
@ -1,8 +1,12 @@
|
||||
import {Component, Input} from '@angular/core';
|
||||
import {ValueListComponent} from "../../../shared/value-list/value-list.component";
|
||||
import {Display, DisplayValue} from "../../../api/Value/Display";
|
||||
import {ValueConstant} from "../../../api/Value/ValueConstant";
|
||||
import {Display, DisplayValue} from "../../../api/value/Display";
|
||||
import {ValueConstant} from "../../../api/value/ValueConstant";
|
||||
import {SeriesCacheService} from "../../../api/series/series-cache.service";
|
||||
import {SliceService} from "../../../api/series/consumption/slice/slice.service";
|
||||
import {Slice} from "../../../api/series/consumption/slice/Slice";
|
||||
import {Interval} from "../../../api/series/consumption/interval/Interval";
|
||||
import {ELECTRICITY_GRID_DELIVERED_ENERGY, ELECTRICITY_GRID_PURCHASED_ENERGY, ELECTRICITY_PHOTOVOLTAIC_PRODUCED} from "../../../api/series/constants";
|
||||
|
||||
const PURCHASING_MUCH = 200;
|
||||
|
||||
@ -22,30 +26,74 @@ export class DashboardElectricityTileComponent {
|
||||
@Input()
|
||||
now!: Date;
|
||||
|
||||
protected producedToday: Slice = Slice.EMPTY;
|
||||
|
||||
protected purchasedToday: Slice = Slice.EMPTY;
|
||||
|
||||
protected deliveredToday: Slice = Slice.EMPTY;
|
||||
|
||||
protected producedYesterday: Slice = Slice.EMPTY;
|
||||
|
||||
protected purchasedYesterday: Slice = Slice.EMPTY;
|
||||
|
||||
protected deliveredYesterday: Slice = Slice.EMPTY;
|
||||
|
||||
@Input()
|
||||
set slowUpdate(_: Date) {
|
||||
this.sliceService.at(ELECTRICITY_PHOTOVOLTAIC_PRODUCED, Interval.Day, 0, slice => this.producedToday = slice);
|
||||
this.sliceService.at(ELECTRICITY_GRID_PURCHASED_ENERGY, Interval.Day, 0, slice => this.purchasedToday = slice);
|
||||
this.sliceService.at(ELECTRICITY_GRID_DELIVERED_ENERGY, Interval.Day, 0, slice => this.deliveredToday = slice);
|
||||
|
||||
this.sliceService.at(ELECTRICITY_PHOTOVOLTAIC_PRODUCED, Interval.Day, 1, slice => this.producedYesterday = slice);
|
||||
this.sliceService.at(ELECTRICITY_GRID_PURCHASED_ENERGY, Interval.Day, 1, slice => this.purchasedYesterday = slice);
|
||||
this.sliceService.at(ELECTRICITY_GRID_DELIVERED_ENERGY, Interval.Day, 1, slice => this.deliveredYesterday = slice);
|
||||
}
|
||||
|
||||
constructor(
|
||||
protected readonly seriesCacheService: SeriesCacheService,
|
||||
private readonly seriesCacheService: SeriesCacheService,
|
||||
private readonly sliceService: SliceService,
|
||||
) {
|
||||
// -
|
||||
}
|
||||
|
||||
getDisplayList(): Display[] {
|
||||
const consumptionPower = this.seriesCacheService.photovoltaicPower.plus(this.seriesCacheService.gridPower).clampNonNegative();
|
||||
const producedAfterChange = this.seriesCacheService.photovoltaicProduced.minus(PRODUCED_BEFORE_METER_CHANGE);
|
||||
const selfAfterChange = producedAfterChange.minus(this.seriesCacheService.gridDelivered);
|
||||
const selfRatio = selfAfterChange.div(producedAfterChange);
|
||||
const selfConsumed = selfRatio.mul(this.seriesCacheService.photovoltaicProduced);
|
||||
|
||||
const consumptionPower = this.seriesCacheService.photovoltaicPower.plus(this.seriesCacheService.gridPower).clampNonNegative();
|
||||
|
||||
const gridColor = this.getGridPowerColor();
|
||||
const productionColor = this.getProductionPowerColor();
|
||||
|
||||
const selfToday = this.producedToday.minus(this.deliveredToday);
|
||||
const consumedToday = this.purchasedToday.plus(selfToday);
|
||||
|
||||
const selfYesterday = this.producedYesterday.minus(this.deliveredYesterday);
|
||||
const consumedYesterday = this.purchasedYesterday.plus(selfYesterday);
|
||||
return [
|
||||
'Zählerstände',
|
||||
new DisplayValue('Bezogen', this.seriesCacheService.gridPurchased, ''),
|
||||
new DisplayValue('Eingespeist', this.seriesCacheService.gridDelivered, ''),
|
||||
new DisplayValue('Produziert', this.seriesCacheService.photovoltaicProduced, ''),
|
||||
new DisplayValue('Selbst verbraucht', selfConsumed, ''),
|
||||
null,
|
||||
'Leistung',
|
||||
new DisplayValue('Produktion', this.seriesCacheService.photovoltaicPower, productionColor),
|
||||
new DisplayValue('Netz', this.seriesCacheService.gridPower, gridColor),
|
||||
new DisplayValue('Verbrauch', consumptionPower, ''),
|
||||
'Heute',
|
||||
new DisplayValue('Produziert', this.producedToday, ''),
|
||||
new DisplayValue('Eingespeist', this.deliveredToday, ''),
|
||||
new DisplayValue('Selbstverbraucht', selfToday, ''),
|
||||
new DisplayValue('Bezogen', this.purchasedToday, ''),
|
||||
new DisplayValue('Verbraucht', consumedToday, ''),
|
||||
'Gestern',
|
||||
new DisplayValue('Produziert', this.producedYesterday, ''),
|
||||
new DisplayValue('Eingespeist', this.deliveredYesterday, ''),
|
||||
new DisplayValue('Selbstverbraucht', selfYesterday, ''),
|
||||
new DisplayValue('Bezogen', this.purchasedYesterday, ''),
|
||||
new DisplayValue('Verbraucht', consumedYesterday, ''),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import {Component, Input} from '@angular/core';
|
||||
import {ValueListComponent} from "../../../shared/value-list/value-list.component";
|
||||
import {Display, DisplayValue} from "../../../api/Value/Display";
|
||||
import {Display, DisplayValue} from "../../../api/value/Display";
|
||||
import {SeriesCacheService} from "../../../api/series/series-cache.service";
|
||||
|
||||
const WARM = 25;
|
||||
|
||||
@ -4,17 +4,22 @@
|
||||
{{ title }}
|
||||
</div>
|
||||
<table class="values">
|
||||
<ng-container *ngFor="let display of displayList">
|
||||
<tr class="rate" *ngIf="display" [style.color]="display.color">
|
||||
<th>{{ display.title }}</th>
|
||||
<ng-container *ngFor="let item of displayList">
|
||||
<tr class="rate" *ngIf="asDisplay(item)" [style.color]="asDisplay(item)?.color">
|
||||
<th>{{ asDisplay(item)?.title }}</th>
|
||||
<td class="v">
|
||||
{{ display?.value?.value | number:'0.0-0' }}
|
||||
{{ asDisplay(item)?.value?.value | number:'0.1-1' }}
|
||||
</td>
|
||||
<td class="u">
|
||||
{{ display?.value?.unit }}
|
||||
{{ asDisplay(item)?.value?.unit }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr *ngIf="display === null">
|
||||
<tr *ngIf="asString(item)">
|
||||
<td colspan="3" class="header">
|
||||
{{ asString(item) }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr *ngIf="item === null">
|
||||
<td colspan="3" class="spacer"></td>
|
||||
</tr>
|
||||
</ng-container>
|
||||
|
||||
@ -49,6 +49,13 @@
|
||||
border-top: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.header {
|
||||
border-top: 1px solid #ddd;
|
||||
text-align: center;
|
||||
font-style: italic;
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import {Component, Input} from '@angular/core';
|
||||
import {DecimalPipe, NgForOf, NgIf} from "@angular/common";
|
||||
import {Display} from "../../api/Value/Display";
|
||||
import {Display, DisplayValue} from "../../api/value/Display";
|
||||
|
||||
@Component({
|
||||
selector: 'app-value-list',
|
||||
@ -51,7 +51,23 @@ export class ValueListComponent {
|
||||
maxAgeSeconds: number = 10;
|
||||
|
||||
private displayUpdate() {
|
||||
this.valid = this.displayList.some(d => !!d && !!d.value && !!d.value.date && (this.now.getTime() - d.value.date.getTime()) <= this.maxAgeSeconds * 1000)
|
||||
this.valid = this.displayList
|
||||
.filter(d => d instanceof DisplayValue)
|
||||
.some(d => !!d && !!d.value && !!d.value.date && (this.now.getTime() - d.value.date.getTime()) <= this.maxAgeSeconds * 1000)
|
||||
}
|
||||
|
||||
asDisplay(item: Display): DisplayValue | null {
|
||||
if (item instanceof DisplayValue) {
|
||||
return item;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
asString(item: Display): string | null {
|
||||
if (typeof item === 'string') {
|
||||
return item;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
package de.ph87.data.series;
|
||||
|
||||
import de.ph87.data.series.consumption.unit.Unit;
|
||||
import de.ph87.data.series.interval.Interval;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Embeddable;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
@ -22,16 +22,16 @@ public class SeriesIntervalKey implements Serializable {
|
||||
|
||||
@NonNull
|
||||
@Column(nullable = false, updatable = false, columnDefinition = "CHAR(1)")
|
||||
private Unit unit;
|
||||
private Interval interval;
|
||||
|
||||
@NonNull
|
||||
@Column(nullable = false, updatable = false)
|
||||
private ZonedDateTime aligned;
|
||||
|
||||
public SeriesIntervalKey(@NonNull final Series series, @NonNull final Unit unit, @NonNull final ZonedDateTime unaligned) {
|
||||
public SeriesIntervalKey(@NonNull final Series series, @NonNull final Interval interval, @NonNull final ZonedDateTime unaligned) {
|
||||
this.series = series;
|
||||
this.unit = unit;
|
||||
this.aligned = unit.align(unaligned);
|
||||
this.interval = interval;
|
||||
this.aligned = interval.align(unaligned);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -5,29 +5,44 @@ import lombok.NonNull;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
public enum SeriesMode {
|
||||
MEASURE((first, second) -> second - first, (last, value) -> value),
|
||||
COUNTER((first, second) -> second - first, Double::sum),
|
||||
INCREASING((first, second) -> second - first, (last, value) -> value),
|
||||
DECREASING((first, second) -> first - second, (last, value) -> value),
|
||||
MEASURE(
|
||||
(first, second) -> second - first,
|
||||
(last, value) -> value
|
||||
),
|
||||
COUNTER(
|
||||
(first, second) -> second - first,
|
||||
Double::sum
|
||||
),
|
||||
INCREASING(
|
||||
(first, second) -> second - first,
|
||||
(last, value) -> value
|
||||
),
|
||||
DECREASING(
|
||||
(first, second) -> first - second,
|
||||
(last, value) -> value
|
||||
),
|
||||
;
|
||||
|
||||
@NonNull
|
||||
private final BiFunction<Double, Double, Double> delta;
|
||||
private final BiFunction<Double, Double, Double> amount;
|
||||
|
||||
@NonNull
|
||||
private final BiFunction<Double, Double, Double> add;
|
||||
private final BiFunction<Double, Double, Double> plus;
|
||||
|
||||
SeriesMode(@NonNull final BiFunction<Double, Double, Double> delta, @NonNull final BiFunction<Double, Double, Double> add) {
|
||||
this.delta = delta;
|
||||
this.add = add;
|
||||
SeriesMode(
|
||||
@NonNull final BiFunction<Double, Double, Double> amount,
|
||||
@NonNull final BiFunction<Double, Double, Double> plus
|
||||
) {
|
||||
this.amount = amount;
|
||||
this.plus = plus;
|
||||
}
|
||||
|
||||
public double getDelta(final double first, final double second) {
|
||||
return delta.apply(first, second);
|
||||
public double amount(final double first, final double second) {
|
||||
return amount.apply(first, second);
|
||||
}
|
||||
|
||||
public double update(final double series, final double value) {
|
||||
return add.apply(series, value);
|
||||
return plus.apply(series, value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -4,8 +4,10 @@ import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.List;
|
||||
@ -37,4 +39,9 @@ public class SeriesService {
|
||||
return seriesRepository.findAll().stream().map(SeriesDto::new).toList();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Series getByName(@NonNull final String name) {
|
||||
return seriesRepository.findByNameOrAliasesContains(name, name).orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
package de.ph87.data.series.consumption;
|
||||
|
||||
import de.ph87.data.series.consumption.period.Period;
|
||||
import de.ph87.data.series.consumption.unit.Unit;
|
||||
import de.ph87.data.series.interval.Interval;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
import static de.ph87.data.series.consumption.slice.SliceService.DL;
|
||||
import static de.ph87.data.series.interval.IntervalHelper.DL;
|
||||
|
||||
@Entity
|
||||
@Getter
|
||||
@ -32,15 +32,13 @@ public class Consumption {
|
||||
}
|
||||
|
||||
@ToString.Include
|
||||
@SuppressWarnings("unused") // toString
|
||||
public Unit unit() {
|
||||
return id.unit;
|
||||
public Interval interval() {
|
||||
return id.interval;
|
||||
}
|
||||
|
||||
@ToString.Include
|
||||
@SuppressWarnings("unused") // toString
|
||||
public String aligned() {
|
||||
return DL(id.unit, id.aligned);
|
||||
return DL(id.interval, id.aligned);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@ -59,8 +57,8 @@ public class Consumption {
|
||||
@Column(nullable = false)
|
||||
private double lastValue;
|
||||
|
||||
public Consumption(@NonNull final Period period, @NonNull final Unit unit, @NonNull final ZonedDateTime aligned, @NonNull final ZonedDateTime date, final double value) {
|
||||
this.id = new Id(period, unit, aligned);
|
||||
public Consumption(@NonNull final Period period, @NonNull final Interval interval, @NonNull final ZonedDateTime aligned, @NonNull final ZonedDateTime date, final double value) {
|
||||
this.id = new Id(period, interval, aligned);
|
||||
this.firstDate = date;
|
||||
this.firstValue = value;
|
||||
this.lastDate = date;
|
||||
@ -81,7 +79,7 @@ public class Consumption {
|
||||
|
||||
@NonNull
|
||||
@Column(nullable = false, updatable = false, columnDefinition = "CHAR(1)")
|
||||
private Unit unit;
|
||||
private Interval interval;
|
||||
|
||||
@NonNull
|
||||
@Column(nullable = false, updatable = false)
|
||||
|
||||
@ -1,80 +0,0 @@
|
||||
package de.ph87.data.series.consumption;
|
||||
|
||||
import de.ph87.data.common.DateTimeHelpers;
|
||||
import de.ph87.data.series.consumption.slice.Slice;
|
||||
import de.ph87.data.series.consumption.slice.SliceService;
|
||||
import de.ph87.data.series.consumption.unit.Unit;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@CrossOrigin
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@RequestMapping("Consumption")
|
||||
public class ConsumptionController {
|
||||
|
||||
private static final int MAX_COUNT = 1500;
|
||||
|
||||
private final SliceService sliceService;
|
||||
|
||||
@NonNull
|
||||
@GetMapping("{seriesId}/{unitName}/last/{count}")
|
||||
public List<List<Number>> latest(@PathVariable final long seriesId, @PathVariable final String unitName, @PathVariable final int count) {
|
||||
return offset(seriesId, unitName, count, 0);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@GetMapping("{seriesId}/{unitName}/last/{count}/{offset}")
|
||||
public List<List<Number>> offset(@PathVariable final long seriesId, @PathVariable final String unitName, @PathVariable final int count, @PathVariable final int offset) {
|
||||
if (count <= 0) {
|
||||
log.error("'count' must at least be 1");
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
if (count > MAX_COUNT) {
|
||||
log.error("'count' must at most be {}", MAX_COUNT);
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
final Unit unit = Unit.valueOf(unitName);
|
||||
final ZonedDateTime end = unit.plus(unit.align(ZonedDateTime.now()), -offset);
|
||||
final ZonedDateTime begin = unit.plus(end, -(count - 1));
|
||||
return between(seriesId, unit, begin, end);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@GetMapping("{seriesId}/{unitName}/between/{beginEpochSeconds}/{endEpochSeconds}")
|
||||
public List<List<Number>> between(@PathVariable final long seriesId, @PathVariable final String unitName, @PathVariable final long beginEpochSeconds, @PathVariable final long endEpochSeconds) {
|
||||
return between(seriesId, Unit.valueOf(unitName), DateTimeHelpers.ZDT(beginEpochSeconds), DateTimeHelpers.ZDT(endEpochSeconds));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private List<List<Number>> between(final long seriesId, @NonNull final Unit unit, @NonNull final ZonedDateTime begin, @NonNull final ZonedDateTime end) {
|
||||
final long estimatedCount = unit.estimateCount(begin, end);
|
||||
log.debug("estimatedCount: {}", estimatedCount);
|
||||
if (estimatedCount > MAX_COUNT) {
|
||||
log.error("'estimatedCount' must at most be {} but is {}", MAX_COUNT, estimatedCount);
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
return sliceService.slice(seriesId, unit, begin, end)
|
||||
.stream()
|
||||
.map(this::map)
|
||||
.toList();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private List<Number> map(@NonNull final Slice slice) {
|
||||
final ArrayList<Number> numbers = new ArrayList<>();
|
||||
numbers.add(slice.begin.toEpochSecond());
|
||||
numbers.add(Double.isNaN(slice.getDelta()) ? null : slice.getDelta());
|
||||
return numbers;
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,22 +1,21 @@
|
||||
package de.ph87.data.series.consumption;
|
||||
|
||||
import de.ph87.data.series.consumption.period.Period;
|
||||
import de.ph87.data.series.consumption.unit.Unit;
|
||||
import de.ph87.data.series.interval.Interval;
|
||||
import lombok.NonNull;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface ConsumptionRepository extends CrudRepository<Consumption, Consumption.Id> {
|
||||
|
||||
Optional<Consumption> findByIdPeriodAndIdUnitAndIdAligned(@NonNull Period period, @NonNull Unit unit, @NonNull ZonedDateTime aligned);
|
||||
Optional<Consumption> findByIdPeriodAndIdIntervalAndIdAligned(@NonNull Period period, @NonNull Interval interval, @NonNull ZonedDateTime aligned);
|
||||
|
||||
Optional<Consumption> findFirstByIdPeriodAndIdUnitAndIdAlignedLessThanOrderByIdAlignedDesc(@NonNull Period period, @NonNull Unit unit, @NonNull ZonedDateTime begin);
|
||||
Optional<Consumption> findFirstByIdPeriodAndIdIntervalAndIdAlignedLessThanOrderByIdAlignedDesc(@NonNull Period period, @NonNull Interval interval, @NonNull ZonedDateTime begin);
|
||||
|
||||
Optional<Consumption> findFirstByIdPeriodAndIdUnitAndIdAlignedGreaterThanOrderByIdAlignedAsc(@NonNull Period period, @NonNull Unit unit, @NonNull ZonedDateTime begin);
|
||||
Optional<Consumption> findFirstByIdPeriodAndIdIntervalAndIdAlignedGreaterThanOrderByIdAlignedAsc(@NonNull Period period, @NonNull Interval interval, @NonNull ZonedDateTime begin);
|
||||
|
||||
List<Consumption> findAllByIdPeriodAndIdUnitAndIdAlignedGreaterThanEqualAndIdAlignedLessThanEqualOrderByIdAlignedAsc(@NonNull Period period, @NonNull Unit unit, @NonNull ZonedDateTime begin, @NonNull ZonedDateTime end);
|
||||
Optional<Consumption> findFirstByIdPeriodAndIdIntervalAndIdAligned(@NonNull Period period, @NonNull Interval interval, @NonNull ZonedDateTime date);
|
||||
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@ import de.ph87.data.series.SeriesMode;
|
||||
import de.ph87.data.series.SeriesService;
|
||||
import de.ph87.data.series.consumption.period.Period;
|
||||
import de.ph87.data.series.consumption.period.PeriodService;
|
||||
import de.ph87.data.series.consumption.unit.Unit;
|
||||
import de.ph87.data.series.interval.Interval;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@ -37,16 +37,16 @@ public class ConsumptionService {
|
||||
period.setLastDate(event.getDate());
|
||||
period.setLastValue(series.getMode().update(period.getLastValue(), event.getValue()));
|
||||
|
||||
for (final Unit unit : Unit.values()) {
|
||||
final ZonedDateTime aligned = unit.align(event.getDate());
|
||||
final Optional<Consumption> existingOptional = consumptionRepository.findByIdPeriodAndIdUnitAndIdAligned(period, unit, aligned);
|
||||
for (final Interval interval : Interval.values()) {
|
||||
final ZonedDateTime aligned = interval.align(event.getDate());
|
||||
final Optional<Consumption> existingOptional = consumptionRepository.findByIdPeriodAndIdIntervalAndIdAligned(period, interval, aligned);
|
||||
if (existingOptional.isPresent()) {
|
||||
final Consumption existing = existingOptional.get();
|
||||
existing.setLastDate(event.getDate());
|
||||
existing.setLastValue(event.getValue());
|
||||
log.debug("Existing Consumption updated: {}", existing);
|
||||
} else {
|
||||
final Consumption created = consumptionRepository.save(new Consumption(period, unit, aligned, event.getDate(), event.getValue()));
|
||||
final Consumption created = consumptionRepository.save(new Consumption(period, interval, aligned, event.getDate(), event.getValue()));
|
||||
log.debug("New Consumption created: created={}", created);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package de.ph87.data.series.consumption.period;
|
||||
|
||||
import de.ph87.data.series.Series;
|
||||
import lombok.NonNull;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
@ -8,6 +9,6 @@ import java.util.List;
|
||||
|
||||
public interface PeriodRepository extends CrudRepository<Period, Long> {
|
||||
|
||||
List<Period> findAllBySeriesIdAndLastDateGreaterThanAndFirstDateLessThan(long seriesId, @NonNull ZonedDateTime wantedEnd, @NonNull ZonedDateTime wantedBegin);
|
||||
List<Period> findAllBySeriesAndLastDateGreaterThanAndFirstDateLessThan(@NonNull Series series, @NonNull ZonedDateTime wantedEnd, @NonNull ZonedDateTime wantedBegin);
|
||||
|
||||
}
|
||||
|
||||
@ -1,71 +0,0 @@
|
||||
package de.ph87.data.series.consumption.slice;
|
||||
|
||||
import de.ph87.data.series.consumption.Consumption;
|
||||
import de.ph87.data.series.consumption.unit.Unit;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
public class Slice {
|
||||
|
||||
@NonNull
|
||||
public final ZonedDateTime begin;
|
||||
|
||||
@NonNull
|
||||
public final ZonedDateTime end;
|
||||
|
||||
@Setter
|
||||
private double delta;
|
||||
|
||||
public Slice(@NonNull final Consumption consumption) {
|
||||
this(consumption.getFirstDate(), consumption.getLastDate(), consumption.getId().getPeriod().getSeries().getMode().getDelta(consumption.getFirstValue(), consumption.getLastValue()));
|
||||
}
|
||||
|
||||
public Slice(@NonNull final Consumption first, @NonNull final Consumption second) {
|
||||
this(first.getLastDate(), second.getFirstDate(), first.getId().getPeriod().getSeries().getMode().getDelta(first.getLastValue(), second.getFirstValue()));
|
||||
}
|
||||
|
||||
private Slice(@NonNull final ZonedDateTime begin, @NonNull final ZonedDateTime end, final double delta) {
|
||||
this.begin = begin;
|
||||
this.end = end;
|
||||
this.delta = delta;
|
||||
}
|
||||
|
||||
public Slice(@NonNull final ZonedDateTime begin, @NonNull final Unit unit) {
|
||||
this.begin = begin;
|
||||
this.end = unit.plus(begin, 1);
|
||||
this.delta = Double.NaN;
|
||||
}
|
||||
|
||||
public double getDeltaPerMilli() {
|
||||
return delta / Duration.between(begin, end).toMillis();
|
||||
}
|
||||
|
||||
public void merge(@NonNull final Slice other) {
|
||||
if (!this.begin.equals(other.begin)) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
if (!this.end.equals(other.end)) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
add(other.delta);
|
||||
}
|
||||
|
||||
public void add(final double addDelta) {
|
||||
if (Double.isNaN(addDelta)) {
|
||||
return;
|
||||
}
|
||||
if (Double.isNaN(this.delta)) {
|
||||
this.delta = addDelta;
|
||||
} else {
|
||||
this.delta += addDelta;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,34 @@
|
||||
package de.ph87.data.series.consumption.slice;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
public class SliceAligned {
|
||||
|
||||
@NonNull
|
||||
private final ZonedDateTime date;
|
||||
|
||||
public final double amount;
|
||||
|
||||
public SliceAligned(@NonNull final SliceAligned a, @NonNull final SliceAligned b) {
|
||||
if (a.date.toEpochSecond() != b.date.toEpochSecond()) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
this.date = a.date;
|
||||
this.amount = a.amount + b.amount;
|
||||
}
|
||||
|
||||
public SliceAligned(@NonNull final ZonedDateTime date, final double amount) {
|
||||
if (amount < 0) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
this.date = date;
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
package de.ph87.data.series.consumption.slice;
|
||||
|
||||
import de.ph87.data.series.interval.Interval;
|
||||
import jakarta.annotation.Nullable;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
@Slf4j
|
||||
@CrossOrigin
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@RequestMapping("Slice")
|
||||
public class SliceController {
|
||||
|
||||
private final SliceService sliceService;
|
||||
|
||||
@Nullable
|
||||
@GetMapping("seriesName/{seriesName}/interval/{intervalName}/offset/{offset}")
|
||||
public SliceDto offset(@NonNull @PathVariable final String seriesName, @NonNull @PathVariable final String intervalName, @PathVariable final int offset) {
|
||||
final Interval interval = Interval.valueOf(intervalName);
|
||||
final ZonedDateTime date = interval.plus(interval.align(ZonedDateTime.now()), -offset);
|
||||
return sliceService.at(seriesName, interval, date);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
package de.ph87.data.series.consumption.slice;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
public class SliceDto {
|
||||
|
||||
public final ZonedDateTime date;
|
||||
|
||||
public final double amount;
|
||||
|
||||
public final String unit;
|
||||
|
||||
public SliceDto(@NonNull final SliceAligned slice, final @NonNull String unit) {
|
||||
this.date = slice.getDate();
|
||||
this.amount = slice.getAmount();
|
||||
this.unit = unit;
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,10 +1,13 @@
|
||||
package de.ph87.data.series.consumption.slice;
|
||||
|
||||
import de.ph87.data.series.Series;
|
||||
import de.ph87.data.series.SeriesMode;
|
||||
import de.ph87.data.series.SeriesService;
|
||||
import de.ph87.data.series.consumption.Consumption;
|
||||
import de.ph87.data.series.consumption.ConsumptionRepository;
|
||||
import de.ph87.data.series.consumption.period.Period;
|
||||
import de.ph87.data.series.consumption.period.PeriodRepository;
|
||||
import de.ph87.data.series.consumption.unit.Unit;
|
||||
import de.ph87.data.series.interval.Interval;
|
||||
import jakarta.annotation.Nullable;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@ -14,11 +17,8 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Objects;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@ -26,166 +26,67 @@ import java.util.Optional;
|
||||
@RequiredArgsConstructor
|
||||
public class SliceService {
|
||||
|
||||
private final ConsumptionRepository consumptionRepository;
|
||||
|
||||
private final PeriodRepository periodRepository;
|
||||
|
||||
@NonNull
|
||||
public List<Slice> slice(final long seriesId, @NonNull final Unit unit, @NonNull final ZonedDateTime begin, @NonNull final ZonedDateTime end) {
|
||||
log.debug("slice:");
|
||||
log.debug(" seriesId: {}", seriesId);
|
||||
log.debug(" unit: {}", unit);
|
||||
private final ConsumptionRepository consumptionRepository;
|
||||
|
||||
final ZonedDateTime wantedFirst = unit.align(begin);
|
||||
final ZonedDateTime wantedLast = unit.align(end);
|
||||
log.debug(" wantedFirst: {}", DL(unit, wantedFirst));
|
||||
log.debug(" wantedLast: {}", DL(unit, wantedLast));
|
||||
private final SeriesService seriesService;
|
||||
|
||||
final List<Period> periods = periodRepository.findAllBySeriesIdAndLastDateGreaterThanAndFirstDateLessThan(seriesId, wantedFirst, unit.plus(wantedLast, 1));
|
||||
log.debug(" periods: {}", periods.size());
|
||||
|
||||
final List<Slice> totalSlices = new ArrayList<>();
|
||||
for (final Period period : periods) {
|
||||
log.debug(" {}", period);
|
||||
log.debug(" firstDate: {}", DL(unit, period.getFirstDate()));
|
||||
log.debug(" lastDate: {}", DL(unit, period.getLastDate()));
|
||||
|
||||
final List<Slice> periodSlices = reslicePeriod(unit, period, wantedFirst, wantedLast);
|
||||
print("periodSlices", periodSlices, 3);
|
||||
periodSlices.forEach(merge -> merge(totalSlices, merge));
|
||||
|
||||
print("totalSlices", totalSlices, 3);
|
||||
}
|
||||
return totalSlices;
|
||||
}
|
||||
|
||||
private static void merge(@NonNull final List<Slice> resultList, @NonNull final Slice merge) {
|
||||
for (int resultIndex = 0; resultIndex < resultList.size(); resultIndex++) {
|
||||
final Slice result = resultList.get(resultIndex);
|
||||
final long compare = result.begin.toEpochSecond() - merge.begin.toEpochSecond();
|
||||
if (compare == 0) {
|
||||
result.merge(merge);
|
||||
return;
|
||||
}
|
||||
if (compare > 0) {
|
||||
resultList.add(resultIndex, merge);
|
||||
return;
|
||||
}
|
||||
}
|
||||
resultList.add(merge);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private List<Slice> reslicePeriod(@NonNull final Unit unit, @NonNull final Period period, @NonNull final ZonedDateTime firstBegin, @NonNull final ZonedDateTime lastBegin) {
|
||||
final ZonedDateTime lastEnd = unit.plus(lastBegin, 1);
|
||||
|
||||
final List<Slice> sourceList = slicePeriod(period, unit, firstBegin, lastBegin);
|
||||
final List<Slice> resultList = new ArrayList<>();
|
||||
|
||||
ZonedDateTime date = firstBegin;
|
||||
Slice result = firstResult(firstBegin, unit, resultList);
|
||||
Slice source = nextSourceIfNeeded(date, null, sourceList);
|
||||
while (date.isBefore(lastEnd)) {
|
||||
source = nextSourceIfNeeded(date, source, sourceList);
|
||||
result = nextResultIfNeeded(date, result, resultList, unit, lastEnd);
|
||||
if (source == null) {
|
||||
date = result.end;
|
||||
} else {
|
||||
final ZonedDateTime earliestEnd = source.end.isBefore(result.end) ? source.end : result.end;
|
||||
if (hasOverlap(source, earliestEnd, date)) {
|
||||
final ZonedDateTime latestBegin = source.begin.isAfter(date) ? source.begin : date;
|
||||
final long millis = Duration.between(latestBegin, earliestEnd).toMillis();
|
||||
result.add(millis * source.getDeltaPerMilli());
|
||||
}
|
||||
date = earliestEnd;
|
||||
}
|
||||
}
|
||||
return resultList;
|
||||
}
|
||||
|
||||
private static boolean hasOverlap(@NonNull final Slice source, @NonNull final ZonedDateTime earliestEnd, @NonNull final ZonedDateTime date) {
|
||||
return source.begin.isBefore(earliestEnd) && source.end.isAfter(date);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static Slice firstResult(@NonNull final ZonedDateTime begin, @NonNull final Unit unit, @NonNull final List<Slice> resultList) {
|
||||
final Slice newWanted = new Slice(begin, unit);
|
||||
resultList.add(newWanted);
|
||||
return newWanted;
|
||||
@Nullable
|
||||
public SliceDto at(@NonNull final String seriesName, @NonNull final Interval interval, @NonNull final ZonedDateTime date) {
|
||||
final ZonedDateTime sliceBegin = interval.align(date);
|
||||
final Series series = seriesService.getByName(seriesName);
|
||||
final List<Period> periods = periodRepository.findAllBySeriesAndLastDateGreaterThanAndFirstDateLessThan(series, sliceBegin, sliceBegin);
|
||||
return periods.stream()
|
||||
.map(period -> at(period, interval, sliceBegin))
|
||||
.filter(Objects::nonNull)
|
||||
.reduce(SliceAligned::new)
|
||||
.map(slice -> new SliceDto(slice, series.getUnit()))
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Slice nextSourceIfNeeded(@NonNull final ZonedDateTime date, @Nullable final Slice source, @NonNull final List<Slice> sourceList) {
|
||||
if (source == null || !date.isBefore(source.end)) {
|
||||
return sourceList.isEmpty() ? null : sourceList.remove(0);
|
||||
private SliceAligned at(@NonNull final Period period, @NonNull final Interval interval, @NonNull final ZonedDateTime sliceBegin) {
|
||||
final SeriesMode mode = period.getSeries().getMode();
|
||||
|
||||
final ZonedDateTime sliceEnd = interval.plus(sliceBegin, 1);
|
||||
|
||||
final Consumption before = consumptionRepository.findFirstByIdPeriodAndIdIntervalAndIdAlignedLessThanOrderByIdAlignedDesc(period, interval, sliceBegin).orElse(null);
|
||||
final Consumption wanted = consumptionRepository.findFirstByIdPeriodAndIdIntervalAndIdAligned(period, interval, sliceBegin).orElse(null);
|
||||
final Consumption after = consumptionRepository.findFirstByIdPeriodAndIdIntervalAndIdAlignedGreaterThanOrderByIdAlignedAsc(period, interval, sliceBegin).orElse(null);
|
||||
|
||||
final double sliceAmount;
|
||||
if (wanted == null) {
|
||||
if (before == null || after == null) {
|
||||
return null;
|
||||
}
|
||||
return source;
|
||||
final long totalMillis = Duration.between(before.getLastDate(), after.getFirstDate()).toMillis();
|
||||
final double totalAmount = mode.amount(before.getLastValue(), after.getFirstValue());
|
||||
final long sliceMillis = Duration.between(sliceBegin, sliceEnd).toMillis();
|
||||
sliceAmount = sliceMillis * totalAmount / totalMillis;
|
||||
} else {
|
||||
final double firstValue;
|
||||
if (before != null) {
|
||||
final long totalMillis = Duration.between(before.getLastDate(), wanted.getFirstDate()).toMillis();
|
||||
final double totalDelta = wanted.getFirstValue() - before.getLastValue();
|
||||
final long beforeMillis = Duration.between(before.getLastDate(), sliceBegin).toMillis();
|
||||
firstValue = before.getLastValue() + beforeMillis * totalDelta / totalMillis;
|
||||
} else {
|
||||
firstValue = wanted.getFirstValue();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static Slice nextResultIfNeeded(@NonNull final ZonedDateTime date, @NonNull final Slice result, @NonNull final List<Slice> resultList, @NonNull final Unit unit, @NonNull final ZonedDateTime lastEnd) {
|
||||
if (date.isBefore(lastEnd) && !date.isBefore(result.end)) {
|
||||
final Slice slice = new Slice(result.end, unit);
|
||||
resultList.add(slice);
|
||||
return slice;
|
||||
final double lastValue;
|
||||
if (after != null) {
|
||||
final long totalMillis = Duration.between(wanted.getLastDate(), after.getFirstDate()).toMillis();
|
||||
final double totalDelta = after.getFirstValue() - wanted.getLastValue();
|
||||
final long afterMillis = Duration.between(sliceEnd, after.getFirstDate()).toMillis();
|
||||
lastValue = after.getFirstValue() - afterMillis * totalDelta / totalMillis;
|
||||
} else {
|
||||
lastValue = wanted.getLastValue();
|
||||
}
|
||||
return result;
|
||||
sliceAmount = mode.amount(firstValue, lastValue);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private List<Slice> slicePeriod(@NonNull final Period period, @NonNull final Unit unit, @NonNull final ZonedDateTime wantedFirst, @NonNull final ZonedDateTime wantedLast) {
|
||||
final Optional<Consumption> firstOptional = consumptionRepository.findFirstByIdPeriodAndIdUnitAndIdAlignedLessThanOrderByIdAlignedDesc(period, unit, wantedFirst)
|
||||
.or(() -> consumptionRepository.findFirstByIdPeriodAndIdUnitAndIdAlignedGreaterThanOrderByIdAlignedAsc(period, unit, wantedFirst));
|
||||
if (firstOptional.isEmpty()) {
|
||||
log.error(" No first Consumption for Period: {}", period);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
final Optional<Consumption> lastOptional = consumptionRepository.findFirstByIdPeriodAndIdUnitAndIdAlignedGreaterThanOrderByIdAlignedAsc(period, unit, wantedLast)
|
||||
.or(() -> consumptionRepository.findFirstByIdPeriodAndIdUnitAndIdAlignedLessThanOrderByIdAlignedDesc(period, unit, wantedLast));
|
||||
if (lastOptional.isEmpty()) {
|
||||
log.error(" No last Consumption for Period: {}", period);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
final Consumption firstToFetch = firstOptional.get();
|
||||
final Consumption lastToFetch = lastOptional.get();
|
||||
final List<Consumption> consumptions = consumptionRepository.findAllByIdPeriodAndIdUnitAndIdAlignedGreaterThanEqualAndIdAlignedLessThanEqualOrderByIdAlignedAsc(period, unit, firstToFetch.getId().getAligned(), lastToFetch.getId().getAligned());
|
||||
|
||||
print("consumptions", consumptions, 3);
|
||||
Consumption last = null;
|
||||
final List<Slice> slices = new ArrayList<>();
|
||||
for (final Consumption consumption : consumptions) {
|
||||
if (last != null) {
|
||||
slices.add(new Slice(last, consumption));
|
||||
}
|
||||
if (!consumption.getFirstDate().equals(consumption.getLastDate())) {
|
||||
slices.add(new Slice(consumption));
|
||||
}
|
||||
last = consumption;
|
||||
}
|
||||
|
||||
print("sourceSlices", slices, 3);
|
||||
return slices;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@SuppressWarnings("SuspiciousDateFormat")
|
||||
public static String DL(@NonNull final Unit unit, @NonNull final ZonedDateTime date) {
|
||||
return switch (unit) {
|
||||
case Quarterhour, Hour -> date.toLocalDateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"));
|
||||
case Day -> date.toLocalDate().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
|
||||
case Week -> date.toLocalDate().format(DateTimeFormatter.ofPattern("YYYY-'KW'w"));
|
||||
case Month -> date.toLocalDate().format(DateTimeFormatter.ofPattern("yyyy-LLLL"));
|
||||
case Year -> date.toLocalDate().format(DateTimeFormatter.ofPattern("yyyy"));
|
||||
};
|
||||
}
|
||||
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
private static void print(@NonNull final String name, @NonNull final List<?> list, final int indent) {
|
||||
final String indentStr = " ".repeat(indent * 2);
|
||||
log.debug("{}{}: {}", indentStr, name, list.size());
|
||||
list.forEach(item -> log.debug("{}{}", indentStr + " ", item.toString()));
|
||||
return new SliceAligned(sliceBegin, sliceAmount);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,27 +0,0 @@
|
||||
package de.ph87.data.series.consumption.unit;
|
||||
|
||||
import jakarta.persistence.AttributeConverter;
|
||||
import jakarta.persistence.Converter;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
@Converter(autoApply = true)
|
||||
public class UnitJpaConverter implements AttributeConverter<Unit, String> {
|
||||
|
||||
@Override
|
||||
public String convertToDatabaseColumn(final Unit unit) {
|
||||
if (unit == null) {
|
||||
return null;
|
||||
}
|
||||
return unit.code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Unit convertToEntityAttribute(final String code) {
|
||||
if (code == null) {
|
||||
return null;
|
||||
}
|
||||
return Arrays.stream(Unit.values()).filter(u -> u.code.equals(code)).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
}
|
||||
@ -4,7 +4,7 @@ import de.ph87.data.series.Series;
|
||||
import de.ph87.data.series.SeriesIntervalKey;
|
||||
import de.ph87.data.series.SeriesMode;
|
||||
import de.ph87.data.series.SeriesService;
|
||||
import de.ph87.data.series.consumption.unit.Unit;
|
||||
import de.ph87.data.series.interval.Interval;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@ -27,8 +27,8 @@ public class CounterService {
|
||||
log.debug("Handling CounterEvent: {}", event);
|
||||
|
||||
final Series series = seriesService.getOrCreateByName(event.getName(), SeriesMode.COUNTER, event.getDate(), event.getCount(), event.getUnit());
|
||||
for (final Unit unit : Unit.values()) {
|
||||
final SeriesIntervalKey id = new SeriesIntervalKey(series, unit, event.getDate());
|
||||
for (final Interval interval : Interval.values()) {
|
||||
final SeriesIntervalKey id = new SeriesIntervalKey(series, interval, event.getDate());
|
||||
counterRepository.findById(id)
|
||||
.stream()
|
||||
.peek(existing -> existing.setCount(existing.getCount() + event.getCount()))
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
package de.ph87.data.series.consumption.unit;
|
||||
package de.ph87.data.series.interval;
|
||||
|
||||
import lombok.NonNull;
|
||||
|
||||
@ -9,7 +9,7 @@ import java.time.temporal.ChronoUnit;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
public enum Unit {
|
||||
public enum Interval {
|
||||
Quarterhour("q", t -> t.truncatedTo(ChronoUnit.MINUTES).minusMinutes(t.getMinute() % 15), (t, count) -> t.plusMinutes(15 * count), (a, b) -> Duration.between(a, b).toMinutes() / 15),
|
||||
Hour("h", t -> t.truncatedTo(ChronoUnit.HOURS), ZonedDateTime::plusHours, (a, b) -> Duration.between(a, b).toHours()),
|
||||
Day("d", t -> t.truncatedTo(ChronoUnit.DAYS), ZonedDateTime::plusDays, (a, b) -> Duration.between(a, b).toDays()),
|
||||
@ -30,7 +30,7 @@ public enum Unit {
|
||||
@NonNull
|
||||
private final BiFunction<ZonedDateTime, ZonedDateTime, Long> estimateCount;
|
||||
|
||||
Unit(@NonNull final String code, @NonNull final Function<ZonedDateTime, ZonedDateTime> align, @NonNull final BiFunction<ZonedDateTime, Long, ZonedDateTime> offset, @NonNull final BiFunction<ZonedDateTime, ZonedDateTime, Long> estimateCount) {
|
||||
Interval(@NonNull final String code, @NonNull final Function<ZonedDateTime, ZonedDateTime> align, @NonNull final BiFunction<ZonedDateTime, Long, ZonedDateTime> offset, @NonNull final BiFunction<ZonedDateTime, ZonedDateTime, Long> estimateCount) {
|
||||
this.code = code;
|
||||
this.align = align;
|
||||
this.offset = offset;
|
||||
@ -0,0 +1,22 @@
|
||||
package de.ph87.data.series.interval;
|
||||
|
||||
import lombok.NonNull;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
public class IntervalHelper {
|
||||
|
||||
@NonNull
|
||||
@SuppressWarnings("SuspiciousDateFormat")
|
||||
public static String DL(@NonNull final Interval interval, @NonNull final ZonedDateTime date) {
|
||||
return switch (interval) {
|
||||
case Quarterhour, Hour -> date.toLocalDateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"));
|
||||
case Day -> date.toLocalDate().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
|
||||
case Week -> date.toLocalDate().format(DateTimeFormatter.ofPattern("YYYY-'KW'w"));
|
||||
case Month -> date.toLocalDate().format(DateTimeFormatter.ofPattern("yyyy-LLLL"));
|
||||
case Year -> date.toLocalDate().format(DateTimeFormatter.ofPattern("yyyy"));
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
package de.ph87.data.series.interval;
|
||||
|
||||
import jakarta.persistence.AttributeConverter;
|
||||
import jakarta.persistence.Converter;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
@Converter(autoApply = true)
|
||||
public class IntervalJpaConverter implements AttributeConverter<Interval, String> {
|
||||
|
||||
@Override
|
||||
public String convertToDatabaseColumn(final Interval interval) {
|
||||
if (interval == null) {
|
||||
return null;
|
||||
}
|
||||
return interval.code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Interval convertToEntityAttribute(final String code) {
|
||||
if (code == null) {
|
||||
return null;
|
||||
}
|
||||
return Arrays.stream(Interval.values()).filter(u -> u.code.equals(code)).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
}
|
||||
@ -4,7 +4,7 @@ import de.ph87.data.series.Series;
|
||||
import de.ph87.data.series.SeriesIntervalKey;
|
||||
import de.ph87.data.series.SeriesMode;
|
||||
import de.ph87.data.series.SeriesService;
|
||||
import de.ph87.data.series.consumption.unit.Unit;
|
||||
import de.ph87.data.series.interval.Interval;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@ -27,8 +27,8 @@ public class MeasureService {
|
||||
log.debug("Handling MeasureEvent: {}", event);
|
||||
|
||||
final Series series = seriesService.getOrCreateByName(event.getName(), SeriesMode.MEASURE, event.getDate(), event.getValue(), event.getUnit());
|
||||
for (final Unit unit : Unit.values()) {
|
||||
final SeriesIntervalKey id = new SeriesIntervalKey(series, unit, event.getDate());
|
||||
for (final Interval interval : Interval.values()) {
|
||||
final SeriesIntervalKey id = new SeriesIntervalKey(series, interval, event.getDate());
|
||||
measureRepository.findById(id)
|
||||
.stream()
|
||||
.peek(existing -> existing.update(event))
|
||||
|
||||
Loading…
Reference in New Issue
Block a user