SFML Eure Erfahrungen sind gefragt // Eure eigenen Karten und Visualieiserungen

Kannst Du mir bitte mal eine Liste per PN schicken welche Sensoren du genau brauchst

1 „Gefällt mir“

Wahnsinn!
Wenn du den Code veröffentlichen möchtest, würde da nicht nein sagen. :slight_smile:
Jedenfalls tolle Arbeit!

2 „Gefällt mir“

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.

:crayon:by HarryP: Zusammenführung Mehrfachpost (bei Änderungen oder hinzufügen von Inhalten bitte die „Bearbeitungsfunktion“ anstatt „Antworten“ zu nutzen)

2 „Gefällt mir“

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. :face_with_raised_eyebrow:
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:

Gefällt mir sehr gut, so kann man im März mal mit Daten vom Winter vergleichen.

Gruß Ralf

2 „Gefällt mir“

hab ich tatsächlich, aber ich muss noch mal schauen. Heute ist keine Zeit mehr. :sweat_smile:

Sinnbefreit, aber ich wollte trotzdem mal wissen was mit „einem“ ausführlichen KI-Prompt möglich ist​:grinning_face:.

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:

4 „Gefällt mir“

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 :slight_smile:

Mal ne Auswerung mit Bordmitteln …

2 „Gefällt mir“

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:

2 „Gefällt mir“

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.

Danke für die Blumen … hab noch ein wenig gebastelt … sorry - die Rahmen sind jetzt weg :wink:

1 „Gefällt mir“

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. :grinning_face:

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.

2 „Gefällt mir“