95 lines
2.9 KiB
TypeScript
95 lines
2.9 KiB
TypeScript
import type { ForecastSlot, TimeseriesEntry, SolarDetails } from "@/types/evcc";
|
|
import deepCopy from "./deepClone";
|
|
|
|
export enum ForecastType {
|
|
Solar = "solar",
|
|
Price = "price",
|
|
Co2 = "co2",
|
|
}
|
|
|
|
// return the date in local YYYY-MM-DD format
|
|
function toDayString(date: Date): string {
|
|
const year = date.getFullYear();
|
|
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
const day = String(date.getDate()).padStart(2, "0");
|
|
return `${year}-${month}-${day}`;
|
|
}
|
|
|
|
// return only slots that are on a given date, ignores slots that are in the past
|
|
export function filterEntriesByDate(
|
|
entries: TimeseriesEntry[],
|
|
dayString: string
|
|
): TimeseriesEntry[] {
|
|
const now = new Date();
|
|
return entries.filter(({ ts }) => {
|
|
const isPast = new Date(ts) < now;
|
|
const dateMatches = toDayString(new Date(ts)) === dayString;
|
|
return !isPast && dateMatches;
|
|
});
|
|
}
|
|
|
|
// return the date in local YYYY-MM-DD format
|
|
export function dayStringByOffset(day: number): string {
|
|
const date = new Date();
|
|
date.setDate(date.getDate() + day);
|
|
return toDayString(date);
|
|
}
|
|
|
|
// return the highest slot for a given day (0 = today, 1 = tomorrow, etc.)
|
|
export function highestSlotIndexByDay(entries: TimeseriesEntry[], day: number = 0): number {
|
|
const dayString = dayStringByOffset(day);
|
|
const dayEntries = filterEntriesByDate(entries, dayString);
|
|
const sortedEntries = dayEntries.sort((a, b) => b.val - a.val);
|
|
const highestEntry = sortedEntries[0];
|
|
if (!highestEntry) return -1;
|
|
return entries.findIndex((entry) => entry.ts === highestEntry.ts);
|
|
}
|
|
|
|
export function adjustedSolar(solar?: SolarDetails): SolarDetails | undefined {
|
|
if (!solar?.scale) return solar;
|
|
|
|
const { scale } = solar;
|
|
const result = deepCopy(solar);
|
|
|
|
if (result.today) result.today.energy *= scale;
|
|
if (result.tomorrow) result.tomorrow.energy *= scale;
|
|
if (result.dayAfterTomorrow) result.dayAfterTomorrow.energy *= scale;
|
|
if (result.timeseries) {
|
|
result.timeseries.forEach((entry) => {
|
|
entry.val *= scale;
|
|
});
|
|
}
|
|
|
|
result.scale = 1 / scale; // invert to allow back-adjustment
|
|
|
|
return result;
|
|
}
|
|
|
|
export function isStaticTariff(slots?: ForecastSlot[]): boolean {
|
|
if (!Array.isArray(slots) || slots.length === 0) return false;
|
|
const firstValue = slots[0].value;
|
|
return slots.every((slot) => slot.value === firstValue);
|
|
}
|
|
|
|
export interface SlotWithValue {
|
|
start: string | Date;
|
|
value: number;
|
|
}
|
|
|
|
// find index of first slot with lowest sum over span (e.g. span=4 for 1h with 15min slots)
|
|
export function findLowestSumSlotIndex(slots: SlotWithValue[], span: number): number {
|
|
if (slots.length < span) return -1;
|
|
|
|
let lowestSum = Infinity;
|
|
let lowestIndex = -1;
|
|
|
|
for (let i = 0; i <= slots.length - span; i++) {
|
|
const sum = slots.slice(i, i + span).reduce((acc, slot) => acc + slot.value, 0);
|
|
if (sum < lowestSum) {
|
|
lowestSum = sum;
|
|
lowestIndex = i;
|
|
}
|
|
}
|
|
|
|
return lowestIndex;
|
|
}
|