fixed monthly and yearly report overlap issue of PV and Battery performance

This commit is contained in:
Yinyin Liu 2026-03-10 12:50:36 +01:00
parent 6cf14e3483
commit ac034b9983
7 changed files with 64 additions and 38 deletions

View File

@ -739,22 +739,23 @@ Exactly 4 bullet points. Each starts with ""- Title: description"" format.";
var prompt = $@"You are an energy advisor for a sodistore home installation: ""{installationName}"".
Write a concise monthly performance summary in {langName} (4 bullet points, plain text, no markdown).
Write a concise monthly performance summary in {langName} (5 bullet points, plain text, no markdown).
MONTHLY FACTS for {monthName} ({weekCount} days of data):
- PV production: {totalPv:F1} kWh
- Total consumption: {totalConsump:F1} kWh
- Self-sufficiency: {selfSufficiency:F1}% (share of energy covered by solar, without drawing from grid)
- Self-sufficiency: {selfSufficiency:F1}% (share of energy covered by solar + battery, not bought from grid)
- Battery: {totalBattChg:F1} kWh charged, {totalBattDis:F1} kWh discharged, efficiency {batteryEff:F1}%
- Energy saved: {energySaved:F1} kWh = ~{savingsCHF:F0} CHF saved (at {ElectricityPriceCHF} CHF/kWh)
{weatherBlock}
INSTRUCTIONS:
1. Savings: state exactly how much energy and money was saved this month. Positive framing.
2. Solar performance: how much the solar system produced and what self-sufficiency % means for the homeowner (e.g. ""Your solar system covered X% of your home's energy needs""). Do NOT mention battery here. Do NOT mention raw grid import kWh.
3. Battery: comment on battery utilization and efficiency. If efficiency < 80%, note it may need attention.
4. Tip: one specific actionable suggestion based on the weakest metric: {weakMetric}.{weatherTipHint} If general, suggest the most impactful habit change based on the numbers above.
2. Energy independence: state the self-sufficiency percentage and what it means X% of the home's energy came from the combined solar and battery system, only Y% was purchased from the grid. Do NOT repeat raw grid import kWh.
3. Solar production: state how much the solar system produced this month and the daily average. Keep it factual. Do NOT repeat self-sufficiency percentage here.
4. Battery: comment on battery utilization and efficiency. If efficiency < 80%, note it may need attention. Do NOT repeat self-sufficiency percentage here.
5. Tip: one specific actionable suggestion based on the weakest metric: {weakMetric}.{weatherTipHint} If general, suggest the most impactful habit change based on the numbers above.
Rules: Write in {langName}. Write for a homeowner, not a technician. No asterisks or formatting marks. No closing remarks. Exactly 4 bullet points starting with ""- "". Each bullet must have a short title followed by colon then description.";
Rules: Write in {langName}. Write for a homeowner, not a technician. No asterisks or formatting marks. No closing remarks. Exactly 5 bullet points starting with ""- "". Each bullet must have a short title followed by colon then description.";
return await CallMistralAsync(apiKey, prompt);
}
@ -774,7 +775,7 @@ Rules: Write in {langName}. Write for a homeowner, not a technician. No asterisk
var langName = GetLanguageName(language);
var prompt = $@"You are an energy advisor for a sodistore home installation: ""{installationName}"".
Write a concise annual performance summary in {langName} (4 bullet points, plain text, no markdown).
Write a concise annual performance summary in {langName} (5 bullet points, plain text, no markdown).
ANNUAL FACTS for {year} ({monthCount} months of data):
- Total PV production: {totalPv:F1} kWh
@ -787,11 +788,12 @@ ANNUAL FACTS for {year} ({monthCount} months of data):
INSTRUCTIONS:
1. Annual savings highlight: total energy and money saved for the year. Use the exact numbers provided.
2. System performance: comment on PV production and battery health indicators.
3. Year-over-year readiness: note any trends or areas of improvement.
4. Looking ahead: one strategic recommendation for the coming year.
2. Energy independence: state the self-sufficiency percentage X% of the home's energy came from the combined solar and battery system. Do NOT repeat raw grid import kWh.
3. Solar production: state total PV production for the year. Keep it factual. Do NOT repeat self-sufficiency percentage here.
4. Battery: comment on battery efficiency. If efficiency < 80%, note it may need attention. Do NOT repeat self-sufficiency percentage here.
5. Looking ahead: one strategic recommendation for the coming year.
Rules: Write in {langName}. Write for a homeowner. No asterisks or formatting marks. No closing remarks. Exactly 4 bullet points starting with ""- "". Each bullet must have a short title followed by colon then description.";
Rules: Write in {langName}. Write for a homeowner. No asterisks or formatting marks. No closing remarks. Exactly 5 bullet points starting with ""- "". Each bullet must have a short title followed by colon then description.";
return await CallMistralAsync(apiKey, prompt);
}

