Kannst Du mir bitte mal eine Liste per PN schicken welche Sensoren du genau brauchst
Wahnsinn!
Wenn du den Code veröffentlichen möchtest, würde da nicht nein sagen. ![]()
Jedenfalls tolle Arbeit!
Ich bastle noch etwas rum - gebt mir noch 1-2 Tage, dann lade ich den aktualisierten Code hoch!
Ist das von Interesse? Eine Panelgruppe im Direktvergleich mit 2 unterschiedlichen Tagen:
Ich habs zuerst in einem Chart probiert - aber da erkennt man dann leider praktisch nichts mehr….daher im 1:1 Vergleich nebenan.
Dafür muss man den SQL Sensor zB auf 720d setzen (anstatt wie jetzt 7), dann kann man auch Jahresvergleiche machen - zB der 11.03.2026 vs 11.03.2027
Hier mal die Karte für den 1:1 Vergleich:
Code
type: custom:mod-card
card:
type: vertical-stack
cards:
- type: horizontal-stack
cards:
- type: vertical-stack
cards:
- type: entities
entities:
- entity: input_datetime.sfml_selected_date_day1
name: " "
card_mod:
style: |
ha-card {
background: transparent;
border-radius: 12px;
border: none;
padding: 0px;
}
- type: custom:config-template-card
entities:
- sensor.sun_next_rising
- sensor.sun_next_setting
- sensor.sfml_offset_tag_1
card:
type: custom:apexcharts-card
grid_options:
columns: full
header:
title: " "
show: true
standard_format: true
show_states: true
colorize_states: true
graph_span: |-
${(() => {
const rise = new Date(states['sensor.sun_next_rising'].state);
const set = new Date(states['sensor.sun_next_setting'].state);
const now = new Date();
if (rise.getDate() !== now.getDate()) rise.setDate(rise.getDate() - 1);
if (set.getDate() !== now.getDate()) set.setDate(set.getDate() - 1);
// Aufgang -2h bis Untergang +2h
const diffMinutes = Math.round((set - rise) / 60000) + 240;
return diffMinutes + "m";
})()}
span:
start: day
offset: |-
${(() => {
const rise = new Date(states['sensor.sun_next_rising'].state);
const now = new Date();
if (rise.getDate() !== now.getDate()) rise.setDate(rise.getDate() - 1);
// Start = Aufgang -2h
const minutes = (rise.getHours() * 60 + rise.getMinutes()) - 120;
return "+" + minutes + "m";
})()}
apex_config:
chart:
background: transparent
height: 350
zoom:
enabled: true
type: x
autoScaleYaxis: true
toolbar:
show: true
tools:
zoom: true
zoomin: true
zoomout: true
pan: true
reset: true
download: true
fill:
type: gradient
gradient:
shade: light
type: vertical
shadeIntensity: 0.2
opacityFrom: 1
opacityTo: 0.1
stops:
- 0
- 100
xaxis:
type: datetime
labels:
datetimeUTC: false
format: HH:mm
legend:
show: true
all_series_config:
type: area
opacity: 0.9
stroke_width: 2
yaxis:
- id: kW
show: true
min: 0
max: 14
apex_config:
tickAmount: 5
title:
text: kWh
- id: header_only
show: false
series:
- entity: sensor.sfml_stats_gesamt_history
name: Tatsächlich
color: orange
stroke_width: 2
opacity: 0.6
yaxis_id: kW
unit: kW
extend_to: false
show:
legend_value: false
in_header: false
data_generator: >
var offset =
parseInt(hass.states['sensor.sfml_offset_tag_1']?.state ||
'0'); var sel = new Date(); sel.setDate(sel.getDate() -
offset); var pad = n => String(n).padStart(2,'0'); var dk
= sel.getFullYear() + '-' + pad(sel.getMonth()+1) + '-' +
pad(sel.getDate()); var now = new Date(); var y =
now.getFullYear(), m = now.getMonth(), d = now.getDate();
var hist = typeof entity.attributes.history_data ===
'string'
? JSON.parse(entity.attributes.history_data)
: entity.attributes.history_data;
var day = hist ? hist[dk] : null; if (!day) return [[new
Date(y,m,d,0,0),0],[new Date(y,m,d,23,59),0]]; day =
typeof day === 'string' ? JSON.parse(day) : day; var pts =
[[new Date(y,m,d,0,0),0]]; day.forEach(function(e){
if (e.actual !== null && e.actual !== undefined)
pts.push([new Date(y,m,d,e.hour,30), e.actual]);
}); pts.push([new Date(y,m,d,23,59),0]); return pts;
- entity: sensor.sfml_stats_gesamt_history
name: Prognose
color: grey
opacity: 0.3
stroke_width: 2
yaxis_id: kW
unit: " kWh"
extend_to: false
show:
legend_value: false
in_header: false
data_generator: >
var offset =
parseInt(hass.states['sensor.sfml_offset_tag_1']?.state ||
'0'); var sel = new Date(); sel.setDate(sel.getDate() -
offset); var pad = n => String(n).padStart(2,'0'); var dk
= sel.getFullYear() + '-' + pad(sel.getMonth()+1) + '-' +
pad(sel.getDate()); var now = new Date(); var y =
now.getFullYear(), m = now.getMonth(), d = now.getDate();
var hist = typeof entity.attributes.history_data ===
'string'
? JSON.parse(entity.attributes.history_data)
: entity.attributes.history_data;
var day = hist ? hist[dk] : null; if (!day) return [[new
Date(y,m,d,0,0),0],[new Date(y,m,d,23,59),0]]; day =
typeof day === 'string' ? JSON.parse(day) : day; var pts =
[[new Date(y,m,d,0,0),0]]; day.forEach(function(e){
pts.push([new Date(y,m,d,e.hour,30), e.pred]);
}); pts.push([new Date(y,m,d,23,59),0]); return pts;
- entity: sensor.sfml_stats_gesamt_history
yaxis_id: header_only
name: Ertrag
color: orange
float_precision: 1
unit: " kWh"
show:
legend_value: true
in_header: true
in_chart: false
data_generator: >
var offset =
parseInt(hass.states['sensor.sfml_offset_tag_1']?.state ||
'0'); var sel = new Date(); sel.setDate(sel.getDate() -
offset); var pad = n => String(n).padStart(2,'0'); var dk
= sel.getFullYear() + '-' + pad(sel.getMonth()+1) + '-' +
pad(sel.getDate()); var hist = typeof
entity.attributes.history_data === 'string'
? JSON.parse(entity.attributes.history_data)
: entity.attributes.history_data;
var day = hist ? hist[dk] : null; if (!day) return [[new
Date(), 0]]; day = typeof day === 'string' ?
JSON.parse(day) : day; var total =
day.reduce(function(s,e){ return s + (e.actual || 0);
},0); return [[new Date(), Math.round(total * 10) / 10]];
- entity: sensor.sfml_stats_gesamt_history
yaxis_id: header_only
name: Prognose
color: grey
float_precision: 1
unit: " kWh"
show:
legend_value: true
in_header: true
in_chart: false
data_generator: >
var offset =
parseInt(hass.states['sensor.sfml_offset_tag_1']?.state ||
'0'); var sel = new Date(); sel.setDate(sel.getDate() -
offset); var pad = n => String(n).padStart(2,'0'); var dk
= sel.getFullYear() + '-' + pad(sel.getMonth()+1) + '-' +
pad(sel.getDate()); var hist = typeof
entity.attributes.history_data === 'string'
? JSON.parse(entity.attributes.history_data)
: entity.attributes.history_data;
var day = hist ? hist[dk] : null; if (!day) return [[new
Date(), 0]]; day = typeof day === 'string' ?
JSON.parse(day) : day; var total =
day.reduce(function(s,e){ return s + (e.pred || 0); },0);
return [[new Date(), Math.round(total * 10) / 10]];
- entity: sensor.sfml_offset_tag_1
yaxis_id: header_only
name: _trigger
show:
in_chart: false
in_header: false
legend_value: false
data_generator: |
return [];
card_mod:
style: |
ha-card {
background: transparent !important;
box-shadow: none !important;
border: none !important;
padding: 1px;
margin-top: -40px;
font-size: 11px;
border-radius: 12px;
}
- type: vertical-stack
cards:
- type: entities
entities:
- entity: input_datetime.sfml_selected_date_day2
name: " "
card_mod:
style: |
ha-card {
background: transparent;
border: none;
border-radius: 12px;
padding: 0px;
}
- type: custom:config-template-card
entities:
- sensor.sun_next_rising
- sensor.sun_next_setting
- sensor.sfml_offset_tag_2
card:
type: custom:apexcharts-card
grid_options:
columns: full
header:
title: " "
show: true
standard_format: true
show_states: true
colorize_states: true
graph_span: |-
${(() => {
const rise = new Date(states['sensor.sun_next_rising'].state);
const set = new Date(states['sensor.sun_next_setting'].state);
const now = new Date();
if (rise.getDate() !== now.getDate()) rise.setDate(rise.getDate() - 1);
if (set.getDate() !== now.getDate()) set.setDate(set.getDate() - 1);
// Aufgang -2h bis Untergang +2h
const diffMinutes = Math.round((set - rise) / 60000) + 240;
return diffMinutes + "m";
})()}
span:
start: day
offset: |-
${(() => {
const rise = new Date(states['sensor.sun_next_rising'].state);
const now = new Date();
if (rise.getDate() !== now.getDate()) rise.setDate(rise.getDate() - 1);
// Start = Aufgang -2h
const minutes = (rise.getHours() * 60 + rise.getMinutes()) - 120;
return "+" + minutes + "m";
})()}
apex_config:
chart:
background: transparent
height: 350
zoom:
enabled: true
type: x
autoScaleYaxis: true
toolbar:
show: true
tools:
zoom: true
zoomin: true
zoomout: true
pan: true
reset: true
download: true
fill:
type: gradient
gradient:
shade: light
type: vertical
shadeIntensity: 0.2
opacityFrom: 1
opacityTo: 0.1
stops:
- 0
- 100
xaxis:
type: datetime
labels:
datetimeUTC: false
format: HH:mm
legend:
show: true
all_series_config:
type: area
opacity: 0.9
stroke_width: 2
yaxis:
- id: kW
show: true
min: 0
max: 14
apex_config:
tickAmount: 5
title:
text: kWh
- id: header_only
show: false
series:
- entity: sensor.sfml_stats_gesamt_history
name: Tatsächlich
color: orange
stroke_width: 2
opacity: 0.6
yaxis_id: kW
unit: kW
extend_to: false
show:
legend_value: false
in_header: false
data_generator: >
var offset =
parseInt(hass.states['sensor.sfml_offset_tag_2']?.state ||
'0'); var sel = new Date(); sel.setDate(sel.getDate() -
offset); var pad = n => String(n).padStart(2,'0'); var dk
= sel.getFullYear() + '-' + pad(sel.getMonth()+1) + '-' +
pad(sel.getDate()); var now = new Date(); var y =
now.getFullYear(), m = now.getMonth(), d = now.getDate();
var hist = typeof entity.attributes.history_data ===
'string'
? JSON.parse(entity.attributes.history_data)
: entity.attributes.history_data;
var day = hist ? hist[dk] : null; if (!day) return [[new
Date(y,m,d,0,0),0],[new Date(y,m,d,23,59),0]]; day =
typeof day === 'string' ? JSON.parse(day) : day; var pts =
[[new Date(y,m,d,0,0),0]]; day.forEach(function(e){
if (e.actual !== null && e.actual !== undefined)
pts.push([new Date(y,m,d,e.hour,30), e.actual]);
}); pts.push([new Date(y,m,d,23,59),0]); return pts;
- entity: sensor.sfml_stats_gesamt_history
name: Prognose
color: grey
opacity: 0.3
stroke_width: 2
yaxis_id: kW
unit: " kWh"
extend_to: false
show:
legend_value: false
in_header: false
data_generator: >
var offset =
parseInt(hass.states['sensor.sfml_offset_tag_2']?.state ||
'0'); var sel = new Date(); sel.setDate(sel.getDate() -
offset); var pad = n => String(n).padStart(2,'0'); var dk
= sel.getFullYear() + '-' + pad(sel.getMonth()+1) + '-' +
pad(sel.getDate()); var now = new Date(); var y =
now.getFullYear(), m = now.getMonth(), d = now.getDate();
var hist = typeof entity.attributes.history_data ===
'string'
? JSON.parse(entity.attributes.history_data)
: entity.attributes.history_data;
var day = hist ? hist[dk] : null; if (!day) return [[new
Date(y,m,d,0,0),0],[new Date(y,m,d,23,59),0]]; day =
typeof day === 'string' ? JSON.parse(day) : day; var pts =
[[new Date(y,m,d,0,0),0]]; day.forEach(function(e){
pts.push([new Date(y,m,d,e.hour,30), e.pred]);
}); pts.push([new Date(y,m,d,23,59),0]); return pts;
- entity: sensor.sfml_stats_gesamt_history
yaxis_id: header_only
name: Ertrag
color: orange
float_precision: 1
unit: " kWh"
show:
legend_value: true
in_header: true
in_chart: false
data_generator: >
var offset =
parseInt(hass.states['sensor.sfml_offset_tag_2']?.state ||
'0'); var sel = new Date(); sel.setDate(sel.getDate() -
offset); var pad = n => String(n).padStart(2,'0'); var dk
= sel.getFullYear() + '-' + pad(sel.getMonth()+1) + '-' +
pad(sel.getDate()); var hist = typeof
entity.attributes.history_data === 'string'
? JSON.parse(entity.attributes.history_data)
: entity.attributes.history_data;
var day = hist ? hist[dk] : null; if (!day) return [[new
Date(), 0]]; day = typeof day === 'string' ?
JSON.parse(day) : day; var total =
day.reduce(function(s,e){ return s + (e.actual || 0);
},0); return [[new Date(), Math.round(total * 10) / 10]];
- entity: sensor.sfml_stats_gesamt_history
yaxis_id: header_only
name: Prognose
color: grey
float_precision: 1
unit: " kWh"
show:
legend_value: true
in_header: true
in_chart: false
data_generator: >
var offset =
parseInt(hass.states['sensor.sfml_offset_tag_2']?.state ||
'0'); var sel = new Date(); sel.setDate(sel.getDate() -
offset); var pad = n => String(n).padStart(2,'0'); var dk
= sel.getFullYear() + '-' + pad(sel.getMonth()+1) + '-' +
pad(sel.getDate()); var hist = typeof
entity.attributes.history_data === 'string'
? JSON.parse(entity.attributes.history_data)
: entity.attributes.history_data;
var day = hist ? hist[dk] : null; if (!day) return [[new
Date(), 0]]; day = typeof day === 'string' ?
JSON.parse(day) : day; var total =
day.reduce(function(s,e){ return s + (e.pred || 0); },0);
return [[new Date(), Math.round(total * 10) / 10]];
- entity: sensor.sfml_offset_tag_2
yaxis_id: header_only
name: _trigger
show:
in_chart: false
in_header: false
legend_value: false
data_generator: |
return [];
card_mod:
style: |
ha-card {
background: transparent !important;
box-shadow: none !important;
border: none !important;
padding: 1px;
margin-top: -40px;
font-size: 11px;
border-radius: 12px;
}
card_mod:
style: |
ha-card {
background: transparent !important;
border: 2px solid rgba(255, 255, 255, 0.4);
border-radius: 12px;
padding: 1px;
box-shadow: 0 0 15px rgba(0,0,0,0.1);
}
grid_options:
columns: 24
rows: 8
Dafür habe ich bei mir die SQL Sensoren von 7 Tage auf 720 Tage erweitert:
Also zB
- name: "SFML Stats String Pool History"
db_url: "sqlite:////config/solar_forecast_ml/solar_forecast.db"
query: >-
SELECT '720d' as state,
(SELECT json_group_object(target_date, hourly_json) FROM (
SELECT hp.target_date,
json_group_array(json_object(
'hour', hp.target_hour,
'pred', ROUND(ppg.prediction_kwh, 4),
'actual', ppg.actual_kwh
)) as hourly_json
FROM hourly_predictions hp
JOIN prediction_panel_groups ppg ON hp.prediction_id = ppg.prediction_id
WHERE hp.target_date >= date('now', 'localtime', '-720 days')
AND hp.target_date < date('now', 'localtime')
AND ppg.group_name = 'Gruppe 5'
GROUP BY hp.target_date ORDER BY hp.target_date
)) as history_data
column: "state"
unique_id: sfml_stats_string_pool_history
Anschließend braucht man 2 Sensoren und 2 Input Datetimes - diese via GUI anlegen:
input_datetime.sfml_selected_date_day1 (nur Datum)
input_datetime.sfml_selected_date_day2 (nur Datum)
2 Template - Sensoren:
Erster Sensor Name sensor.sfml_offset_tag_1
{% set d = states('input_datetime.sfml_selected_date_day1') %}
{% if d not in ['unknown','unavailable',''] %}
{{ (now().date() - strptime(d, '%Y-%m-%d').date()).days }}
{% else %}
0
{% endif %}
Zweiter Sensor Name sensor.sfml_offset_tag_2
{% set d = states('input_datetime.sfml_selected_date_day2') %}
{% if d not in ['unknown','unavailable',''] %}
{{ (now().date() - strptime(d, '%Y-%m-%d').date()).days }}
{% else %}
0
{% endif %}
Anmerkung:
Vergleiche gehen nur mit jeweils einem Datum kleiner heute.
by HarryP: Zusammenführung Mehrfachpost (bei Änderungen oder hinzufügen von Inhalten bitte die „Bearbeitungsfunktion“ anstatt „Antworten“ zu nutzen)
Vielen Dank, ich schaue es mir nachher mal an.
Du musst dazu schreiben, das man diesen String an seine Gruppe anpassen muss.
AND ppg.group_name = ‘Gruppe 5’
Und zu der Karte generell, ich bekomme leider einen Konfig fehler.
Kann dir aber gerade nicht sagen, was für einen und warum.
Edit: In der Apex Config, zielst du auf einen Sensor, den es nicht gibt. ![]()
Wenn ich alle, auf “SFML Stats String Pool History” ändere, behebt es nicht das Problem.
Der zielt auf die History SQL Sensoren ab aus dem Projekt hier:
Edit: Leider Konfig Fehler. Ich schaue mal was Burghard noch geändert hatte.
Aber dein Ansatz funktioniert bei mir gerade nicht. Ggf. Fehlt etwas.
Funktioniert hier einwandfrei.
Vorraussetzung:
- Die eingerichteten SQL-Sensoren aus dem Projekt Solar-Prognose-Card von BMeyendriesch
Solar-Prognose-Card/README.md at main · BMeyendriesch/Solar-Prognose-Card · GitHub - Anpassung aller History-Sensoren von 7d auf 720d oder andere höhere Werte als 7d, dazu gehören die
Stats gesamt Historyund die entsprechenden Sensoren der Panel-Gruppen. - Installation der zusätzlichen Karten aus HACS
Gefällt mir sehr gut, so kann man im März mal mit Daten vom Winter vergleichen.
Gruß Ralf
hab ich tatsächlich, aber ich muss noch mal schauen. Heute ist keine Zeit mehr. ![]()
Sinnbefreit, aber ich wollte trotzdem mal wissen was mit „einem“ ausführlichen KI-Prompt möglich ist
.
SFML Vorhersage:
KI-Prompt basierend auf den PV-Spezifikationen:
PV-TAGESPROGNOSE ALS ENERGY-DASHBOARD MIT ZUSÄTZLICHER EINZELAUSWERTUNG
Erzeuge eine PV-Tagesprognose als modernes Energy-Dashboard und ergänze es um eine zusätzliche Einzelauswertung als separates Dashboard-Modul.
WICHTIG:
Arbeite strikt regelbasiert und deterministisch. Ziel ist, dass bei wiederholter Ausführung möglichst ähnliche Ergebnisse entstehen. Nutze feste Klassen, feste Standardwerte und feste Rechenregeln. Vermeide freie kreative Interpretation.
⸻
STANDORT
Neumünster · Schleswig-Holstein · 54,07°N · 9,98°E
⸻
PV-ANLAGE
• Ost: 10.650 Wp / Azimut 82° / Neigung 35°
• West: 12.070 Wp / Azimut 262° / Neigung 35°
• Süd: 5.325 Wp / Azimut 200° / Neigung 33°
• Effektive DC-Nennleistung nach ca. 4 % Altersdegradation: 27,1 kWp
⸻
ANLAGENLOGIK
• Keine reine Südanlage, sondern Ost/West/Süd
• Ost prägt die frühe Tageshälfte
• Süd stabilisiert den Tagesverlauf um die solar starke Phase
• West prägt die späte Tageshälfte bis zum Erzeugungsende
• Dadurch entsteht ein breites, eher flaches Tagesprofil ohne scharfen Mittagspeak
• Bedeckte, aber trockene Tage nicht wie Totalausfall behandeln
• Diffuslicht bei dieser Anlage relevant berücksichtigen
• Die Erzeugung soll sich an realer Tageslänge, Sonnenstand und Wetter orientieren, nicht an starren Uhrzeitfenstern
⸻
PFLICHT
1. Führe zwingend eine Websuche für Neumünster durch.
2. Ermittle für heute, morgen und übermorgen:
• Wetterlage / Kurzbeschreibung
• Bewölkung / Himmelszustand
• Sonnenstunden
• Niederschlag tagsüber
• Sonnenaufgang / Sonnenuntergang für heute
• heutige Tageslänge
3. Verwende möglichst dieselbe Wetterquelle für alle 3 Tage.
4. Nenne die verwendeten Wetterdaten kurz vor den Karten.
5. Leite je Tag einen GHI-Faktor offen ab.
⸻
TAGESKLASSEN
Ordne jeden Tag genau einer Klasse zu:
• A = klar / heiter
• B = leicht bewölkt / hohe Wolken
• C = wechselnd bewölkt / teils Sonne
• D = stärker bewölkt, diffus, meist trocken
• E = bedeckt / Nebel / Hochnebel, meist trocken
• F = Regen / dichte Wolken / echter Schlechtwettertag
⸻
GHI-STANDARDWERTE
• A = 0,92
• B = 0,83
• C = 0,71
• D = 0,58
• E = 0,40
• F = 0,22
⸻
ABWEICHUNGSREGEL
Nur wenn die Wetterdaten eindeutig dafür sprechen, darf der GHI-Faktor maximal ±0,02 vom Standardwert abweichen.
⸻
FESTE EINORDNUNGSREGELN
• „wechselnd bewölkt” → immer C
• „hohe Wolken” / „Sonne durch Schleierwolken” → immer B
• „stark bewölkt, aber meist trocken” → eher D
• „bedeckt, aber trocken” → E, nicht automatisch F
• „klarer Regen tagsüber” + sehr wenig Sonne → F
• Wenn Sonnenstunden ≥ 8 und tagsüber 0 mm → nicht schlechter als C
• Wenn Sonnenstunden ≤ 2 und tagsüber Regen genannt wird → F
• Wenn overcast/cloudy, aber meist trocken → D oder E, nicht F
⸻
SAISONALE BASISSKALIERUNG
Leite die saisonale Ertragsbasis nicht aus einem festen Monat ab, sondern aus Datum, Tageslänge und Jahreszeit in Norddeutschland.
Nutze diese festen saisonalen Leitplanken für 27,1 kWp:
Tiefwinter (ca. Dezember bis Januar, sehr kurze Tage, sehr niedrige Sonnenhöhe):
• A: 28–45 kWh · B: 22–38 kWh · C: 16–30 kWh · D: 10–22 kWh · E: 6–16 kWh · F: 3–10 kWh
Spätwinter / Vorfrühling (ca. Februar bis Mitte März):
• A: 50–78 kWh · B: 42–68 kWh · C: 32–55 kWh · D: 22–42 kWh · E: 14–30 kWh · F: 6–18 kWh
Frühling / Herbst (ca. Mitte März bis Mai sowie September bis Mitte Oktober):
• A: 90–125 kWh · B: 78–111 kWh · C: 62–95 kWh · D: 45–77 kWh · E: 28–59 kWh · F: 14–39 kWh
Hochsommernah / Sommer (ca. Mitte Mai bis August, lange Tage, hohe Sonnenhöhe):
• A: 118–145 kWh · B: 102–128 kWh · C: 82–110 kWh · D: 62–90 kWh · E: 40–68 kWh · F: 20–44 kWh
Frühherbst / Spätsommer (ca. Mitte Oktober bis November):
• A: 45–75 kWh · B: 38–64 kWh · C: 28–52 kWh · D: 18–38 kWh · E: 10–26 kWh · F: 4–14 kWh
Wähle den Mittelwert der Bandbreite als Prognosewert. Weiche nur ab, wenn die Wetterdaten (z. B. besonders gute oder schlechte Bedingungen innerhalb der Klasse) dies eindeutig rechtfertigen – maximal bis zur Bandbreitengrenze.
⸻
STÜNDLICHES ERTRAGSPROFIL
Berechne das stündliche Ertragsprofil deterministisch und nachvollziehbar – nicht schematisch.
Gehe dabei wie folgt vor:
1. Tageslichtfenster:
Bestimme zunächst Sonnenaufgang und Sonnenuntergang für den jeweiligen Tag und Standort (54,07°N, 9,98°E) rechnerisch. Berechne dann für jede volle Stunde innerhalb dieses Zeitfensters (d. h. nur solange die Sonne über dem Horizont steht, Elevation > 0°) den Sonnenstand. Stunden außerhalb dieses Fensters entfallen vollständig – kein Eintrag, keine Nullzeile.
2. Flächengewichtete Einstrahlung je Stunde:
Berechne für jede der drei Teilflächen (Ost 82°, Süd 200°, West 262°, Neigung Ost/West 35°, Süd 33°) den Cosinus des Einfallswinkels (cos θ) in Abhängigkeit von Sonnenazimut und Elevation. Negative cos θ-Werte (Sonne hinter der Fläche) werden auf 0 gesetzt. Summiere die drei Teilbeiträge gewichtet nach Nennleistung (Ost: 10,65 kWp, Süd: 5,33 kWp, West: 12,07 kWp).
3. GHI-Skalierung:
Multipliziere den stündlichen Rohwert mit dem Tages-GHI-Faktor der jeweiligen Klasse.
4. Normierung auf Tagesertrag:
Skaliere die Stundenwerte so, dass ihre Summe exakt dem prognostizierten Tagesertrag entspricht.
5. Ausgabe:
Gib für jede Stunde innerhalb des tatsächlichen Tageslichtfensters (Elevation > 0°, tagesspezifisch berechnet) den berechneten Ertrag in kWh aus (auf eine Nachkommastelle gerundet). Die Stundenachse im Balkendiagramm beginnt bei der ersten Sonnenstunde und endet bei der letzten – dynamisch je Tag. Diese Werte werden direkt als Balkenhöhen im Dashboard verwendet – keine weitere Interpretation oder Glättung.
⸻
ZUSÄTZLICHES MODUL: EINZELAUSWERTUNG
Erzeuge zusätzlich eine vierte, separate Dashboard-Karte bzw. ein eigenes Modul als Einzelauswertung.
Diese Einzelauswertung soll denselben dunklen Energy-Dashboard-Stil haben wie die drei Tageskarten und immer den wichtigsten Einzeltag hervorheben.
FESTE REGEL, WELCHER TAG ALS EINZELAUSWERTUNG GEZEIGT WIRD
Wähle deterministisch genau einen Tag nach dieser festen Priorität:
1. Tag mit dem höchsten prognostizierten Tagesertrag
2. Bei Gleichstand: heute
3. Danach: morgen
4. Danach: übermorgen
⸻
INHALT DER EINZELAUSWERTUNG
Die Einzelauswertung muss als großes Analyse-Modul zusätzlich zu den 3 Tageskarten dargestellt werden und enthalten:
• Datum, Wochentag, ausgewählter Tag
• Tagesklasse (Buchstabe + Bezeichnung)
• Wetterbeschreibung kompakt
• GHI-Faktor mit kurzer offener Herleitung
• Prognostizierter Tagesertrag in kWh, groß hervorgehoben
• Klassenbandbreite und Position innerhalb der Bandbreite
• Saisonzuordnung
• Sonnenaufgang, Sonnenuntergang, Tageslänge
• Peak-Leistung ca.
• Spezifischer Ertrag in kWh/kWp
• Diffus-Anteil in %
• Vergleich zu den beiden anderen Tagen:
• absolut in kWh
• relativ in %
• Deterministische textliche Einordnung der Erzeugungscharakteristik:
• z. B. „früher Schwerpunkt”, „breiter Tagesverlauf”, „später stärker”, „diffus getragen”, „gleichmäßiger Solarverlauf”
• Stündliches Ertragsprofil als größeres Balkendiagramm
• Zusätzlich ein Segmentprofil nach Anlagenteilen:
• Ost-Anteil in %
• Süd-Anteil in %
• West-Anteil in %
• Feste Erläuterung, warum die Anlagengeometrie an diesem Tag so wirkt:
• Ost fördert frühe Stunden
• Süd stabilisiert die solar starke Phase
• West trägt späte Stunden
• bei diffusem Wetter flacht sich die Richtungsdominanz ab
⸻
REGELN FÜR DIE EINZELAUSWERTUNG
• Keine freie Interpretation, sondern nur aus den festen Rechenwerten ableiten
• Die textliche Einordnung muss regelbasiert sein
• Verwende feste Formulierungsbausteine
• Wenn Klasse A oder B: stärkerer Hinweis auf solar aktive Stunden
• Wenn Klasse D oder E: stärkerer Hinweis auf Diffuslicht
• Wenn Klasse F: Fokus auf stark begrenzte Direktstrahlung und geringe Ertragsspitze
• Wenn Ost- und Westanteil zusammen deutlich über Südanteil liegen, benenne das Profil als „breit gezogen“
• Wenn Mittagsbereich trotz guter Klasse nicht dominant ist, benenne das Profil als „ohne harten Mittagspeak“
⸻
FESTE TEXTBAUSTEINE FÜR DIE EINZELAUSWERTUNG
Nutze möglichst nur diese festen Formulierungen:
Erzeugungsprofil:
• „früher Schwerpunkt“
• „breit gezogener Tagesverlauf“
• „stabile Mittagsphase“
• „später Ertragsschwerpunkt“
• „diffus geprägter Verlauf“
• „ohne harten Mittagspeak“
Wetterwirkung:
• „hoher Direktstrahlungsanteil“
• „gute Solartransparenz“
• „teilweise diffuse Dämpfung“
• „deutlich diffuse Prägung“
• „stark begrenzte Direktstrahlung“
Anlagenwirkung:
• „Ostfläche aktiviert die frühen Stunden“
• „Südfläche stabilisiert die solar starke Phase“
• „Westfläche verlängert den Ertrag bis in die späten Stunden“
• „Diffuslicht reduziert die Richtungsdominanz“
⸻
DASHBOARD-GESTALTUNG
Erstelle das Dashboard als einzelne HTML-Datei mit:
• 3 kompakten Karten für heute, morgen und übermorgen
• 1 zusätzlichem Einzelauswertungs-Modul im selben Stil
Jede Tageskarte enthält:
• Datum, Wochentag, Tagesklasse (Buchstabe + Bezeichnung)
• Wetterbeschreibung + Kennwerte (Temp., Niederschlag, Sonnenstunden)
• Prognostizierter Tagesertrag in kWh (groß, farbig hervorgehoben)
• Bandbreite der Klasse
• Auslastungsbalken relativ zum saisonalen Klassenmittel
• Stündliches Ertragsprofil als Balkendiagramm (berechnete Werte, nicht schematisch), mit dynamischer Stundenachse (beginnt und endet tagesspezifisch mit der ersten bzw. letzten Sonnenstunde) und kWh-Beschriftung pro Balken
• Kennwerte: Peak-Leistung ca., spez. Ertrag kWh/kWp, Diffus-Anteil, Vergleich zu Vortag/heute
• GHI-Faktor als Chip
• Systemstreifen oben: Sonnenaufgang, Sonnenuntergang, Tageslänge, Saison, Anlagenprofil, Quelle
Die Einzelauswertung enthält zusätzlich:
• größere KPI-Flächen
• ein vergrößertes Stundenprofil
• Segmentauswertung Ost/Süd/West
• Vergleich der drei Tage
• regelbasierte Kurzinterpretation
⸻
STIL
• modernes Energy-Dashboard
• dunkles Theme
• industriell-präzise Typografie
• farbkodiert nach Klasse
• A = Gold
• C = Hellblau
• E = Grau-Blau
• animierte Balken beim Laden
• kein generisches KI-Aussehen
⸻
FOOTER
• Methodik
• Quellen
• Datum
⸻
AUSGABEFORMAT
Gib das Ergebnis als vollständige einzelne HTML-Datei aus.
Die Datei muss direkt speicher- und renderbar sein.
Verwende nur HTML, CSS und optional sehr leichtes eingebettetes JavaScript für Animationen.
Keine externen Frameworks.
Alle Daten, Styles und Berechnungsresultate müssen in der Datei enthalten sein.
ChatGPT Plus Ergebis:
Whow, beeindruckender Prompt! Und schönes Ergebnis! Jetzt würde mich nur noch interessieren, ob die Prognosen etwas mit der eingetretenen Realität zu tun hatten.
Heute war es zumindest dicht dran… Morgen schaue ich auch nochmal aus Spaß mit einem neuen Prompt-Durchlauf…
sehr cool, hab mir Dein Prompt gleich mal weggespeichert ![]()
Danke - die Vorhersagebalken habe ich mir mal dreist geklaut. Hatte das vorher in einem Apexchart, aber wenn man pro Tag nur einen Wert im Graph sehen will, muss man wieder auf die Historie gehen und dann hat man bei “Rest heute” eine starke zeitliche Verzögerung….
Guten Morgen,
bin wieder am Testen einer neuen Karte - eine Übersicht über die aktuelle Differenz pro Panelgruppe.
D.h. Aufstellung jeder Stunde, Ist-kWh sowie Prognose, Delta dazu usw…
Oben die Gesamt-Tagesansicht inkl der aktuell besten PV Stunde.
Muss mich damit heute Abend noch spielen, ob man das mit einem Datepicker zusammenschmeißen kann, damit man auch historische Tage auswählen kann.
Moin … hier mal ein Zwischenstand meines SFML-Dashboards (noch in Arbeit)
genutze Karten:
- Home Assistant Energy Entity Card
- Energy Custom Graph
- Lovelace Entity Progress Card
- Lovelace Mini Graph Card
- anchor-card (genutzt als “Scroll to Top” im Footer)
- energy-date-selection (im Footer)
https://github.com/zeronounours/lovelace-energy-entity-row
https://github.com/thybag/ha-energy-entity-card
https://github.com/Thyraz/energy-custom-graph
alle 3 Karten können die “energy-date-selection” nutzen. Ist vielleicht hilfreich für dich.
Sieht wirklich gut aus. Ich bin eigentlich kein Fan von “Dark Themes”, aber der minimalistische Stil plus die eckigen Rahmen….echt edel.
Ich hab schon eine Lösung gefunden (habe mir einen eigenen Datepicker gebaut) - aber vielen Dank für die Karten - schaue ich mir an.
Sehr cool! Sieht auch ohne Rahmen gut aus…wie aus einem Wurf weil alles farblich abgestimmt ist.
Ich arbeite viel mit browser-mod, um weitere Informationen anzuzeigen in einem Popup - sonst wirds zumindest bei mir zu viel an Informationen (da ich 5 Strings habe).
Praktisch ist auf jeden Fall, dass du mit dem HA-eigenen Date Picker arbeiten kannst…das erspart viel Arbeit. ![]()
ich habe das zB so bei mir:
Und man kann mit den Buttons “Detail”, “AI Status” und “Panel History” verzweigen via Browser Mod in Untersektionen…zB
Detail:
Oder bei AI Status - da verzweigt man in die Logs für die EoD Verarbeitung, die Mid Day Correction bzw in die AI Daten…
Ist aber mühsam muss ich gestehen via Browser Mod, weil du alles in Stacks packen musst wenn man die Karten in einem bestimmten Layout haben will.
Manches mache ich auch via Plotly Graph Card mit einem Range Selector - dann braucht man zB den Datepicker nicht. Alles andere mache ich eigentlich mit Apex, weil die Funktionen wie span oder group_by einfach sehr viele Probleme löst, wofür man sonst eigene Sensoren bräuchte.

















