bullet points (split on newlines, strip leading "- " or "1. ")
var insightLines = r.AiInsight
.Split('\n', StringSplitOptions.RemoveEmptyEntries)
.Select(l => System.Text.RegularExpressions.Regex.Replace(l.Trim(), @"^[\d]+[.)]\s*|^[-*]\s*", ""))
.Where(l => l.Length > 0)
.ToList();
var insightHtml = insightLines.Count > 1
? "" +
string.Join("", insightLines.Select(l => $"- {FormatInsightLine(l)}
")) +
"
"
: $"{FormatInsightLine(r.AiInsight)}
";
// Daily rows
var dailyRows = "";
foreach (var d in r.DailyData)
{
var dayName = DateTime.Parse(d.Date).ToString("ddd");
var isCurrentWeek = string.Compare(d.Date, r.PeriodStart, StringComparison.Ordinal) >= 0;
var bgColor = isCurrentWeek ? "#ffffff" : "#f9f9f9";
dailyRows += $@"
| {dayName} {d.Date} |
{d.PvProduction:F1} |
{d.LoadConsumption:F1} |
{d.GridImport:F1} |
{d.GridExport:F1} |
{d.BatteryCharged:F1}/{d.BatteryDischarged:F1} |
";
}
// Week-over-week comparison rows
var comparisonHtml = prev != null
? $@"
| PV Production |
{cur.TotalPvProduction:F1} kWh |
{prev.TotalPvProduction:F1} kWh |
{FormatChange(r.PvChangePercent)} |
| Consumption |
{cur.TotalConsumption:F1} kWh |
{prev.TotalConsumption:F1} kWh |
{FormatChange(r.ConsumptionChangePercent)} |
| Grid Import |
{cur.TotalGridImport:F1} kWh |
{prev.TotalGridImport:F1} kWh |
{FormatChange(r.GridImportChangePercent)} |
| Grid Export |
{cur.TotalGridExport:F1} kWh |
{prev.TotalGridExport:F1} kWh |
— |
| Battery In/Out |
{cur.TotalBatteryCharged:F1}/{cur.TotalBatteryDischarged:F1} kWh |
{prev.TotalBatteryCharged:F1}/{prev.TotalBatteryDischarged:F1} kWh |
— |
"
: $@"
| PV Production | {cur.TotalPvProduction:F1} kWh |
| Consumption | {cur.TotalConsumption:F1} kWh |
| Grid Import | {cur.TotalGridImport:F1} kWh |
| Grid Export | {cur.TotalGridExport:F1} kWh |
| Battery In/Out | {cur.TotalBatteryCharged:F1}/{cur.TotalBatteryDischarged:F1} kWh |
";
var comparisonHeaders = prev != null
? @"This Week |
Last Week |
Change | "
: @"This Week | ";
return $@"
|
Weekly Performance Report
{r.InstallationName}
{r.PeriodStart} — {r.PeriodEnd}
|
|
Weekly Insights
{insightHtml}
|
|
Weekly Summary
| Metric |
{comparisonHeaders}
{comparisonHtml}
|
|
Your Savings This Week
{SavingsBox("Solar Energy Used", $"{r.CurrentWeek.TotalPvProduction - r.CurrentWeek.TotalGridExport:F1} kWh", "stayed at home", "#27ae60")}
{SavingsBox("Est. Money Saved", $"~{(r.CurrentWeek.TotalPvProduction - r.CurrentWeek.TotalGridExport) * 0.27:F1} CHF", "at 0.27 CHF/kWh", "#2980b9")}
{SavingsBox("Solar Coverage", $"{r.SelfSufficiencyPercent:F0}%", "from solar", "#8e44ad")}
{SavingsBox("Battery Eff.", $"{r.BatteryEfficiencyPercent:F0}%", "out vs in", "#e67e22")}
|
|
Daily Breakdown (kWh)
| Day |
PV |
Load |
Grid In |
Grid Out |
Batt In/Out |
{dailyRows}
|
|
Generated by Inesco Energy Monitor Platform · Powered by Mistral AI
|
|
";
}
private static string SavingsBox(string label, string value, string subtitle, string color) =>
$@"
{value}
{label}
{subtitle}
| ";
// Bolds "Title" before first colon, and numbers+units in the rest
private static string FormatInsightLine(string line)
{
var colonIdx = line.IndexOf(':');
string result;
if (colonIdx > 0)
{
var title = line[..colonIdx];
var rest = line[colonIdx..]; // includes the colon
result = $"{title}{rest}";
}
else
{
result = line;
}
// Bold numbers followed by units
result = System.Text.RegularExpressions.Regex.Replace(
result,
@"(\d+[\d,.]*\s*(?:kWh|CHF|%|days?))",
"$1");
return result;
}
private static string FormatChange(double pct) =>
pct == 0 ? "—" : pct > 0 ? $"+{pct:F1}%" : $"{pct:F1}%";
private static string ChangeColor(double pct) =>
pct > 0 ? "#27ae60" : pct < 0 ? "#e74c3c" : "#888";
}