addes cost and usage calculation
This commit is contained in:
@@ -0,0 +1,197 @@
|
||||
import type { MeterReading } from '../types';
|
||||
|
||||
// Konstante für Gas → kWh Umrechnung (Standard Erdgas H)
|
||||
export const GAS_TO_KWH_FACTOR = 10.5;
|
||||
|
||||
// Ergebnis der Verbrauchsberechnung
|
||||
export interface ConsumptionCalculation {
|
||||
type: 'exact' | 'projected' | 'insufficient' | 'none';
|
||||
consumptionM3?: number; // Nur bei Gas
|
||||
consumptionKwh: number;
|
||||
startReading?: MeterReading;
|
||||
endReading?: MeterReading;
|
||||
projectedEndDate?: string; // Nur bei Hochrechnung
|
||||
message?: string;
|
||||
}
|
||||
|
||||
// Ergebnis der Kostenberechnung
|
||||
export interface CostCalculation {
|
||||
annualBaseCost: number; // basePrice × 12
|
||||
annualConsumptionCost: number; // verbrauch × unitPrice
|
||||
annualTotalCost: number; // Summe
|
||||
monthlyPayment: number; // annualTotalCost / 12
|
||||
bonus?: number;
|
||||
effectiveAnnualCost: number; // annualTotalCost - bonus
|
||||
}
|
||||
|
||||
/**
|
||||
* Berechnet die Differenz in Tagen zwischen zwei Daten
|
||||
*/
|
||||
function daysDiff(startDate: string, endDate: string): number {
|
||||
const start = new Date(startDate);
|
||||
const end = new Date(endDate);
|
||||
start.setHours(0, 0, 0, 0);
|
||||
end.setHours(0, 0, 0, 0);
|
||||
const diffTime = end.getTime() - start.getTime();
|
||||
return Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
||||
}
|
||||
|
||||
/**
|
||||
* Filtert Zählerstände nach Vertragszeitraum
|
||||
* Ein Zählerstand gilt als "im Zeitraum" wenn:
|
||||
* readingDate >= startDate UND readingDate <= endDate
|
||||
*/
|
||||
export function filterReadingsByContractPeriod(
|
||||
readings: MeterReading[],
|
||||
startDate: string,
|
||||
endDate: string
|
||||
): MeterReading[] {
|
||||
const start = new Date(startDate);
|
||||
const end = new Date(endDate);
|
||||
start.setHours(0, 0, 0, 0);
|
||||
end.setHours(0, 0, 0, 0);
|
||||
|
||||
return readings.filter((reading) => {
|
||||
const readingDate = new Date(reading.readingDate);
|
||||
readingDate.setHours(0, 0, 0, 0);
|
||||
return readingDate >= start && readingDate <= end;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Berechnet den Verbrauch basierend auf den Zählerständen
|
||||
*
|
||||
* Fälle:
|
||||
* A) Anfangs- UND Endzählerstand vorhanden -> exakter Verbrauch
|
||||
* B) >= 2 Stände, kein Endzählerstand -> Hochrechnung
|
||||
* C) Nur 1 Stand -> insufficient
|
||||
* D) Keine Stände -> none
|
||||
*/
|
||||
export function calculateConsumption(
|
||||
readings: MeterReading[],
|
||||
startDate: string,
|
||||
endDate: string,
|
||||
contractType: 'ELECTRICITY' | 'GAS'
|
||||
): ConsumptionCalculation {
|
||||
const filteredReadings = filterReadingsByContractPeriod(readings, startDate, endDate);
|
||||
|
||||
// Fall D: Keine Zählerstände
|
||||
if (filteredReadings.length === 0) {
|
||||
return { type: 'none', consumptionKwh: 0 };
|
||||
}
|
||||
|
||||
// Fall C: Nur 1 Zählerstand
|
||||
if (filteredReadings.length === 1) {
|
||||
return {
|
||||
type: 'insufficient',
|
||||
consumptionKwh: 0,
|
||||
message: 'Berechnung auf Grund fehlender Stände nicht möglich',
|
||||
};
|
||||
}
|
||||
|
||||
// Sortieren nach Datum (älteste zuerst)
|
||||
const sorted = [...filteredReadings].sort(
|
||||
(a, b) => new Date(a.readingDate).getTime() - new Date(b.readingDate).getTime()
|
||||
);
|
||||
|
||||
const firstReading = sorted[0];
|
||||
const lastReading = sorted[sorted.length - 1];
|
||||
|
||||
// Prüfen ob Endzählerstand am/nach Vertragsende liegt
|
||||
const lastReadingDate = new Date(lastReading.readingDate);
|
||||
const contractEndDate = new Date(endDate);
|
||||
lastReadingDate.setHours(0, 0, 0, 0);
|
||||
contractEndDate.setHours(0, 0, 0, 0);
|
||||
|
||||
// Fall A: Exakter Verbrauch (Endzählerstand am oder nach Vertragsende)
|
||||
if (lastReadingDate >= contractEndDate) {
|
||||
const consumption = lastReading.value - firstReading.value;
|
||||
return formatConsumptionResult('exact', consumption, contractType, firstReading, lastReading);
|
||||
}
|
||||
|
||||
// Fall B: Hochrechnung erforderlich
|
||||
const daysBetweenReadings = daysDiff(firstReading.readingDate, lastReading.readingDate);
|
||||
|
||||
// Mindestens 1 Tag zwischen den Messungen für sinnvolle Hochrechnung
|
||||
if (daysBetweenReadings < 1) {
|
||||
return {
|
||||
type: 'insufficient',
|
||||
consumptionKwh: 0,
|
||||
message: 'Zeitraum zwischen Zählerständen zu kurz für Berechnung',
|
||||
};
|
||||
}
|
||||
|
||||
const totalContractDays = daysDiff(startDate, endDate);
|
||||
const measuredConsumption = lastReading.value - firstReading.value;
|
||||
const projectedConsumption = (measuredConsumption / daysBetweenReadings) * totalContractDays;
|
||||
|
||||
return formatConsumptionResult(
|
||||
'projected',
|
||||
projectedConsumption,
|
||||
contractType,
|
||||
firstReading,
|
||||
lastReading,
|
||||
endDate
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formatiert das Ergebnis der Verbrauchsberechnung
|
||||
*/
|
||||
function formatConsumptionResult(
|
||||
type: 'exact' | 'projected',
|
||||
consumption: number,
|
||||
contractType: 'ELECTRICITY' | 'GAS',
|
||||
startReading: MeterReading,
|
||||
endReading: MeterReading,
|
||||
projectedEndDate?: string
|
||||
): ConsumptionCalculation {
|
||||
if (contractType === 'GAS') {
|
||||
return {
|
||||
type,
|
||||
consumptionM3: consumption,
|
||||
consumptionKwh: consumption * GAS_TO_KWH_FACTOR,
|
||||
startReading,
|
||||
endReading,
|
||||
projectedEndDate,
|
||||
};
|
||||
}
|
||||
return {
|
||||
type,
|
||||
consumptionKwh: consumption,
|
||||
startReading,
|
||||
endReading,
|
||||
projectedEndDate,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Berechnet die Kosten basierend auf Verbrauch und Preisen
|
||||
* Gibt null zurück wenn keine Preise vorhanden sind
|
||||
*/
|
||||
export function calculateCosts(
|
||||
consumptionKwh: number,
|
||||
basePrice?: number,
|
||||
unitPrice?: number,
|
||||
bonus?: number
|
||||
): CostCalculation | null {
|
||||
// Mindestens ein Preis muss vorhanden sein
|
||||
if (basePrice == null && unitPrice == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const annualBaseCost = (basePrice ?? 0) * 12;
|
||||
const annualConsumptionCost = consumptionKwh * (unitPrice ?? 0);
|
||||
const annualTotalCost = annualBaseCost + annualConsumptionCost;
|
||||
const effectiveAnnualCost = annualTotalCost - (bonus ?? 0);
|
||||
const monthlyPayment = effectiveAnnualCost / 12;
|
||||
|
||||
return {
|
||||
annualBaseCost,
|
||||
annualConsumptionCost,
|
||||
annualTotalCost,
|
||||
monthlyPayment,
|
||||
bonus: bonus ?? undefined,
|
||||
effectiveAnnualCost,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user