diff --git a/src/main/angular/src/app/api/series/constants.ts b/src/main/angular/src/app/api/series/constants.ts index 797cb1d..560071b 100644 --- a/src/main/angular/src/app/api/series/constants.ts +++ b/src/main/angular/src/app/api/series/constants.ts @@ -28,9 +28,7 @@ export const HEATING_BUFFER_CIRCULATION_TEMPERATURE = 'heating.buffer.circulatio export const HEATING_LOOP_SUPPLY_TEMPERATURE = 'heating.loop.supply.temperature'; export const HEATING_LOOP_RETURN_TEMPERATURE = 'heating.loop.return.temperature'; -export const PERCENT = new ValueConstant(100, "%"); -export const MILLIS_PER_MONTH = new ValueConstant(24 * 60 * 60 * 30 * 1000, ''); -export const MILLIS_PER_YEAR = new ValueConstant(24 * 60 * 60 * 365 * 1000, ''); +export const MILLIS_PER_YEAR = 24 * 60 * 60 * 365 * 1000; export const ELECTRICITY_GRID_PURCHASED_FEW = 7.7; export const ELECTRICITY_GRID_PURCHASED_MUCH = 9.7; diff --git a/src/main/angular/src/app/api/value/Display.ts b/src/main/angular/src/app/api/value/Display.ts index a9087ff..a7c593a 100644 --- a/src/main/angular/src/app/api/value/Display.ts +++ b/src/main/angular/src/app/api/value/Display.ts @@ -4,7 +4,7 @@ export class DisplayValue { constructor( readonly title: string, - readonly value: Value, + readonly value: Value | string, readonly color: string = '', ) { // - diff --git a/src/main/angular/src/app/api/value/Value.ts b/src/main/angular/src/app/api/value/Value.ts index 9666705..7931e3e 100644 --- a/src/main/angular/src/app/api/value/Value.ts +++ b/src/main/angular/src/app/api/value/Value.ts @@ -1,3 +1,5 @@ +export type V = Value | number; + export class Value { constructor( @@ -8,50 +10,12 @@ export class Value { // - } + /* UNARY ---------------------------------------------------------------------------------------- */ + clampNonNegative(): Value { return this.unary(v => v > 0 ? v : 0); } - plus(other: Value): Value { - return this.binary(other, (a, b) => a + b); - } - - minus(other: Value): Value { - return this.binary(other, (a, b) => a - b); - } - - mul(other: Value): Value { - return this.binary(other, (a, b) => a * b); - } - - div(other: Value): Value { - return this.binary(other, (a, b) => a / b); - } - - ne(other: Value): boolean { - return this.compare(other) !== 0; - } - - eq(other: Value): boolean { - return this.compare(other) === 0; - } - - gt(other: Value): boolean { - return this.compare(other) > 0; - } - - gte(other: Value): boolean { - return this.compare(other) >= 0; - } - - lt(other: Value): boolean { - return this.compare(other) < 0; - } - - lte(other: Value): boolean { - return this.compare(other) <= 0; - } - times(factor: number) { return this.unary(a => a * factor); } @@ -68,7 +32,41 @@ export class Value { return new Value(this.date, value, this.unit); } - binary(other: Value, func: (a: number, b: number) => number): Value { + /* BINARY --------------------------------------------------------------------------------------- */ + + plus(other: V): Value { + return this.binary(other, (a, b) => a + b); + } + + minus(other: V): Value { + return this.binary(other, (a, b) => a - b); + } + + mul(other: V): Value { + return this.binary(other, (a, b) => a * b); + } + + div(other: V): Value { + return this.binary(other, (a, b) => a / b); + } + + binary(other: V, func: (a: number, b: number) => number): Value { + if (typeof other === 'number') { + return this.binaryNumber(other, func); + } + return this.binaryValue(other, func); + } + + private binaryNumber(other: number, func: (a: number, b: number) => number) { + if (this.date === null || this.value === null) { + return new Value(null, null, ''); + } + const oldestDate = this.getOldestDate(other); + const difference = func(this.value, other); + return new Value(oldestDate, difference, this.unit); + } + + private binaryValue(other: Value, func: (a: number, b: number) => number) { if (this.date === null || this.value === null) { return new Value(null, null, other.unit); } @@ -80,7 +78,50 @@ export class Value { return new Value(oldestDate, difference, this.unit); } - compare(other: Value, comparing?: (a: number, b: number) => number): number { + /* COMPARE -------------------------------------------------------------------------------------- */ + + ne(other: V): boolean { + return this.compare(other) !== 0; + } + + eq(other: V): boolean { + return this.compare(other) === 0; + } + + gt(other: V): boolean { + return this.compare(other) > 0; + } + + gte(other: V): boolean { + return this.compare(other) >= 0; + } + + lt(other: V): boolean { + return this.compare(other) < 0; + } + + lte(other: V): boolean { + return this.compare(other) <= 0; + } + + compare(other: V, comparing?: (a: number, b: number) => number): number { + if (typeof other === 'number') { + return this.compareNumber(comparing, other); + } + return this.compareValue(other, comparing); + } + + private compareNumber(comparing: ((a: number, b: number) => number) | undefined, other: number) { + if (this.date === null || this.value === null) { + return -1; + } + if (comparing === undefined) { + return this.value - other; + } + return comparing(this.value, other); + } + + private compareValue(other: Value, comparing: ((a: number, b: number) => number) | undefined) { if (this.date === null || this.value === null) { return -1; } @@ -93,6 +134,8 @@ export class Value { return comparing(this.value, other.value); } + /* COLOR ---------------------------------------------------------------------------------------- */ + color(middle: number, high: number, smallColor: string = '#23F', middleColor: string = 'orange', highColor: string = 'red'): string { if (this.value === null) { return ""; @@ -105,7 +148,20 @@ export class Value { return highColor; } - private getOldestDate(other: Value) { + /* OLDEST DATE ---------------------------------------------------------------------------------- */ + + private getOldestDate(other: V) { + if (typeof other === 'number') { + return this.getOldestDateNumber(); + } + return this.getOldestDateValue(other); + } + + private getOldestDateNumber() { + return this.date; + } + + private getOldestDateValue(other: Value) { if (this.date === null || other.date === null) { return null; } @@ -117,10 +173,17 @@ export class Value { return this.date; } + /* MAP ------------------------------------------------------------------------------------------ */ + + map(map: (v: number) => T): T | null { + return this.value === null ? null : map(this.value); + } + + /* UNIT ----------------------------------------------------------------------------------------- */ + withUnit(unit: string) { return new Value(this.date, this.value, unit); } - } export class ValueConstant extends Value { diff --git a/src/main/angular/src/app/pages/dashboard/amortisation/dashboard-amortisation.component.ts b/src/main/angular/src/app/pages/dashboard/amortisation/dashboard-amortisation.component.ts index 4b811ab..57627d3 100644 --- a/src/main/angular/src/app/pages/dashboard/amortisation/dashboard-amortisation.component.ts +++ b/src/main/angular/src/app/pages/dashboard/amortisation/dashboard-amortisation.component.ts @@ -1,10 +1,10 @@ import {Component, Input} from '@angular/core'; import {SeriesCacheService} from "../../../api/series/series-cache.service"; import {Display, DisplayValue} from "../../../api/value/Display"; -import {ELECTRICITY_PHOTOVOLTAIC_PRODUCED_BEFORE_METER_CHANGE, GRID_KWH_EURO, MILLIS_PER_YEAR, PERCENT, PV_COST_AMORTISATION_BEGIN, PV_COST_AMORTISATION_KWH, PV_COST_TOTAL_EURO} from "../../../api/series/constants"; +import {ELECTRICITY_PHOTOVOLTAIC_PRODUCED_BEFORE_METER_CHANGE, GRID_KWH_EURO, MILLIS_PER_YEAR, PV_COST_AMORTISATION_BEGIN, PV_COST_AMORTISATION_KWH, PV_COST_TOTAL_EURO} from "../../../api/series/constants"; import {ValueListComponent} from "../../../shared/value-list/value-list.component"; -import {ValueConstant} from "../../../api/value/Value"; import {DatePipe} from "@angular/common"; +import {ValueConstant} from "../../../api/value/Value"; @Component({ selector: 'app-dashboard-amortisation', @@ -36,17 +36,17 @@ export class DashboardAmortisationComponent { const selfRatio = selfAfterChange.div(producedAfterChange); const selfConsumed = selfRatio.mul(this.seriesCacheService.photovoltaicProduced); const costsSaved = selfConsumed.mul(GRID_KWH_EURO).withUnit("€") - const amortisationPercent = selfConsumed.div(PV_COST_AMORTISATION_KWH).mul(PERCENT).withUnit('%'); + const amortisationPercent = selfConsumed.div(PV_COST_AMORTISATION_KWH).mul(100).withUnit('%'); const amortisationAlreadyMillis = new ValueConstant(this.now.getTime() - PV_COST_AMORTISATION_BEGIN.getTime(), 'ms'); const amortisationMilliPerKWh = amortisationAlreadyMillis.div(selfConsumed).withUnit('ms/kWh'); const amortisationRestKWh = PV_COST_AMORTISATION_KWH.minus(selfConsumed); const amortisationRestMillis = amortisationRestKWh.mul(amortisationMilliPerKWh).withUnit('ms'); const amortisationRestYears = amortisationRestMillis.div(MILLIS_PER_YEAR).withUnit('Jahre'); - const amortisationTotalMillis = PV_COST_AMORTISATION_KWH.mul(amortisationMilliPerKWh).withUnit('ms'); + const amortisationTotalMillis = amortisationMilliPerKWh.mul(PV_COST_AMORTISATION_KWH).withUnit('ms'); const amortisationTotalYears = amortisationTotalMillis.div(MILLIS_PER_YEAR).withUnit('Jahre'); - const amortisationDate = new Date(this.now.getTime() + (amortisationRestMillis.value || 0)); + const amortisationDate = amortisationRestMillis.plus(this.now.getTime()); const constRestEuro = PV_COST_TOTAL_EURO.minus(costsSaved); - const amortisationDateString = 'Erwarte volle Amortisation am: ' + this.datePipe.transform(amortisationDate, 'dd. MMM yyyy'); + const amortisationDateString = 'Erwarte volle Amortisation am: ' + this.datePipe.transform(amortisationDate.map(v => new Date(v)), 'dd. MMM yyyy'); return [ new DisplayValue('Ausgaben', PV_COST_TOTAL_EURO), new DisplayValue('Ersparnis', costsSaved), diff --git a/src/main/angular/src/app/pages/dashboard/electricity/dashboard-electricity-tile.component.ts b/src/main/angular/src/app/pages/dashboard/electricity/dashboard-electricity-tile.component.ts index 3b09183..ad0448e 100644 --- a/src/main/angular/src/app/pages/dashboard/electricity/dashboard-electricity-tile.component.ts +++ b/src/main/angular/src/app/pages/dashboard/electricity/dashboard-electricity-tile.component.ts @@ -5,7 +5,7 @@ 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_POWER_MUCH, ELECTRICITY_GRID_PURCHASED_ENERGY, ELECTRICITY_GRID_PURCHASED_FEW, ELECTRICITY_GRID_PURCHASED_MUCH, ELECTRICITY_PHOTOVOLTAIC_POWER_FEW, ELECTRICITY_PHOTOVOLTAIC_POWER_MUCH, ELECTRICITY_PHOTOVOLTAIC_PRODUCED, ELECTRICITY_PHOTOVOLTAIC_PRODUCED_BEFORE_METER_CHANGE, ELECTRICITY_PHOTOVOLTAIC_PRODUCED_FEW, ELECTRICITY_PHOTOVOLTAIC_PRODUCED_MUCH, PERCENT} from "../../../api/series/constants"; +import {ELECTRICITY_GRID_DELIVERED_ENERGY, ELECTRICITY_GRID_POWER_MUCH, ELECTRICITY_GRID_PURCHASED_ENERGY, ELECTRICITY_GRID_PURCHASED_FEW, ELECTRICITY_GRID_PURCHASED_MUCH, ELECTRICITY_PHOTOVOLTAIC_POWER_FEW, ELECTRICITY_PHOTOVOLTAIC_POWER_MUCH, ELECTRICITY_PHOTOVOLTAIC_PRODUCED, ELECTRICITY_PHOTOVOLTAIC_PRODUCED_BEFORE_METER_CHANGE, ELECTRICITY_PHOTOVOLTAIC_PRODUCED_FEW, ELECTRICITY_PHOTOVOLTAIC_PRODUCED_MUCH} from "../../../api/series/constants"; @Component({ selector: 'app-dashboard-electricity-tile', @@ -63,13 +63,13 @@ export class DashboardElectricityTileComponent { const selfToday = this.producedToday.minus(this.deliveredToday); const consumedToday = this.purchasedToday.plus(selfToday); - const selfPercentToday = selfToday.div(this.producedToday).mul(PERCENT).withUnit('%'); - const selfAutarkyPercentToday = selfToday.div(consumedToday).mul(PERCENT).withUnit('%'); + const selfPercentToday = selfToday.div(this.producedToday).mul(100).withUnit('%'); + const selfAutarkyPercentToday = selfToday.div(consumedToday).mul(100).withUnit('%'); const selfYesterday = this.producedYesterday.minus(this.deliveredYesterday); const consumedYesterday = this.purchasedYesterday.plus(selfYesterday); - const selfPercentYesterday = selfYesterday.div(this.producedYesterday).mul(PERCENT).withUnit('%'); - const selfAutarkyPercentYesterday = selfYesterday.div(consumedYesterday).mul(PERCENT).withUnit('%'); + const selfPercentYesterday = selfYesterday.div(this.producedYesterday).mul(100).withUnit('%'); + const selfAutarkyPercentYesterday = selfYesterday.div(consumedYesterday).mul(100).withUnit('%'); return [ 'Zählerstände', diff --git a/src/main/angular/src/app/shared/value-list/value-list.component.html b/src/main/angular/src/app/shared/value-list/value-list.component.html index 03074ec..4d650e3 100644 --- a/src/main/angular/src/app/shared/value-list/value-list.component.html +++ b/src/main/angular/src/app/shared/value-list/value-list.component.html @@ -5,14 +5,24 @@ - + - - + + + + + + + + + +
{{ asDisplay(item)?.title }} - {{ asDisplay(item)?.value?.value | number:'0.1-1' }} - - {{ asDisplay(item)?.value?.unit }} - + {{ asDisplayNumber(item)?.value | number:'0.1-1' }} + + {{ asDisplayNumber(item)?.unit }} + + {{ asDisplayString(item) }} +
diff --git a/src/main/angular/src/app/shared/value-list/value-list.component.ts b/src/main/angular/src/app/shared/value-list/value-list.component.ts index 7d7b3ba..73048c1 100644 --- a/src/main/angular/src/app/shared/value-list/value-list.component.ts +++ b/src/main/angular/src/app/shared/value-list/value-list.component.ts @@ -1,6 +1,7 @@ import {Component, Input} from '@angular/core'; import {DecimalPipe, NgForOf, NgIf} from "@angular/common"; import {Display, DisplayValue} from "../../api/value/Display"; +import {Value} from "../../api/value/Value"; @Component({ selector: 'app-value-list', @@ -53,7 +54,7 @@ export class ValueListComponent { private displayUpdate() { 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) + .some(d => !!d && !!d.value && (!(typeof d.value === 'object' && 'date' in d.value) || !!d.value.date && (this.now.getTime() - d.value.date.getTime()) <= this.maxAgeSeconds * 1000)); } asDisplay(item: Display): DisplayValue | null { @@ -70,4 +71,22 @@ export class ValueListComponent { return null; } + asDisplayNumber(value: DisplayValue | string | null): Value | null { + if (value instanceof DisplayValue) { + if (value.value instanceof Value) { + return value.value; + } + } + return null; + } + + asDisplayString(value: DisplayValue | string | null): string | null { + if (value instanceof DisplayValue) { + if (typeof value.value === 'string') { + return value.value; + } + } + return null; + } + }