View File

@ -105,8 +105,8 @@ public static class ReportEmailService
StayedAtHome: "Solar + Batterie, nicht vom Netz",
EstMoneySaved: "Geschätzte Ersparnis",
AtRate: "bei 0.39 CHF/kWh",
SolarCoverage: "Eigenversorgung",
FromSolar: "aus Solar + Batterie",
SolarCoverage: "Energieunabhängigkeit",
FromSolar: "aus eigenem Solar + Batterie System",
BatteryEff: "Batterie-Eff.",
OutVsIn: "Entladung vs. Ladung",
Day: "Tag",
@ -135,8 +135,8 @@ public static class ReportEmailService
StayedAtHome: "solaire + batterie, non achetée au réseau",
EstMoneySaved: "Économies estimées",
AtRate: "à 0.39 CHF/kWh",
SolarCoverage: "Autosuffisance",
FromSolar: "du solaire + batterie",
SolarCoverage: "Indépendance énergétique",
FromSolar: "de votre système solaire + batterie",
BatteryEff: "Eff. batterie",
OutVsIn: "décharge vs charge",
Day: "Jour",
@ -165,8 +165,8 @@ public static class ReportEmailService
StayedAtHome: "solare + batteria, non acquistata dalla rete",
EstMoneySaved: "Risparmio stimato",
AtRate: "a 0.39 CHF/kWh",
SolarCoverage: "Autosufficienza",
FromSolar: "da solare + batteria",
SolarCoverage: "Indipendenza energetica",
FromSolar: "dal proprio impianto solare + batteria",
BatteryEff: "Eff. batteria",
OutVsIn: "scarica vs carica",
Day: "Giorno",
@ -195,8 +195,8 @@ public static class ReportEmailService
StayedAtHome: "solar + battery, not bought from grid",
EstMoneySaved: "Est. Money Saved",
AtRate: "at 0.39 CHF/kWh",
SolarCoverage: "Self-Sufficiency",
FromSolar: "from solar + battery",
SolarCoverage: "Energy Independence",
FromSolar: "from your own solar + battery system",
BatteryEff: "Battery Eff.",
OutVsIn: "discharge vs charge",
Day: "Day",
@ -534,49 +534,49 @@ public static class ReportEmailService
"Monatlicher Leistungsbericht", "Monatliche Erkenntnisse", "Monatliche Zusammenfassung", "Ihre Ersparnisse diesen Monat",
"Kennzahl", "Gesamt", "PV-Produktion", "Verbrauch", "Netzbezug", "Netzeinspeisung", "Batterie Laden / Entladen",
"Energie gespart", "Solar + Batterie, nicht vom Netz", "Geschätzte Ersparnis", "bei 0.39 CHF/kWh",
"Eigenversorgung", "aus Solar + Batterie", "Batterie-Eff.", "Entladung vs. Ladung",
"Energieunabhängigkeit", "aus eigenem Solar + Batterie System", "Batterie-Eff.", "Entladung vs. Ladung",
"Tage aggregiert", "Erstellt von <strong style=\"color:#666\">inesco Energy Monitor</strong>"),
("de", "yearly") => new AggregatedEmailStrings(
"Jährlicher Leistungsbericht", "Jährliche Erkenntnisse", "Jährliche Zusammenfassung", "Ihre Ersparnisse dieses Jahr",
"Kennzahl", "Gesamt", "PV-Produktion", "Verbrauch", "Netzbezug", "Netzeinspeisung", "Batterie Laden / Entladen",
"Energie gespart", "Solar + Batterie, nicht vom Netz", "Geschätzte Ersparnis", "bei 0.39 CHF/kWh",
"Eigenversorgung", "aus Solar + Batterie", "Batterie-Eff.", "Entladung vs. Ladung",
"Energieunabhängigkeit", "aus eigenem Solar + Batterie System", "Batterie-Eff.", "Entladung vs. Ladung",
"Monate aggregiert", "Erstellt von <strong style=\"color:#666\">inesco Energy Monitor</strong>"),
("fr", "monthly") => new AggregatedEmailStrings(
"Rapport de performance mensuel", "Aperçus du mois", "Résumé du mois", "Vos économies ce mois",
"Indicateur", "Total", "Production PV", "Consommation", "Import réseau", "Export réseau", "Batterie Charge / Décharge",
"Énergie économisée", "solaire + batterie, non achetée au réseau", "Économies estimées", "à 0.39 CHF/kWh",
"Autosuffisance", "du solaire + batterie", "Eff. batterie", "décharge vs charge",
"Indépendance énergétique", "de votre système solaire + batterie", "Eff. batterie", "décharge vs charge",
"jours agrégés", "Généré par <strong style=\"color:#666\">inesco Energy Monitor</strong>"),
("fr", "yearly") => new AggregatedEmailStrings(
"Rapport de performance annuel", "Aperçus de l'année", "Résumé de l'année", "Vos économies cette année",
"Indicateur", "Total", "Production PV", "Consommation", "Import réseau", "Export réseau", "Batterie Charge / Décharge",
"Énergie économisée", "solaire + batterie, non achetée au réseau", "Économies estimées", "à 0.39 CHF/kWh",
"Autosuffisance", "du solaire + batterie", "Eff. batterie", "décharge vs charge",
"Indépendance énergétique", "de votre système solaire + batterie", "Eff. batterie", "décharge vs charge",
"mois agrégés", "Généré par <strong style=\"color:#666\">inesco Energy Monitor</strong>"),
("it", "monthly") => new AggregatedEmailStrings(
"Rapporto mensile delle prestazioni", "Approfondimenti mensili", "Riepilogo mensile", "I tuoi risparmi questo mese",
"Metrica", "Totale", "Produzione PV", "Consumo", "Import dalla rete", "Export nella rete", "Batteria Carica / Scarica",
"Energia risparmiata", "solare + batteria, non acquistata dalla rete", "Risparmio stimato", "a 0.39 CHF/kWh",
"Autosufficienza", "da solare + batteria", "Eff. batteria", "scarica vs carica",
"Indipendenza energetica", "dal proprio impianto solare + batteria", "Eff. batteria", "scarica vs carica",
"giorni aggregati", "Generato da <strong style=\"color:#666\">inesco Energy Monitor</strong>"),
("it", "yearly") => new AggregatedEmailStrings(
"Rapporto annuale delle prestazioni", "Approfondimenti annuali", "Riepilogo annuale", "I tuoi risparmi quest'anno",
"Metrica", "Totale", "Produzione PV", "Consumo", "Import dalla rete", "Export nella rete", "Batteria Carica / Scarica",
"Energia risparmiata", "solare + batteria, non acquistata dalla rete", "Risparmio stimato", "a 0.39 CHF/kWh",
"Autosufficienza", "da solare + batteria", "Eff. batteria", "scarica vs carica",
"Indipendenza energetica", "dal proprio impianto solare + batteria", "Eff. batteria", "scarica vs carica",
"mesi aggregati", "Generato da <strong style=\"color:#666\">inesco Energy Monitor</strong>"),
(_, "monthly") => new AggregatedEmailStrings(
"Monthly Performance Report", "Monthly Insights", "Monthly Summary", "Your Savings This Month",
"Metric", "Total", "PV Production", "Consumption", "Grid Import", "Grid Export", "Battery Charge / Discharge",
"Energy Saved", "solar + battery, not bought from grid", "Est. Money Saved", "at 0.39 CHF/kWh",
"Self-Sufficiency", "from solar + battery", "Battery Eff.", "discharge vs charge",
"Energy Independence", "from your own solar + battery system", "Battery Eff.", "discharge vs charge",
"days aggregated", "Generated by <strong style=\"color:#666\">inesco Energy Monitor</strong>"),
_ => new AggregatedEmailStrings(
"Annual Performance Report", "Annual Insights", "Annual Summary", "Your Savings This Year",
"Metric", "Total", "PV Production", "Consumption", "Grid Import", "Grid Export", "Battery Charge / Discharge",
"Energy Saved", "solar + battery, not bought from grid", "Est. Money Saved", "at 0.39 CHF/kWh",
"Self-Sufficiency", "from solar + battery", "Battery Eff.", "discharge vs charge",
"Energy Independence", "from your own solar + battery system", "Battery Eff.", "discharge vs charge",
"months aggregated", "Generated by <strong style=\"color:#666\">inesco Energy Monitor</strong>")
};

