improved weekly performance report quality based on inesco team meeting feedback on 24.02
This commit is contained in:
parent
3bffe70a75
commit
36848b97c5
|
|
@ -206,8 +206,8 @@ public static class DiagnosticService
|
||||||
? string.Join(", ", recentErrors)
|
? string.Join(", ", recentErrors)
|
||||||
: "none";
|
: "none";
|
||||||
|
|
||||||
return $@"You are a technician for Innovenergy {productName} battery energy storage systems.
|
return $@"You are a technician for {productName} battery energy storage systems.
|
||||||
These are lithium-ion BESS units with a BMS, PV inverter, and grid inverter.
|
These are sodium-ion BESS units with a BMS, PV inverter, and grid inverter.
|
||||||
|
|
||||||
Error: {errorDescription}
|
Error: {errorDescription}
|
||||||
Other recent errors: {recentList}
|
Other recent errors: {recentList}
|
||||||
|
|
|
||||||
|
|
@ -99,20 +99,20 @@ public static class ReportEmailService
|
||||||
Consumption: "Verbrauch",
|
Consumption: "Verbrauch",
|
||||||
GridImport: "Netzbezug",
|
GridImport: "Netzbezug",
|
||||||
GridExport: "Netzeinspeisung",
|
GridExport: "Netzeinspeisung",
|
||||||
BatteryInOut: "Batterie Ein/Aus",
|
BatteryInOut: "Batterie Laden / Entladen",
|
||||||
SolarEnergyUsed: "Energie gespart",
|
SolarEnergyUsed: "Energie gespart",
|
||||||
StayedAtHome: "Solar + Batterie, nicht vom Netz",
|
StayedAtHome: "Solar + Batterie, nicht vom Netz",
|
||||||
EstMoneySaved: "Geschätzte Ersparnis",
|
EstMoneySaved: "Geschätzte Ersparnis",
|
||||||
AtRate: "bei 0.27 CHF/kWh",
|
AtRate: "bei 0.39 CHF/kWh",
|
||||||
SolarCoverage: "Eigenversorgung",
|
SolarCoverage: "Eigenversorgung",
|
||||||
FromSolar: "aus Solar + Batterie",
|
FromSolar: "aus Solar + Batterie",
|
||||||
BatteryEff: "Batterie-Eff.",
|
BatteryEff: "Batterie-Eff.",
|
||||||
OutVsIn: "Aus vs. Ein",
|
OutVsIn: "Entladung vs. Ladung",
|
||||||
Day: "Tag",
|
Day: "Tag",
|
||||||
Load: "Last",
|
Load: "Last",
|
||||||
GridIn: "Netz Ein",
|
GridIn: "Netz Ein",
|
||||||
GridOut: "Netz Aus",
|
GridOut: "Netz Aus",
|
||||||
BattInOut: "Batt. Ein/Aus",
|
BattInOut: "Batt. Laden/Entl.",
|
||||||
Footer: "Erstellt von <strong style=\"color:#666\">Inesco Energy Monitor Platform</strong> · Powered by Mistral AI"
|
Footer: "Erstellt von <strong style=\"color:#666\">Inesco Energy Monitor Platform</strong> · Powered by Mistral AI"
|
||||||
),
|
),
|
||||||
"fr" => new EmailStrings(
|
"fr" => new EmailStrings(
|
||||||
|
|
@ -129,20 +129,20 @@ public static class ReportEmailService
|
||||||
Consumption: "Consommation",
|
Consumption: "Consommation",
|
||||||
GridImport: "Import réseau",
|
GridImport: "Import réseau",
|
||||||
GridExport: "Export réseau",
|
GridExport: "Export réseau",
|
||||||
BatteryInOut: "Batterie Entrée/Sortie",
|
BatteryInOut: "Batterie Charge / Décharge",
|
||||||
SolarEnergyUsed: "Énergie économisée",
|
SolarEnergyUsed: "Énergie économisée",
|
||||||
StayedAtHome: "solaire + batterie, non achetée au réseau",
|
StayedAtHome: "solaire + batterie, non achetée au réseau",
|
||||||
EstMoneySaved: "Économies estimées",
|
EstMoneySaved: "Économies estimées",
|
||||||
AtRate: "à 0.27 CHF/kWh",
|
AtRate: "à 0.39 CHF/kWh",
|
||||||
SolarCoverage: "Autosuffisance",
|
SolarCoverage: "Autosuffisance",
|
||||||
FromSolar: "du solaire + batterie",
|
FromSolar: "du solaire + batterie",
|
||||||
BatteryEff: "Eff. batterie",
|
BatteryEff: "Eff. batterie",
|
||||||
OutVsIn: "sortie vs entrée",
|
OutVsIn: "décharge vs charge",
|
||||||
Day: "Jour",
|
Day: "Jour",
|
||||||
Load: "Charge",
|
Load: "Charge",
|
||||||
GridIn: "Réseau Ent.",
|
GridIn: "Réseau Ent.",
|
||||||
GridOut: "Réseau Sor.",
|
GridOut: "Réseau Sor.",
|
||||||
BattInOut: "Batt. Ent./Sor.",
|
BattInOut: "Batt. Ch./Déch.",
|
||||||
Footer: "Généré par <strong style=\"color:#666\">Inesco Energy Monitor Platform</strong> · Propulsé par Mistral AI"
|
Footer: "Généré par <strong style=\"color:#666\">Inesco Energy Monitor Platform</strong> · Propulsé par Mistral AI"
|
||||||
),
|
),
|
||||||
"it" => new EmailStrings(
|
"it" => new EmailStrings(
|
||||||
|
|
@ -159,20 +159,20 @@ public static class ReportEmailService
|
||||||
Consumption: "Consumo",
|
Consumption: "Consumo",
|
||||||
GridImport: "Import dalla rete",
|
GridImport: "Import dalla rete",
|
||||||
GridExport: "Export nella rete",
|
GridExport: "Export nella rete",
|
||||||
BatteryInOut: "Batteria Ent./Usc.",
|
BatteryInOut: "Batteria Carica / Scarica",
|
||||||
SolarEnergyUsed: "Energia risparmiata",
|
SolarEnergyUsed: "Energia risparmiata",
|
||||||
StayedAtHome: "solare + batteria, non acquistata dalla rete",
|
StayedAtHome: "solare + batteria, non acquistata dalla rete",
|
||||||
EstMoneySaved: "Risparmio stimato",
|
EstMoneySaved: "Risparmio stimato",
|
||||||
AtRate: "a 0.27 CHF/kWh",
|
AtRate: "a 0.39 CHF/kWh",
|
||||||
SolarCoverage: "Autosufficienza",
|
SolarCoverage: "Autosufficienza",
|
||||||
FromSolar: "da solare + batteria",
|
FromSolar: "da solare + batteria",
|
||||||
BatteryEff: "Eff. batteria",
|
BatteryEff: "Eff. batteria",
|
||||||
OutVsIn: "uscita vs entrata",
|
OutVsIn: "scarica vs carica",
|
||||||
Day: "Giorno",
|
Day: "Giorno",
|
||||||
Load: "Carico",
|
Load: "Carico",
|
||||||
GridIn: "Rete Ent.",
|
GridIn: "Rete Ent.",
|
||||||
GridOut: "Rete Usc.",
|
GridOut: "Rete Usc.",
|
||||||
BattInOut: "Batt. Ent./Usc.",
|
BattInOut: "Batt. Car./Sc.",
|
||||||
Footer: "Generato da <strong style=\"color:#666\">Inesco Energy Monitor Platform</strong> · Powered by Mistral AI"
|
Footer: "Generato da <strong style=\"color:#666\">Inesco Energy Monitor Platform</strong> · Powered by Mistral AI"
|
||||||
),
|
),
|
||||||
_ => new EmailStrings(
|
_ => new EmailStrings(
|
||||||
|
|
@ -189,20 +189,20 @@ public static class ReportEmailService
|
||||||
Consumption: "Consumption",
|
Consumption: "Consumption",
|
||||||
GridImport: "Grid Import",
|
GridImport: "Grid Import",
|
||||||
GridExport: "Grid Export",
|
GridExport: "Grid Export",
|
||||||
BatteryInOut: "Battery In/Out",
|
BatteryInOut: "Battery Charge / Discharge",
|
||||||
SolarEnergyUsed: "Energy Saved",
|
SolarEnergyUsed: "Energy Saved",
|
||||||
StayedAtHome: "solar + battery, not bought from grid",
|
StayedAtHome: "solar + battery, not bought from grid",
|
||||||
EstMoneySaved: "Est. Money Saved",
|
EstMoneySaved: "Est. Money Saved",
|
||||||
AtRate: "at 0.27 CHF/kWh",
|
AtRate: "at 0.39 CHF/kWh",
|
||||||
SolarCoverage: "Self-Sufficiency",
|
SolarCoverage: "Self-Sufficiency",
|
||||||
FromSolar: "from solar + battery",
|
FromSolar: "from solar + battery",
|
||||||
BatteryEff: "Battery Eff.",
|
BatteryEff: "Battery Eff.",
|
||||||
OutVsIn: "out vs in",
|
OutVsIn: "discharge vs charge",
|
||||||
Day: "Day",
|
Day: "Day",
|
||||||
Load: "Load",
|
Load: "Load",
|
||||||
GridIn: "Grid In",
|
GridIn: "Grid In",
|
||||||
GridOut: "Grid Out",
|
GridOut: "Grid Out",
|
||||||
BattInOut: "Batt In/Out",
|
BattInOut: "Batt. Ch./Dis.",
|
||||||
Footer: "Generated by <strong style=\"color:#666\">Inesco Energy Monitor Platform</strong> · Powered by Mistral AI"
|
Footer: "Generated by <strong style=\"color:#666\">Inesco Energy Monitor Platform</strong> · Powered by Mistral AI"
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
@ -230,21 +230,44 @@ public static class ReportEmailService
|
||||||
"</ul>"
|
"</ul>"
|
||||||
: $"<p style=\"margin:0;line-height:1.6\">{FormatInsightLine(r.AiInsight)}</p>";
|
: $"<p style=\"margin:0;line-height:1.6\">{FormatInsightLine(r.AiInsight)}</p>";
|
||||||
|
|
||||||
// Daily rows
|
// Detect which components are present across all daily data
|
||||||
|
var showPv = r.DailyData.Any(d => d.PvProduction > 0.1);
|
||||||
|
var showGrid = r.DailyData.Any(d => d.GridImport > 0.1);
|
||||||
|
|
||||||
|
// Daily rows — colorful bar chart (pixel widths, email-safe)
|
||||||
|
// Scale each day's bars so their combined total always fills maxBarPx (right-edge aligned).
|
||||||
|
// This replicates the web page's CSS flexbox flex-shrink:1 behaviour.
|
||||||
|
const int maxBarPx = 400;
|
||||||
|
|
||||||
var dailyRows = "";
|
var dailyRows = "";
|
||||||
foreach (var d in r.DailyData)
|
foreach (var d in r.DailyData)
|
||||||
{
|
{
|
||||||
var dayName = DateTime.Parse(d.Date).ToString("ddd");
|
var dayName = DateTime.Parse(d.Date).ToString("ddd dd.MM");
|
||||||
var isCurrentWeek = string.Compare(d.Date, r.PeriodStart, StringComparison.Ordinal) >= 0;
|
var isCurrentWeek = string.Compare(d.Date, r.PeriodStart, StringComparison.Ordinal) >= 0;
|
||||||
var bgColor = isCurrentWeek ? "#ffffff" : "#f9f9f9";
|
var opacity = isCurrentWeek ? "1" : "0.55";
|
||||||
|
var fontWeight = isCurrentWeek ? "bold" : "normal";
|
||||||
|
var dayTotal = (showPv ? d.PvProduction : 0) + d.LoadConsumption + (showGrid ? d.GridImport : 0);
|
||||||
|
if (dayTotal < 0.1) dayTotal = 0.1;
|
||||||
|
var pvPx = showPv ? (int)(d.PvProduction / dayTotal * maxBarPx) : 0;
|
||||||
|
var ldPx = (int)(d.LoadConsumption / dayTotal * maxBarPx);
|
||||||
|
var giPx = showGrid ? (int)(d.GridImport / dayTotal * maxBarPx) : 0;
|
||||||
|
|
||||||
|
var pvSpan = showPv ? $@"<span style=""display:inline-block;height:14px;background:#f39c12;width:{pvPx}px;border-radius:2px 0 0 2px""></span>" : "";
|
||||||
|
var gridSpan = showGrid ? $@"<span style=""display:inline-block;height:14px;background:#e74c3c;width:{giPx}px;border-radius:0 2px 2px 0;margin-left:2px""></span>" : "";
|
||||||
|
var ldRadius = (!showPv ? "border-radius:2px 0 0 2px;" : "") + (!showGrid ? "border-radius:0 2px 2px 0;" : "");
|
||||||
|
|
||||||
|
var valueText = (showPv ? $"PV {d.PvProduction:F1} | " : "")
|
||||||
|
+ $"{s.Load} {d.LoadConsumption:F1}"
|
||||||
|
+ (showGrid ? $" | {s.GridIn} {d.GridImport:F1}" : "")
|
||||||
|
+ " kWh";
|
||||||
|
|
||||||
dailyRows += $@"
|
dailyRows += $@"
|
||||||
<tr style=""background:{bgColor}"">
|
<tr style=""opacity:{opacity};border-bottom:1px solid #f0f0f0"">
|
||||||
<td style=""padding:6px 10px;border-bottom:1px solid #eee"">{dayName} {d.Date}</td>
|
<td style=""padding:6px 8px;font-size:12px;font-weight:{fontWeight};white-space:nowrap;width:80px;vertical-align:top;padding-top:10px"">{dayName}</td>
|
||||||
<td style=""padding:6px 10px;border-bottom:1px solid #eee;text-align:right"">{d.PvProduction:F1}</td>
|
<td style=""padding:4px 8px"">
|
||||||
<td style=""padding:6px 10px;border-bottom:1px solid #eee;text-align:right"">{d.LoadConsumption:F1}</td>
|
<div style=""font-size:10px;color:#888;margin-bottom:3px;text-align:right"">{valueText}</div>
|
||||||
<td style=""padding:6px 10px;border-bottom:1px solid #eee;text-align:right"">{d.GridImport:F1}</td>
|
<div style=""height:14px;line-height:14px;font-size:0;white-space:nowrap;width:{maxBarPx}px"">{pvSpan}<span style=""display:inline-block;height:14px;background:#3498db;width:{ldPx}px;{ldRadius}margin-left:{(showPv ? 2 : 0)}px""></span>{gridSpan}</div>
|
||||||
<td style=""padding:6px 10px;border-bottom:1px solid #eee;text-align:right"">{d.GridExport:F1}</td>
|
</td>
|
||||||
<td style=""padding:6px 10px;border-bottom:1px solid #eee;text-align:right"">{d.BatteryCharged:F1}/{d.BatteryDischarged:F1}</td>
|
|
||||||
</tr>";
|
</tr>";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -351,18 +374,18 @@ public static class ReportEmailService
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<!-- Daily Breakdown -->
|
<!-- Daily Breakdown (bar chart) -->
|
||||||
<tr>
|
<tr>
|
||||||
<td style=""padding:0 30px 24px"">
|
<td style=""padding:0 30px 24px"">
|
||||||
<div style=""font-size:16px;font-weight:bold;margin-bottom:12px;color:#2c3e50"">{s.DailyBreakdown}</div>
|
<div style=""font-size:16px;font-weight:bold;margin-bottom:8px;color:#2c3e50"">{s.DailyBreakdown}</div>
|
||||||
<table width=""100%"" cellpadding=""0"" cellspacing=""0"" style=""border:1px solid #eee;border-radius:4px;font-size:13px"">
|
<table width=""100%"" cellpadding=""0"" cellspacing=""0"" style=""border:1px solid #eee;border-radius:4px;font-size:13px"">
|
||||||
|
<!-- Legend -->
|
||||||
<tr style=""background:#f8f9fa"">
|
<tr style=""background:#f8f9fa"">
|
||||||
<th style=""padding:6px 10px;text-align:left"">{s.Day}</th>
|
<td colspan=""2"" style=""padding:8px 10px;font-size:12px"">
|
||||||
<th style=""padding:6px 10px;text-align:right"">PV</th>
|
{(showPv ? @$"<span style=""display:inline-block;width:10px;height:10px;background:#f39c12;border-radius:2px;margin-right:4px""></span>PV " : "")}
|
||||||
<th style=""padding:6px 10px;text-align:right"">{s.Load}</th>
|
<span style=""display:inline-block;width:10px;height:10px;background:#3498db;border-radius:2px;margin-right:4px""></span>{s.Load}
|
||||||
<th style=""padding:6px 10px;text-align:right"">{s.GridIn}</th>
|
{(showGrid ? @$"<span style=""display:inline-block;width:10px;height:10px;background:#e74c3c;border-radius:2px;margin-right:4px""></span>{s.GridIn}" : "")}
|
||||||
<th style=""padding:6px 10px;text-align:right"">{s.GridOut}</th>
|
</td>
|
||||||
<th style=""padding:6px 10px;text-align:right"">{s.BattInOut}</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
{dailyRows}
|
{dailyRows}
|
||||||
</table>
|
</table>
|
||||||
|
|
|
||||||
|
|
@ -120,7 +120,7 @@ public static class WeeklyReportService
|
||||||
var behavior = BehaviorAnalyzer.Analyze(currentHourlyData);
|
var behavior = BehaviorAnalyzer.Analyze(currentHourlyData);
|
||||||
|
|
||||||
// Pre-computed savings — single source of truth for UI and AI
|
// Pre-computed savings — single source of truth for UI and AI
|
||||||
const double ElectricityPriceCHF = 0.27;
|
const double ElectricityPriceCHF = 0.39;
|
||||||
var totalEnergySaved = Math.Round(currentSummary.TotalConsumption - currentSummary.TotalGridImport, 1);
|
var totalEnergySaved = Math.Round(currentSummary.TotalConsumption - currentSummary.TotalGridImport, 1);
|
||||||
var totalSavingsCHF = Math.Round(totalEnergySaved * ElectricityPriceCHF, 0);
|
var totalSavingsCHF = Math.Round(totalEnergySaved * ElectricityPriceCHF, 0);
|
||||||
var avgDailyConsumption = currentWeekDays.Count > 0 ? currentSummary.TotalConsumption / currentWeekDays.Count : 0;
|
var avgDailyConsumption = currentWeekDays.Count > 0 ? currentSummary.TotalConsumption / currentWeekDays.Count : 0;
|
||||||
|
|
@ -183,7 +183,8 @@ public static class WeeklyReportService
|
||||||
_ => "English"
|
_ => "English"
|
||||||
};
|
};
|
||||||
|
|
||||||
private static string FormatHour(int hour) => $"{hour:D2}:00";
|
private static string FormatHour(int hour) => $"{hour:D2}:00";
|
||||||
|
private static string FormatHourSlot(int hour) => $"{hour:D2}:00–{hour + 1:D2}:00";
|
||||||
|
|
||||||
private static async Task<string> GetAiInsightAsync(
|
private static async Task<string> GetAiInsightAsync(
|
||||||
List<DailyEnergyData> currentWeek,
|
List<DailyEnergyData> currentWeek,
|
||||||
|
|
@ -203,7 +204,13 @@ public static class WeeklyReportService
|
||||||
return "AI insight unavailable (API key not configured).";
|
return "AI insight unavailable (API key not configured).";
|
||||||
}
|
}
|
||||||
|
|
||||||
const double ElectricityPriceCHF = 0.27;
|
const double ElectricityPriceCHF = 0.39;
|
||||||
|
|
||||||
|
// Detect which components are present
|
||||||
|
var hasPv = currentWeek.Sum(d => d.PvProduction) > 0.5;
|
||||||
|
var hasBattery = currentWeek.Sum(d => d.BatteryCharged) > 0.5
|
||||||
|
|| currentWeek.Sum(d => d.BatteryDischarged) > 0.5;
|
||||||
|
var hasGrid = currentWeek.Sum(d => d.GridImport) > 0.5;
|
||||||
|
|
||||||
var bestDay = currentWeek.OrderByDescending(d => d.PvProduction).First();
|
var bestDay = currentWeek.OrderByDescending(d => d.PvProduction).First();
|
||||||
var worstDay = currentWeek.OrderBy(d => d.PvProduction).First();
|
var worstDay = currentWeek.OrderBy(d => d.PvProduction).First();
|
||||||
|
|
@ -214,16 +221,59 @@ public static class WeeklyReportService
|
||||||
var topBattDayName = DateTime.Parse(topBattDay.Date).ToString("dddd");
|
var topBattDayName = DateTime.Parse(topBattDay.Date).ToString("dddd");
|
||||||
|
|
||||||
// Behavioral facts as compact lines
|
// Behavioral facts as compact lines
|
||||||
var peakSolarWindow = FormatHour(behavior.PeakSolarHour) + "–" + FormatHour(behavior.PeakSolarEndHour);
|
var peakSolarWindow = FormatHour(behavior.PeakSolarHour) + "–" + FormatHour(behavior.PeakSolarEndHour);
|
||||||
var avoidableSavingsCHF = Math.Round(behavior.AvoidableGridKwh * ElectricityPriceCHF, 0);
|
var avoidableSavingsCHF = Math.Round(behavior.AvoidableGridKwh * ElectricityPriceCHF, 0);
|
||||||
var battDepleteLine = behavior.AvgBatteryDepletedHour >= 0
|
|
||||||
? $"Battery typically depletes below 20% around {FormatHour(behavior.AvgBatteryDepletedHour)}."
|
var battDepleteLine = hasBattery
|
||||||
: "Battery SoC data not available.";
|
? (behavior.AvgBatteryDepletedHour >= 0
|
||||||
|
? $"Battery typically depletes below 20% during {FormatHourSlot(behavior.AvgBatteryDepletedHour)}."
|
||||||
|
: "Battery stayed above 20% SoC every night this week.")
|
||||||
|
: "";
|
||||||
|
|
||||||
var weekdayWeekendLine = behavior.WeekendAvgDailyLoad > 0
|
var weekdayWeekendLine = behavior.WeekendAvgDailyLoad > 0
|
||||||
? $"Weekday avg load: {behavior.WeekdayAvgDailyLoad} kWh/day. Weekend avg: {behavior.WeekendAvgDailyLoad} kWh/day."
|
? $"Weekday avg load: {behavior.WeekdayAvgDailyLoad} kWh/day. Weekend avg: {behavior.WeekendAvgDailyLoad} kWh/day."
|
||||||
: $"Weekday avg load: {behavior.WeekdayAvgDailyLoad} kWh/day.";
|
: $"Weekday avg load: {behavior.WeekdayAvgDailyLoad} kWh/day.";
|
||||||
|
|
||||||
|
// Build conditional fact lines
|
||||||
|
var pvDailyFact = hasPv
|
||||||
|
? $"- PV: total {current.TotalPvProduction:F1} kWh this week. Best day: {bestDayName} ({bestDay.PvProduction:F1} kWh), worst: {worstDayName} ({worstDay.PvProduction:F1} kWh). Solar covered {selfSufficiency}% of consumption."
|
||||||
|
: "";
|
||||||
|
var battDailyFact = hasBattery
|
||||||
|
? $"- Battery: {current.TotalBatteryCharged:F1} kWh charged, {current.TotalBatteryDischarged:F1} kWh discharged. Most active day: {topBattDayName} ({topBattDay.BatteryCharged:F1} kWh charged)."
|
||||||
|
: "";
|
||||||
|
var gridDailyFact = hasGrid
|
||||||
|
? $"- Grid import: {current.TotalGridImport:F1} kWh total this week."
|
||||||
|
: "";
|
||||||
|
|
||||||
|
var pvBehaviorLines = hasPv ? $@"
|
||||||
|
- Solar active window: {peakSolarWindow}; peak hour: {FormatHourSlot(behavior.PeakSolarHour)}, avg {behavior.AvgPeakSolarKwh} kWh during that hour
|
||||||
|
- Grid imported while solar was active: {behavior.AvoidableGridKwh} kWh = {avoidableSavingsCHF} CHF that could have been avoided" : "";
|
||||||
|
|
||||||
|
var gridBehaviorLine = hasGrid
|
||||||
|
? $"- Highest grid-import hour: {FormatHourSlot(behavior.HighestGridImportHour)}, avg {behavior.AvgGridImportAtPeakHour} kWh during that hour"
|
||||||
|
: "";
|
||||||
|
|
||||||
|
var battBehaviorLine = !string.IsNullOrEmpty(battDepleteLine) ? $"- {battDepleteLine}" : "";
|
||||||
|
|
||||||
|
// Build conditional instructions
|
||||||
|
var instruction1 = $"1. Energy savings: Write 1–2 sentences. Say that this week, thanks to SodistoreHome, the customer avoided buying {totalEnergySaved} kWh from the grid, saving {totalSavingsCHF} CHF (at {ElectricityPriceCHF} CHF/kWh). Use these exact numbers — do not recalculate or change them.";
|
||||||
|
|
||||||
|
var instruction2 = hasPv
|
||||||
|
? $"2. Solar performance: Comment on the best and worst solar day this week and the likely weather reason."
|
||||||
|
: hasGrid
|
||||||
|
? $"2. Grid usage: Comment on the {current.TotalGridImport:F1} kWh drawn from the grid this week and what time of day drives it most ({FormatHourSlot(behavior.HighestGridImportHour)})."
|
||||||
|
: "2. Consumption pattern: Comment on the weekday vs weekend load pattern.";
|
||||||
|
|
||||||
|
var instruction3 = hasBattery
|
||||||
|
? $"3. Battery performance: Use the daily facts. Keep it simple for a homeowner."
|
||||||
|
: "3. Consumption pattern: Comment on the peak load time and weekday vs weekend usage.";
|
||||||
|
|
||||||
|
var instruction4 = hasPv
|
||||||
|
? $"4. Smart action for next week: Write exactly 2 sentences. Sentence 1: point out the timing mismatch using exact numbers — peak household load is during {FormatHourSlot(behavior.PeakLoadHour)} ({behavior.AvgPeakLoadKwh} kWh) but solar peaks during {FormatHourSlot(behavior.PeakSolarHour)} ({behavior.AvgPeakSolarKwh} kWh), with solar active from {peakSolarWindow}. Sentence 2: suggest shifting energy-intensive appliances (such as washing machine, dishwasher, heat pump, or EV charger if applicable) to run during the solar window {peakSolarWindow} — do not assume which specific device the customer has."
|
||||||
|
: hasGrid
|
||||||
|
? $"4. Smart action for next week: Write exactly 2 sentences. Sentence 1: state that the peak grid-import hour is {FormatHourSlot(behavior.HighestGridImportHour)} ({behavior.AvgGridImportAtPeakHour} kWh avg). Sentence 2: suggest one action to reduce grid use during that hour — shifting energy-intensive appliances (washing machine, dishwasher, heat pump, EV charger) away from that time."
|
||||||
|
: "4. Smart action for next week: Give one practical tip to reduce energy consumption based on the peak load time and weekday/weekend pattern.";
|
||||||
|
|
||||||
var prompt = $@"You are an energy advisor for a SodistoreHome installation: ""{installationName}"".
|
var prompt = $@"You are an energy advisor for a SodistoreHome installation: ""{installationName}"".
|
||||||
|
|
||||||
Write 4 bullet points (each on its own line starting with ""- ""). No bold markers, no asterisks, no markdown — plain text only.
|
Write 4 bullet points (each on its own line starting with ""- ""). No bold markers, no asterisks, no markdown — plain text only.
|
||||||
|
|
@ -232,26 +282,27 @@ IMPORTANT FORMAT RULE: Each bullet MUST start with a short title followed by a c
|
||||||
|
|
||||||
CRITICAL: All numbers below are pre-calculated. Use these values as-is — do not recalculate, round differently, or change any number.
|
CRITICAL: All numbers below are pre-calculated. Use these values as-is — do not recalculate, round differently, or change any number.
|
||||||
|
|
||||||
|
SYSTEM COMPONENTS: PV={hasPv}, Battery={hasBattery}, Grid={hasGrid}
|
||||||
|
|
||||||
DAILY FACTS:
|
DAILY FACTS:
|
||||||
- Total energy saved: {totalEnergySaved} kWh (solar + battery), saving {totalSavingsCHF} CHF at {ElectricityPriceCHF} CHF/kWh. Self-sufficient {selfSufficiency}% of the time.
|
- Total consumption: {current.TotalConsumption:F1} kWh this week. Self-sufficiency: {selfSufficiency}%.
|
||||||
- Best solar day: {bestDayName} with {bestDay.PvProduction:F1} kWh. Worst: {worstDayName} with {worstDay.PvProduction:F1} kWh.
|
{pvDailyFact}
|
||||||
- Battery: {current.TotalBatteryCharged:F1} kWh charged, {current.TotalBatteryDischarged:F1} kWh discharged. Most active day: {topBattDayName} ({topBattDay.BatteryCharged:F1} kWh charged).
|
{battDailyFact}
|
||||||
|
{gridDailyFact}
|
||||||
|
|
||||||
BEHAVIORAL PATTERN (from hourly data this week):
|
BEHAVIORAL PATTERN (from hourly data this week):
|
||||||
- Peak household load: {FormatHour(behavior.PeakLoadHour)} avg {behavior.AvgPeakLoadKwh} kWh/hr
|
- Peak household load: {FormatHourSlot(behavior.PeakLoadHour)}, avg {behavior.AvgPeakLoadKwh} kWh during that hour
|
||||||
- Peak solar window: {peakSolarWindow}, avg {behavior.AvgPeakSolarKwh} kWh/hr
|
- {weekdayWeekendLine}{pvBehaviorLines}
|
||||||
- Grid imported while solar was active this week: {behavior.AvoidableGridKwh} kWh total = {avoidableSavingsCHF} CHF that could have been avoided
|
{gridBehaviorLine}
|
||||||
- Highest single grid-import hour: {FormatHour(behavior.HighestGridImportHour)}, avg {behavior.AvgGridImportAtPeakHour} kWh/hr
|
{battBehaviorLine}
|
||||||
- {weekdayWeekendLine}
|
|
||||||
- {battDepleteLine}
|
|
||||||
|
|
||||||
INSTRUCTIONS:
|
INSTRUCTIONS:
|
||||||
1. Energy savings: Use the daily facts. State {totalEnergySaved} kWh and {totalSavingsCHF} CHF. Use these exact numbers — do not recalculate or substitute any of them.
|
{instruction1}
|
||||||
2. Best vs worst solar day: Use the daily facts. Mention likely weather reason.
|
{instruction2}
|
||||||
3. Battery performance: Use the daily facts. Keep it simple for a homeowner.
|
{instruction3}
|
||||||
4. Smart action for next week: Write exactly 2 sentences. Sentence 1: state the pattern using these exact numbers — peak load at {FormatHour(behavior.PeakLoadHour)} ({behavior.AvgPeakLoadKwh} kWh/hr) vs solar peak at {peakSolarWindow} ({behavior.AvgPeakSolarKwh} kWh/hr), and that {behavior.AvoidableGridKwh} kWh ({avoidableSavingsCHF} CHF) was drawn from the grid while solar was active. Sentence 2: give ONE concrete action (what appliance to shift, to which hours) and state it would recover the {avoidableSavingsCHF} CHF. Use all these exact numbers — do not substitute or omit any.
|
{instruction4}
|
||||||
|
|
||||||
Rules: Write for a homeowner, not an engineer. Do NOT use asterisks or any formatting marks.
|
Rules: Write for a homeowner, not an engineer. Do NOT use asterisks or any formatting marks. Only describe components that exist (PV={hasPv}, Battery={hasBattery}, Grid={hasGrid}). Do NOT add any closing remark, summary sentence, or motivational phrase after the 4 bullet points (e.g. no 'Das ist ein guter Fortschritt', 'Keep it up', 'Good progress', etc.). Write exactly 4 bullet points — nothing before, nothing after.
|
||||||
IMPORTANT: Write your entire response in {LanguageName(language)}.";
|
IMPORTANT: Write your entire response in {LanguageName(language)}.";
|
||||||
|
|
||||||
try
|
try
|
||||||
|
|
|
||||||
|
|
@ -342,10 +342,10 @@ function WeeklyReport({ installationId }: WeeklyReportProps) {
|
||||||
<FormattedMessage id="avgDailyConsumption" defaultMessage="Avg Daily Consumption" />
|
<FormattedMessage id="avgDailyConsumption" defaultMessage="Avg Daily Consumption" />
|
||||||
</td>
|
</td>
|
||||||
<td style={{ textAlign: 'right', color: '#888', fontSize: '13px' }}>
|
<td style={{ textAlign: 'right', color: '#888', fontSize: '13px' }}>
|
||||||
{(cur.totalConsumption / currentWeekDayCount).toFixed(1)} kWh/day
|
{(cur.totalConsumption / currentWeekDayCount).toFixed(1)} kWh
|
||||||
</td>
|
</td>
|
||||||
{prev && <td style={{ textAlign: 'right', color: '#bbb', fontSize: '13px' }}>
|
{prev && <td style={{ textAlign: 'right', color: '#bbb', fontSize: '13px' }}>
|
||||||
{(prev.totalConsumption / previousWeekDayCount).toFixed(1)} kWh/day
|
{(prev.totalConsumption / previousWeekDayCount).toFixed(1)} kWh
|
||||||
</td>}
|
</td>}
|
||||||
{prev && <td />}
|
{prev && <td />}
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
||||||
|
|
@ -102,12 +102,12 @@
|
||||||
"solarStayedHome": "Solar + Batterie, nicht vom Netz",
|
"solarStayedHome": "Solar + Batterie, nicht vom Netz",
|
||||||
"daysOfYourUsage": "Tage Ihres Verbrauchs",
|
"daysOfYourUsage": "Tage Ihres Verbrauchs",
|
||||||
"estMoneySaved": "Geschätzte Ersparnisse",
|
"estMoneySaved": "Geschätzte Ersparnisse",
|
||||||
"atCHFRate": "bei 0,27 CHF/kWh Ø",
|
"atCHFRate": "bei 0,39 CHF/kWh Ø",
|
||||||
"solarCoverage": "Eigenversorgung",
|
"solarCoverage": "Eigenversorgung",
|
||||||
"fromSolarSub": "aus Solar + Batterie",
|
"fromSolarSub": "aus Solar + Batterie",
|
||||||
"avgDailyConsumption": "Ø Tagesverbrauch",
|
"avgDailyConsumption": "Ø Tagesverbrauch",
|
||||||
"batteryEfficiency": "Batterieeffizienz",
|
"batteryEfficiency": "Batterieeffizienz",
|
||||||
"batteryEffSub": "Energie aus vs. Energie ein",
|
"batteryEffSub": "Entladung vs. Ladung",
|
||||||
"weeklySummary": "Wöchentliche Zusammenfassung",
|
"weeklySummary": "Wöchentliche Zusammenfassung",
|
||||||
"metric": "Kennzahl",
|
"metric": "Kennzahl",
|
||||||
"thisWeek": "Diese Woche",
|
"thisWeek": "Diese Woche",
|
||||||
|
|
@ -116,7 +116,7 @@
|
||||||
"consumption": "Verbrauch",
|
"consumption": "Verbrauch",
|
||||||
"gridImport": "Netzbezug",
|
"gridImport": "Netzbezug",
|
||||||
"gridExport": "Netzeinspeisung",
|
"gridExport": "Netzeinspeisung",
|
||||||
"batteryInOut": "Batterie Ein / Aus",
|
"batteryInOut": "Batterie Laden / Entladen",
|
||||||
"dailyBreakdown": "Tägliche Aufschlüsselung",
|
"dailyBreakdown": "Tägliche Aufschlüsselung",
|
||||||
"prevWeek": "(Vorwoche)",
|
"prevWeek": "(Vorwoche)",
|
||||||
"sendReport": "Bericht senden",
|
"sendReport": "Bericht senden",
|
||||||
|
|
@ -373,5 +373,10 @@
|
||||||
"alarm_AFCIFault": "Lichtbogenfehler",
|
"alarm_AFCIFault": "Lichtbogenfehler",
|
||||||
"alarm_GFCIHigh": "Erhöhter Fehlerstrom",
|
"alarm_GFCIHigh": "Erhöhter Fehlerstrom",
|
||||||
"alarm_PVVoltageHigh": "PV-Spannung zu hoch",
|
"alarm_PVVoltageHigh": "PV-Spannung zu hoch",
|
||||||
"alarm_OffGridBusVoltageTooLow": "Off-Grid-Busspannung zu niedrig"
|
"alarm_OffGridBusVoltageTooLow": "Off-Grid-Busspannung zu niedrig",
|
||||||
}
|
"Information": "Informationen",
|
||||||
|
"allInstallations": "Alle Installationen",
|
||||||
|
"group": "Gruppe",
|
||||||
|
"groups": "Gruppen",
|
||||||
|
"requiredOrderNumber": "Pflichtbestellnummer"
|
||||||
|
}
|
||||||
|
|
@ -84,12 +84,12 @@
|
||||||
"solarStayedHome": "solar + battery, not bought from grid",
|
"solarStayedHome": "solar + battery, not bought from grid",
|
||||||
"daysOfYourUsage": "days of your usage",
|
"daysOfYourUsage": "days of your usage",
|
||||||
"estMoneySaved": "Est. Money Saved",
|
"estMoneySaved": "Est. Money Saved",
|
||||||
"atCHFRate": "at 0.27 CHF/kWh avg.",
|
"atCHFRate": "at 0.39 CHF/kWh avg.",
|
||||||
"solarCoverage": "Self-Sufficiency",
|
"solarCoverage": "Self-Sufficiency",
|
||||||
"fromSolarSub": "from solar + battery",
|
"fromSolarSub": "from solar + battery",
|
||||||
"avgDailyConsumption": "Avg Daily Consumption",
|
"avgDailyConsumption": "Avg Daily Consumption",
|
||||||
"batteryEfficiency": "Battery Efficiency",
|
"batteryEfficiency": "Battery Efficiency",
|
||||||
"batteryEffSub": "energy out vs energy in",
|
"batteryEffSub": "discharge vs charge",
|
||||||
"weeklySummary": "Weekly Summary",
|
"weeklySummary": "Weekly Summary",
|
||||||
"metric": "Metric",
|
"metric": "Metric",
|
||||||
"thisWeek": "This Week",
|
"thisWeek": "This Week",
|
||||||
|
|
@ -98,7 +98,7 @@
|
||||||
"consumption": "Consumption",
|
"consumption": "Consumption",
|
||||||
"gridImport": "Grid Import",
|
"gridImport": "Grid Import",
|
||||||
"gridExport": "Grid Export",
|
"gridExport": "Grid Export",
|
||||||
"batteryInOut": "Battery In / Out",
|
"batteryInOut": "Battery Charge / Discharge",
|
||||||
"dailyBreakdown": "Daily Breakdown",
|
"dailyBreakdown": "Daily Breakdown",
|
||||||
"prevWeek": "(prev week)",
|
"prevWeek": "(prev week)",
|
||||||
"sendReport": "Send Report",
|
"sendReport": "Send Report",
|
||||||
|
|
|
||||||
|
|
@ -96,12 +96,12 @@
|
||||||
"solarStayedHome": "solaire + batterie, non achetée au réseau",
|
"solarStayedHome": "solaire + batterie, non achetée au réseau",
|
||||||
"daysOfYourUsage": "jours de votre consommation",
|
"daysOfYourUsage": "jours de votre consommation",
|
||||||
"estMoneySaved": "Économies estimées",
|
"estMoneySaved": "Économies estimées",
|
||||||
"atCHFRate": "à 0,27 CHF/kWh moy.",
|
"atCHFRate": "à 0,39 CHF/kWh moy.",
|
||||||
"solarCoverage": "Autosuffisance",
|
"solarCoverage": "Autosuffisance",
|
||||||
"fromSolarSub": "du solaire + batterie",
|
"fromSolarSub": "du solaire + batterie",
|
||||||
"avgDailyConsumption": "Conso. quotidienne moy.",
|
"avgDailyConsumption": "Conso. quotidienne moy.",
|
||||||
"batteryEfficiency": "Efficacité de la batterie",
|
"batteryEfficiency": "Efficacité de la batterie",
|
||||||
"batteryEffSub": "énergie sortante vs énergie entrante",
|
"batteryEffSub": "décharge vs charge",
|
||||||
"weeklySummary": "Résumé hebdomadaire",
|
"weeklySummary": "Résumé hebdomadaire",
|
||||||
"metric": "Métrique",
|
"metric": "Métrique",
|
||||||
"thisWeek": "Cette semaine",
|
"thisWeek": "Cette semaine",
|
||||||
|
|
@ -110,7 +110,7 @@
|
||||||
"consumption": "Consommation",
|
"consumption": "Consommation",
|
||||||
"gridImport": "Importation réseau",
|
"gridImport": "Importation réseau",
|
||||||
"gridExport": "Exportation réseau",
|
"gridExport": "Exportation réseau",
|
||||||
"batteryInOut": "Batterie Entrée / Sortie",
|
"batteryInOut": "Batterie Charge / Décharge",
|
||||||
"dailyBreakdown": "Répartition quotidienne",
|
"dailyBreakdown": "Répartition quotidienne",
|
||||||
"prevWeek": "(semaine précédente)",
|
"prevWeek": "(semaine précédente)",
|
||||||
"sendReport": "Envoyer le rapport",
|
"sendReport": "Envoyer le rapport",
|
||||||
|
|
@ -367,5 +367,16 @@
|
||||||
"alarm_AFCIFault": "Défaillance AFCI",
|
"alarm_AFCIFault": "Défaillance AFCI",
|
||||||
"alarm_GFCIHigh": "Courant de défaut élevé",
|
"alarm_GFCIHigh": "Courant de défaut élevé",
|
||||||
"alarm_PVVoltageHigh": "Tension PV élevée",
|
"alarm_PVVoltageHigh": "Tension PV élevée",
|
||||||
"alarm_OffGridBusVoltageTooLow": "Tension du bus hors réseau trop faible"
|
"alarm_OffGridBusVoltageTooLow": "Tension du bus hors réseau trop faible",
|
||||||
}
|
"Information": "Informations",
|
||||||
|
"allInstallations": "Toutes les installations",
|
||||||
|
"group": "Groupe",
|
||||||
|
"groups": "Groupes",
|
||||||
|
"requiredOrderNumber": "Numéro de commande requis",
|
||||||
|
"addNewChild": "Ajouter un sous-élément",
|
||||||
|
"addNewDialogButton": "Ajouter un bouton de dialogue",
|
||||||
|
"groupTabs": "Groupes",
|
||||||
|
"groupTree": "Arborescence de groupes",
|
||||||
|
"installationTabs": "Installations",
|
||||||
|
"navigationTabs": "Navigation"
|
||||||
|
}
|
||||||
|
|
@ -107,12 +107,12 @@
|
||||||
"solarStayedHome": "solare + batteria, non acquistata dalla rete",
|
"solarStayedHome": "solare + batteria, non acquistata dalla rete",
|
||||||
"daysOfYourUsage": "giorni del tuo consumo",
|
"daysOfYourUsage": "giorni del tuo consumo",
|
||||||
"estMoneySaved": "Risparmio stimato",
|
"estMoneySaved": "Risparmio stimato",
|
||||||
"atCHFRate": "a 0,27 CHF/kWh media",
|
"atCHFRate": "a 0,39 CHF/kWh media",
|
||||||
"solarCoverage": "Autosufficienza",
|
"solarCoverage": "Autosufficienza",
|
||||||
"fromSolarSub": "da solare + batteria",
|
"fromSolarSub": "da solare + batteria",
|
||||||
"avgDailyConsumption": "Consumo medio giornaliero",
|
"avgDailyConsumption": "Consumo medio giornaliero",
|
||||||
"batteryEfficiency": "Efficienza della batteria",
|
"batteryEfficiency": "Efficienza della batteria",
|
||||||
"batteryEffSub": "energia in uscita vs energia in entrata",
|
"batteryEffSub": "scarica vs carica",
|
||||||
"weeklySummary": "Riepilogo settimanale",
|
"weeklySummary": "Riepilogo settimanale",
|
||||||
"metric": "Metrica",
|
"metric": "Metrica",
|
||||||
"thisWeek": "Questa settimana",
|
"thisWeek": "Questa settimana",
|
||||||
|
|
@ -121,7 +121,7 @@
|
||||||
"consumption": "Consumo",
|
"consumption": "Consumo",
|
||||||
"gridImport": "Importazione rete",
|
"gridImport": "Importazione rete",
|
||||||
"gridExport": "Esportazione rete",
|
"gridExport": "Esportazione rete",
|
||||||
"batteryInOut": "Batteria Entrata / Uscita",
|
"batteryInOut": "Batteria Carica / Scarica",
|
||||||
"dailyBreakdown": "Ripartizione giornaliera",
|
"dailyBreakdown": "Ripartizione giornaliera",
|
||||||
"prevWeek": "(settimana precedente)",
|
"prevWeek": "(settimana precedente)",
|
||||||
"sendReport": "Invia rapporto",
|
"sendReport": "Invia rapporto",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue