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
? $@"
| {s.PvProduction} |
{cur.TotalPvProduction:F1} kWh |
{prev.TotalPvProduction:F1} kWh |
{FormatChange(r.PvChangePercent)} |
| {s.Consumption} |
{cur.TotalConsumption:F1} kWh |
{prev.TotalConsumption:F1} kWh |
{FormatChange(r.ConsumptionChangePercent)} |
| {s.GridImport} |
{cur.TotalGridImport:F1} kWh |
{prev.TotalGridImport:F1} kWh |
{FormatChange(r.GridImportChangePercent)} |
| {s.GridExport} |
{cur.TotalGridExport:F1} kWh |
{prev.TotalGridExport:F1} kWh |
— |
| {s.BatteryInOut} |
{cur.TotalBatteryCharged:F1}/{cur.TotalBatteryDischarged:F1} kWh |
{prev.TotalBatteryCharged:F1}/{prev.TotalBatteryDischarged:F1} kWh |
— |
"
: $@"
| {s.PvProduction} | {cur.TotalPvProduction:F1} kWh |
| {s.Consumption} | {cur.TotalConsumption:F1} kWh |
| {s.GridImport} | {cur.TotalGridImport:F1} kWh |
| {s.GridExport} | {cur.TotalGridExport:F1} kWh |
| {s.BatteryInOut} | {cur.TotalBatteryCharged:F1}/{cur.TotalBatteryDischarged:F1} kWh |
";
var comparisonHeaders = prev != null
? $@"{s.ThisWeek} |
{s.LastWeek} |
{s.Change} | "
: $@"{s.ThisWeek} | ";
return $@"
|
{s.Title}
{r.InstallationName}
{r.PeriodStart} — {r.PeriodEnd}
|
|
{s.Insights}
{insightHtml}
|
|
{s.Summary}
| {s.Metric} |
{comparisonHeaders}
{comparisonHtml}
|
|
{s.SavingsHeader}
{SavingsBox(s.SolarEnergyUsed, $"{r.TotalEnergySaved:F1} kWh", s.StayedAtHome, "#27ae60")}
{SavingsBox(s.EstMoneySaved, $"~{r.TotalSavingsCHF:F0} CHF", s.AtRate, "#2980b9")}
{SavingsBox(s.SolarCoverage, $"{r.SelfSufficiencyPercent:F0}%", s.FromSolar, "#8e44ad")}
{SavingsBox(s.BatteryEff, $"{r.BatteryEfficiencyPercent:F0}%", s.OutVsIn, "#e67e22")}
|
|
{s.DailyBreakdown}
| {s.Day} |
PV |
{s.Load} |
{s.GridIn} |
{s.GridOut} |
{s.BattInOut} |
{dailyRows}
|
|
{s.Footer}
|
|
";
}
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 all numbers: time ranges (14:00–18:00), times (09:00), decimals, integers
result = System.Text.RegularExpressions.Regex.Replace(
result,
@"(\d{1,2}:\d{2}(?:[–\-]\d{1,2}:\d{2})?|\d+[.,]\d+|\d+)",
"$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";
}