Value-operations accept Value|number and Display.value accepts Value|string now

This commit is contained in:
Patrick Haßel 2024-10-31 10:46:44 +01:00
parent a02b59fd7e
commit 1ee3215c91
7 changed files with 157 additions and 67 deletions

View File

@ -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_SUPPLY_TEMPERATURE = 'heating.loop.supply.temperature';
export const HEATING_LOOP_RETURN_TEMPERATURE = 'heating.loop.return.temperature'; export const HEATING_LOOP_RETURN_TEMPERATURE = 'heating.loop.return.temperature';
export const PERCENT = new ValueConstant(100, "%"); export const MILLIS_PER_YEAR = 24 * 60 * 60 * 365 * 1000;
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 ELECTRICITY_GRID_PURCHASED_FEW = 7.7; export const ELECTRICITY_GRID_PURCHASED_FEW = 7.7;
export const ELECTRICITY_GRID_PURCHASED_MUCH = 9.7; export const ELECTRICITY_GRID_PURCHASED_MUCH = 9.7;

View File

@ -4,7 +4,7 @@ export class DisplayValue {
constructor( constructor(
readonly title: string, readonly title: string,
readonly value: Value, readonly value: Value | string,
readonly color: string = '', readonly color: string = '',
) { ) {
// - // -

View File

@ -1,3 +1,5 @@
export type V = Value | number;
export class Value { export class Value {
constructor( constructor(
@ -8,50 +10,12 @@ export class Value {
// - // -
} }
/* UNARY ---------------------------------------------------------------------------------------- */
clampNonNegative(): Value { clampNonNegative(): Value {
return this.unary(v => v > 0 ? v : 0); 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) { times(factor: number) {
return this.unary(a => a * factor); return this.unary(a => a * factor);
} }
@ -68,7 +32,41 @@ export class Value {
return new Value(this.date, value, this.unit); 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) { if (this.date === null || this.value === null) {
return new Value(null, null, other.unit); return new Value(null, null, other.unit);
} }
@ -80,7 +78,50 @@ export class Value {
return new Value(oldestDate, difference, this.unit); 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) { if (this.date === null || this.value === null) {
return -1; return -1;
} }
@ -93,6 +134,8 @@ export class Value {
return comparing(this.value, other.value); return comparing(this.value, other.value);
} }
/* COLOR ---------------------------------------------------------------------------------------- */
color(middle: number, high: number, smallColor: string = '#23F', middleColor: string = 'orange', highColor: string = 'red'): string { color(middle: number, high: number, smallColor: string = '#23F', middleColor: string = 'orange', highColor: string = 'red'): string {
if (this.value === null) { if (this.value === null) {
return ""; return "";
@ -105,7 +148,20 @@ export class Value {
return highColor; 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) { if (this.date === null || other.date === null) {
return null; return null;
} }
@ -117,10 +173,17 @@ export class Value {
return this.date; return this.date;
} }
/* MAP ------------------------------------------------------------------------------------------ */
map<T>(map: (v: number) => T): T | null {
return this.value === null ? null : map(this.value);
}
/* UNIT ----------------------------------------------------------------------------------------- */
withUnit(unit: string) { withUnit(unit: string) {
return new Value(this.date, this.value, unit); return new Value(this.date, this.value, unit);
} }
} }
export class ValueConstant extends Value { export class ValueConstant extends Value {

View File

@ -1,10 +1,10 @@
import {Component, Input} from '@angular/core'; import {Component, Input} from '@angular/core';
import {SeriesCacheService} from "../../../api/series/series-cache.service"; import {SeriesCacheService} from "../../../api/series/series-cache.service";
import {Display, DisplayValue} from "../../../api/value/Display"; 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 {ValueListComponent} from "../../../shared/value-list/value-list.component";
import {ValueConstant} from "../../../api/value/Value";
import {DatePipe} from "@angular/common"; import {DatePipe} from "@angular/common";
import {ValueConstant} from "../../../api/value/Value";
@Component({ @Component({
selector: 'app-dashboard-amortisation', selector: 'app-dashboard-amortisation',
@ -36,17 +36,17 @@ export class DashboardAmortisationComponent {
const selfRatio = selfAfterChange.div(producedAfterChange); const selfRatio = selfAfterChange.div(producedAfterChange);
const selfConsumed = selfRatio.mul(this.seriesCacheService.photovoltaicProduced); const selfConsumed = selfRatio.mul(this.seriesCacheService.photovoltaicProduced);
const costsSaved = selfConsumed.mul(GRID_KWH_EURO).withUnit("€") 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 amortisationAlreadyMillis = new ValueConstant(this.now.getTime() - PV_COST_AMORTISATION_BEGIN.getTime(), 'ms');
const amortisationMilliPerKWh = amortisationAlreadyMillis.div(selfConsumed).withUnit('ms/kWh'); const amortisationMilliPerKWh = amortisationAlreadyMillis.div(selfConsumed).withUnit('ms/kWh');
const amortisationRestKWh = PV_COST_AMORTISATION_KWH.minus(selfConsumed); const amortisationRestKWh = PV_COST_AMORTISATION_KWH.minus(selfConsumed);
const amortisationRestMillis = amortisationRestKWh.mul(amortisationMilliPerKWh).withUnit('ms'); const amortisationRestMillis = amortisationRestKWh.mul(amortisationMilliPerKWh).withUnit('ms');
const amortisationRestYears = amortisationRestMillis.div(MILLIS_PER_YEAR).withUnit('Jahre'); 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 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 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 [ return [
new DisplayValue('Ausgaben', PV_COST_TOTAL_EURO), new DisplayValue('Ausgaben', PV_COST_TOTAL_EURO),
new DisplayValue('Ersparnis', costsSaved), new DisplayValue('Ersparnis', costsSaved),

View File

@ -5,7 +5,7 @@ import {SeriesCacheService} from "../../../api/series/series-cache.service";
import {SliceService} from "../../../api/series/consumption/slice/slice.service"; import {SliceService} from "../../../api/series/consumption/slice/slice.service";
import {Slice} from "../../../api/series/consumption/slice/Slice"; import {Slice} from "../../../api/series/consumption/slice/Slice";
import {Interval} from "../../../api/series/consumption/interval/Interval"; 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({ @Component({
selector: 'app-dashboard-electricity-tile', selector: 'app-dashboard-electricity-tile',
@ -63,13 +63,13 @@ export class DashboardElectricityTileComponent {
const selfToday = this.producedToday.minus(this.deliveredToday); const selfToday = this.producedToday.minus(this.deliveredToday);
const consumedToday = this.purchasedToday.plus(selfToday); const consumedToday = this.purchasedToday.plus(selfToday);
const selfPercentToday = selfToday.div(this.producedToday).mul(PERCENT).withUnit('%'); const selfPercentToday = selfToday.div(this.producedToday).mul(100).withUnit('%');
const selfAutarkyPercentToday = selfToday.div(consumedToday).mul(PERCENT).withUnit('%'); const selfAutarkyPercentToday = selfToday.div(consumedToday).mul(100).withUnit('%');
const selfYesterday = this.producedYesterday.minus(this.deliveredYesterday); const selfYesterday = this.producedYesterday.minus(this.deliveredYesterday);
const consumedYesterday = this.purchasedYesterday.plus(selfYesterday); const consumedYesterday = this.purchasedYesterday.plus(selfYesterday);
const selfPercentYesterday = selfYesterday.div(this.producedYesterday).mul(PERCENT).withUnit('%'); const selfPercentYesterday = selfYesterday.div(this.producedYesterday).mul(100).withUnit('%');
const selfAutarkyPercentYesterday = selfYesterday.div(consumedYesterday).mul(PERCENT).withUnit('%'); const selfAutarkyPercentYesterday = selfYesterday.div(consumedYesterday).mul(100).withUnit('%');
return [ return [
'Zählerstände', 'Zählerstände',

View File

@ -5,14 +5,24 @@
</div> </div>
<table class="values"> <table class="values">
<ng-container *ngFor="let item of displayList"> <ng-container *ngFor="let item of displayList">
<tr class="rate" *ngIf="asDisplay(item)" [style.color]="asDisplay(item)?.color"> <tr class="rate" *ngIf="asDisplay(item) !== null" [style.color]="asDisplay(item)?.color">
<th>{{ asDisplay(item)?.title }}</th> <th>{{ asDisplay(item)?.title }}</th>
<td class="v">
{{ asDisplay(item)?.value?.value | number:'0.1-1' }} <ng-container *ngIf="asDisplayNumber(item) !== null">
</td> <td class="v">
<td class="u"> {{ asDisplayNumber(item)?.value | number:'0.1-1' }}
{{ asDisplay(item)?.value?.unit }} </td>
</td> <td class="u">
{{ asDisplayNumber(item)?.unit }}
</td>
</ng-container>
<ng-container *ngIf="asDisplayString(item) !== null">
<td class="v" colspan="2">
{{ asDisplayString(item) }}
</td>
</ng-container>
</tr> </tr>
<tr *ngIf="asString(item)"> <tr *ngIf="asString(item)">
<td colspan="3" class="header"> <td colspan="3" class="header">

View File

@ -1,6 +1,7 @@
import {Component, Input} from '@angular/core'; import {Component, Input} from '@angular/core';
import {DecimalPipe, NgForOf, NgIf} from "@angular/common"; import {DecimalPipe, NgForOf, NgIf} from "@angular/common";
import {Display, DisplayValue} from "../../api/value/Display"; import {Display, DisplayValue} from "../../api/value/Display";
import {Value} from "../../api/value/Value";
@Component({ @Component({
selector: 'app-value-list', selector: 'app-value-list',
@ -53,7 +54,7 @@ export class ValueListComponent {
private displayUpdate() { private displayUpdate() {
this.valid = this.displayList this.valid = this.displayList
.filter(d => d instanceof DisplayValue) .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 { asDisplay(item: Display): DisplayValue | null {
@ -70,4 +71,22 @@ export class ValueListComponent {
return null; 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;
}
} }