View File

@ -703,7 +703,7 @@ function WeeklyHistory({ installationId, latestMonthlyPeriodEnd, currentReportPe
<Grid item xs={6} sm={3}>
<Box sx={{ textAlign: 'center' }}>
<Typography variant="body1" fontWeight="bold" color="#8e44ad">{rec.selfSufficiencyPercent.toFixed(0)}%</Typography>
<Typography variant="caption" color="#888"><FormattedMessage id="solarCoverage" defaultMessage="Self-Sufficiency" /></Typography>
<Typography variant="caption" color="#888"><FormattedMessage id="solarCoverage" defaultMessage="Energy Independence" /></Typography>
</Box>
</Grid>
<Grid item xs={6} sm={3}>
@ -798,6 +798,7 @@ function MonthlySection({
countFn={(r: MonthlyReport) => r.weekCount}
sendEndpoint="/SendMonthlyReportEmail"
sendParamsFn={(r: MonthlyReport) => ({ installationId, year: r.year, month: r.month })}
onRegenerate={(r: MonthlyReport) => onGenerate(r.year, r.month)}
/>
) : pendingMonths.length === 0 ? (
<Alert severity="info">
@ -871,6 +872,7 @@ function YearlySection({
countFn={(r: YearlyReport) => r.monthCount}
sendEndpoint="/SendYearlyReportEmail"
sendParamsFn={(r: YearlyReport) => ({ installationId, year: r.year })}
onRegenerate={(r: YearlyReport) => onGenerate(r.year)}
/>
) : pendingYears.length === 0 ? (
<Alert severity="info">
@ -890,7 +892,8 @@ function AggregatedSection<T extends ReportSummary>({
countLabelId,
countFn,
sendEndpoint,
sendParamsFn
sendParamsFn,
onRegenerate
}: {
reports: T[];
type: 'monthly' | 'yearly';
@ -899,9 +902,11 @@ function AggregatedSection<T extends ReportSummary>({
countFn: (r: T) => number;
sendEndpoint: string;
sendParamsFn: (r: T) => object;
onRegenerate?: (r: T) => void | Promise<void>;
}) {
const intl = useIntl();
const [selectedIdx, setSelectedIdx] = useState(0);
const [regenerating, setRegenerating] = useState(false);
if (reports.length === 0) {
return (
@ -947,7 +952,22 @@ function AggregatedSection<T extends ReportSummary>({
))}
</Select>
)}
<Box sx={{ ml: 'auto' }}>
<Box sx={{ ml: 'auto', display: 'flex', alignItems: 'center', gap: 1 }}>
{onRegenerate && (
<Button
variant="outlined"
size="small"
disabled={regenerating}
startIcon={regenerating ? <CircularProgress size={14} /> : <RefreshIcon />}
onClick={async () => {
setRegenerating(true);
try { await onRegenerate(r); } finally { setRegenerating(false); }
}}
sx={{ textTransform: 'none' }}
>
<FormattedMessage id="regenerateReport" defaultMessage="Regenerate" />
</Button>
)}
<EmailBar onSend={handleSendEmail} />
</Box>
</Box>

View File

@ -107,8 +107,8 @@
"daysOfYourUsage": "Tage Ihres Verbrauchs",
"estMoneySaved": "Geschätzte Ersparnisse",
"atCHFRate": "bei 0,39 CHF/kWh Ø",
"solarCoverage": "Eigenversorgung",
"fromSolarSub": "aus Solar + Batterie",
"solarCoverage": "Energieunabhängigkeit",
"fromSolarSub": "aus eigenem Solar + Batterie System",
"avgDailyConsumption": "Ø Tagesverbrauch",
"batteryEfficiency": "Batterieeffizienz",
"batteryEffSub": "Entladung vs. Ladung",
@ -172,6 +172,7 @@
"availableForGeneration": "Zur Generierung verfügbar",
"generateMonth": "{month} {year} generieren ({count} Wochen)",
"generateYear": "{year} generieren ({count} Monate)",
"regenerateReport": "Neu generieren",
"generatingMonthly": "Wird generiert...",
"generatingYearly": "Wird generiert...",
"thisMonthWeeklyReports": "Wöchentliche Berichte dieses Monats",

View File

@ -89,8 +89,8 @@
"daysOfYourUsage": "days of your usage",
"estMoneySaved": "Est. Money Saved",
"atCHFRate": "at 0.39 CHF/kWh avg.",
"solarCoverage": "Self-Sufficiency",
"fromSolarSub": "from solar + battery",
"solarCoverage": "Energy Independence",
"fromSolarSub": "from your own solar + battery system",
"avgDailyConsumption": "Avg Daily Consumption",
"batteryEfficiency": "Battery Efficiency",
"batteryEffSub": "discharge vs charge",
@ -154,6 +154,7 @@
"availableForGeneration": "Available for Generation",
"generateMonth": "Generate {month} {year} ({count} weeks)",
"generateYear": "Generate {year} ({count} months)",
"regenerateReport": "Regenerate",
"generatingMonthly": "Generating...",
"generatingYearly": "Generating...",
"thisMonthWeeklyReports": "This Month's Weekly Reports",

View File

@ -101,8 +101,8 @@
"daysOfYourUsage": "jours de votre consommation",
"estMoneySaved": "Économies estimées",
"atCHFRate": "à 0,39 CHF/kWh moy.",
"solarCoverage": "Autosuffisance",
"fromSolarSub": "du solaire + batterie",
"solarCoverage": "Indépendance énergétique",
"fromSolarSub": "de votre système solaire + batterie",
"avgDailyConsumption": "Conso. quotidienne moy.",
"batteryEfficiency": "Efficacité de la batterie",
"batteryEffSub": "décharge vs charge",
@ -166,6 +166,7 @@
"availableForGeneration": "Disponible pour génération",
"generateMonth": "Générer {month} {year} ({count} semaines)",
"generateYear": "Générer {year} ({count} mois)",
"regenerateReport": "Régénérer",
"generatingMonthly": "Génération en cours...",
"generatingYearly": "Génération en cours...",
"thisMonthWeeklyReports": "Rapports hebdomadaires de ce mois",

View File

@ -112,8 +112,8 @@
"daysOfYourUsage": "giorni del tuo consumo",
"estMoneySaved": "Risparmio stimato",
"atCHFRate": "a 0,39 CHF/kWh media",
"solarCoverage": "Autosufficienza",
"fromSolarSub": "da solare + batteria",
"solarCoverage": "Indipendenza energetica",
"fromSolarSub": "dal proprio impianto solare + batteria",
"avgDailyConsumption": "Consumo medio giornaliero",
"batteryEfficiency": "Efficienza della batteria",
"batteryEffSub": "scarica vs carica",
@ -177,6 +177,7 @@
"availableForGeneration": "Disponibile per la generazione",
"generateMonth": "Genera {month} {year} ({count} settimane)",
"generateYear": "Genera {year} ({count} mesi)",
"regenerateReport": "Rigenera",
"generatingMonthly": "Generazione in corso...",
"generatingYearly": "Generazione in corso...",
"thisMonthWeeklyReports": "Rapporti settimanali di questo mese",