102 lines
5.0 KiB
C#
102 lines
5.0 KiB
C#
using InnovEnergy.App.Backend.DataTypes;
|
|
|
|
namespace InnovEnergy.App.Backend.Services;
|
|
|
|
public static class BehaviorAnalyzer
|
|
{
|
|
private const double SolarActiveThresholdKwh = 0.1; // min PV kWh in an hour to count as "solar active"
|
|
private const double LowSoCThreshold = 20.0; // % below which battery is considered depleted
|
|
|
|
/// <summary>
|
|
/// Derives behavioral facts from hourly data for the current week only.
|
|
/// All computation is pure C# — no AI involved.
|
|
/// </summary>
|
|
public static BehavioralPattern Analyze(List<HourlyEnergyData> hourlyData)
|
|
{
|
|
if (hourlyData.Count == 0)
|
|
return new BehavioralPattern();
|
|
|
|
// ── Per-hour averages across the week ──────────────────────────────
|
|
// Group by hour-of-day (0-23), average each metric across all days
|
|
var byHour = Enumerable.Range(0, 24).Select(h =>
|
|
{
|
|
var rows = hourlyData.Where(r => r.Hour == h).ToList();
|
|
if (rows.Count == 0)
|
|
return (Hour: h, AvgPv: 0.0, AvgLoad: 0.0, AvgGridImport: 0.0);
|
|
return (
|
|
Hour: h,
|
|
AvgPv: rows.Average(r => r.PvKwh),
|
|
AvgLoad: rows.Average(r => r.LoadKwh),
|
|
AvgGridImport: rows.Average(r => r.GridImportKwh)
|
|
);
|
|
}).ToList();
|
|
|
|
// ── Peak load hour ─────────────────────────────────────────────────
|
|
var peakLoadEntry = byHour.OrderByDescending(h => h.AvgLoad).First();
|
|
|
|
// ── Peak solar hour and end of solar window ────────────────────────
|
|
var peakSolarEntry = byHour.OrderByDescending(h => h.AvgPv).First();
|
|
|
|
// Solar window: last hour in the day where avg PV > threshold
|
|
var solarActiveHours = byHour.Where(h => h.AvgPv >= SolarActiveThresholdKwh).ToList();
|
|
var peakSolarEndHour = solarActiveHours.Count > 0
|
|
? solarActiveHours.Max(h => h.Hour)
|
|
: peakSolarEntry.Hour;
|
|
|
|
// ── Highest grid-import hour ────────────────────────────────────────
|
|
var worstGridEntry = byHour.OrderByDescending(h => h.AvgGridImport).First();
|
|
|
|
// ── Avoidable grid imports: grid drawn during hours when solar was active ──
|
|
// For each actual hourly record: if solar > threshold AND grid import > 0 → avoidable
|
|
var avoidableGridKwh = Math.Round(
|
|
hourlyData
|
|
.Where(r => r.PvKwh >= SolarActiveThresholdKwh && r.GridImportKwh > 0)
|
|
.Sum(r => r.GridImportKwh),
|
|
1);
|
|
|
|
// ── Weekday vs weekend average daily load ──────────────────────────
|
|
var weekdayDays = hourlyData
|
|
.Where(r => !r.IsWeekend)
|
|
.GroupBy(r => r.DateTime.Date)
|
|
.Select(g => g.Sum(r => r.LoadKwh))
|
|
.ToList();
|
|
|
|
var weekendDays = hourlyData
|
|
.Where(r => r.IsWeekend)
|
|
.GroupBy(r => r.DateTime.Date)
|
|
.Select(g => g.Sum(r => r.LoadKwh))
|
|
.ToList();
|
|
|
|
var weekdayAvg = weekdayDays.Count > 0 ? Math.Round(weekdayDays.Average(), 1) : 0;
|
|
var weekendAvg = weekendDays.Count > 0 ? Math.Round(weekendDays.Average(), 1) : 0;
|
|
|
|
// ── Battery depletion hour ─────────────────────────────────────────
|
|
// For each day, find the first evening hour (after 18:00) where SoC < threshold
|
|
// Average that hour across days where it occurs
|
|
var depletionHours = hourlyData
|
|
.Where(r => r.Hour >= 18 && r.BattSoC > 0 && r.BattSoC < LowSoCThreshold)
|
|
.GroupBy(r => r.DateTime.Date)
|
|
.Select(g => g.OrderBy(r => r.Hour).First().Hour)
|
|
.ToList();
|
|
|
|
var avgDepletedHour = depletionHours.Count > 0 ? (int)Math.Round(depletionHours.Average()) : -1;
|
|
var batteryDepletsNight = depletionHours.Count >= 3; // happens on 3+ nights = consistent pattern
|
|
|
|
return new BehavioralPattern
|
|
{
|
|
PeakLoadHour = peakLoadEntry.Hour,
|
|
AvgPeakLoadKwh = Math.Round(peakLoadEntry.AvgLoad, 2),
|
|
PeakSolarHour = peakSolarEntry.Hour,
|
|
PeakSolarEndHour = peakSolarEndHour,
|
|
AvgPeakSolarKwh = Math.Round(peakSolarEntry.AvgPv, 2),
|
|
HighestGridImportHour = worstGridEntry.Hour,
|
|
AvgGridImportAtPeakHour = Math.Round(worstGridEntry.AvgGridImport, 2),
|
|
AvoidableGridKwh = avoidableGridKwh,
|
|
WeekdayAvgDailyLoad = weekdayAvg,
|
|
WeekendAvgDailyLoad = weekendAvg,
|
|
AvgBatteryDepletedHour = avgDepletedHour,
|
|
BatteryDepletesOvernight = batteryDepletsNight,
|
|
};
|
|
}
|
|
}
|