feat(workflow): publish verified forecast controls and consolidated docs
Tests / test (push) Successful in 1m1s
Tests / test (push) Successful in 1m1s
Approved by Daniel Haefliger for develop and beta. Author dh_Agent, authenticated account dh. Preserve published battery, charging and overall Energy Pie changes. No deployment or plant control authorization.
This commit is contained in:
@@ -845,20 +845,14 @@
|
||||
schedule: true
|
||||
});
|
||||
if (result.status === "license_required") {
|
||||
byId("forecastApiStatus").textContent = "Keine aktive Prognoselizenz";
|
||||
return setForecastState("Für dieses System ist eine aktive Prognoselizenz erforderlich.");
|
||||
}
|
||||
if (result.status === "not_connected") {
|
||||
byId("forecastApiStatus").textContent = "Manager noch nicht verbunden";
|
||||
return setForecastState("Verbinde den Manager zuerst mit dem Lizenzcode dieses Systems.");
|
||||
}
|
||||
if (result.status === "no_data") {
|
||||
byId("forecastApiStatus").textContent = "Verbindung bereit, noch keine Daten";
|
||||
return setForecastState("Für dieses System liegen im gewählten Zeitfenster noch keine Prognosedaten vor.");
|
||||
return setForecastState("Für dieses System liegen im gewählten Zeitfenster noch keine historischen Daten vor.");
|
||||
}
|
||||
byId("forecastApiStatus").textContent = result.configured
|
||||
? "Prognose aktiv"
|
||||
: "Daten empfangen, Konfiguration offen";
|
||||
renderForecastChart(result.series || []);
|
||||
}
|
||||
|
||||
@@ -1167,13 +1161,7 @@
|
||||
form.elements.tarif_peak_fest.value = forecastNumber(configuration.tarif_peak_fest, 5);
|
||||
renderTariffSelector(form, "import", configuration.tarif_bezug_modus, configuration.tarif_bezug, configuration.tarif_bezug_fest, true);
|
||||
renderTariffSelector(form, "export", configuration.tarif_einspeisung_modus, configuration.tarif_einspeisung, configuration.tarif_einspeisung_fest, true);
|
||||
Object.entries(forecastCapabilityVariants).forEach(function (entry) {
|
||||
var capability = entry[0];
|
||||
var variants = entry[1];
|
||||
form.elements[capability].checked = variants.some(function (variant) {
|
||||
return Boolean(configuration["prog_var_" + variant]);
|
||||
});
|
||||
});
|
||||
form.dataset.forecastVariants = JSON.stringify(forecastGeneratorFlags(configuration));
|
||||
var managerOwned = result.technicalSource === "manager" && Boolean(result.topology);
|
||||
renderForecastRoofs(configuration.daecher || [], managerOwned);
|
||||
renderForecastTopology(result.topology || null);
|
||||
@@ -1192,59 +1180,47 @@
|
||||
control.disabled = true;
|
||||
});
|
||||
}
|
||||
var licensed = result.forecastCapabilities || {
|
||||
pv: true,
|
||||
load: true,
|
||||
schedule: true
|
||||
};
|
||||
[
|
||||
["forecast_pv", "pv"],
|
||||
["forecast_load", "load"],
|
||||
["grid_schedule", "schedule"]
|
||||
].forEach(function (entry) {
|
||||
if (!licensed[entry[1]]) {
|
||||
form.elements[entry[0]].checked = false;
|
||||
form.elements[entry[0]].disabled = true;
|
||||
}
|
||||
});
|
||||
var licensed = result.forecastCapabilities;
|
||||
byId("forecastLicensedScope").textContent = licensed ? ([
|
||||
licensed.pv ? "PV-Ertragsprognose" : "", licensed.load ? "Hausprognose" : "", licensed.schedule ? "Netzfahrplan" : ""
|
||||
].filter(Boolean).join(" · ") || "Keine Prognosefunktion lizenziert") : "Lizenzumfang nicht bestätigt";
|
||||
byId("forecastConfigStatus").textContent = managerOwned
|
||||
? "Technik vom Manager"
|
||||
: (result.configured ? "Gespeichert" : "Noch nicht gespeichert");
|
||||
byId("forecastConfigStatus").className = "status " + (result.configured ? "available" : "review");
|
||||
form.classList.remove("hidden");
|
||||
byId("forecastConfigSection").classList.remove("hidden");
|
||||
}
|
||||
|
||||
async function loadForecastConfiguration(plant, requestId) {
|
||||
var form = byId("forecastConfigForm");
|
||||
form.classList.add("hidden");
|
||||
form.dataset.plantId = "";
|
||||
try {
|
||||
var result = await api("/api/plants/" + plant.id + "/prognosis/configuration");
|
||||
if (requestId !== state.forecastRequest) return;
|
||||
fillForecastConfiguration(plant, result);
|
||||
} catch (error) {
|
||||
if (requestId !== state.forecastRequest) return;
|
||||
byId("forecastConfigStatus").textContent = "Nicht verfügbar";
|
||||
byId("forecastConfigError").textContent = error.message;
|
||||
byId("forecastConfigSection").classList.remove("hidden");
|
||||
}
|
||||
}
|
||||
|
||||
function forecastConfigurationPayload(form) {
|
||||
var selected = {
|
||||
forecast_pv: form.elements.forecast_pv.checked,
|
||||
forecast_load: form.elements.forecast_load.checked,
|
||||
grid_schedule: form.elements.grid_schedule.checked
|
||||
};
|
||||
if (selected.grid_schedule) {
|
||||
selected.forecast_pv = true;
|
||||
selected.forecast_load = true;
|
||||
}
|
||||
if (!Object.values(selected).some(Boolean)) {
|
||||
throw new Error("Bitte mindestens eine Prognosefunktion aktivieren.");
|
||||
}
|
||||
function forecastGeneratorFlags(configuration) {
|
||||
var variants = {};
|
||||
Object.entries(forecastCapabilityVariants).forEach(function (entry) {
|
||||
entry[1].forEach(function (variant) {
|
||||
variants["prog_var_" + variant] = selected[entry[0]];
|
||||
var key = "prog_var_" + variant;
|
||||
if (Object.prototype.hasOwnProperty.call(configuration, key)) variants[key] = configuration[key];
|
||||
});
|
||||
});
|
||||
return variants;
|
||||
}
|
||||
|
||||
function forecastConfigurationPayload(form) {
|
||||
var variants = forecastGeneratorFlags(JSON.parse(form.dataset.forecastVariants || "{}"));
|
||||
var roofs = Array.from(byId("forecastRoofs").querySelectorAll(".forecast-roof-row")).map(function (row) {
|
||||
return {
|
||||
kwp: Number(row.querySelector("[data-roof-field='kwp']").value),
|
||||
@@ -1271,13 +1247,15 @@
|
||||
}
|
||||
|
||||
var integratedPlanner = null;
|
||||
var forecastHistoryRequest = 0;
|
||||
var forecastHistoryKey = null;
|
||||
|
||||
async function loadIntegratedPlanner(plant, requestId) {
|
||||
var host = byId("forecastPlanner");
|
||||
if (!host || !plant.installation_id) return;
|
||||
host.textContent = "Aktueller Netzfahrplan wird geladen.";
|
||||
try {
|
||||
var plannerModule = await import("/netplan-v4.js?v=20261006-integrated");
|
||||
var plannerModule = await import("/netplan-v4.js?v=20261008-workflow");
|
||||
if (requestId !== state.forecastRequest || !byId("view-forecast").classList.contains("active")) return;
|
||||
integratedPlanner = plannerModule.mountPlanner(host, { plantId: plant.id, session: state.session });
|
||||
await integratedPlanner.ready;
|
||||
@@ -1292,6 +1270,8 @@
|
||||
if (byId("forecastPlanner")) byId("forecastPlanner").replaceChildren();
|
||||
var plant = selectedPlant("forecastPlant");
|
||||
var requestId = ++state.forecastRequest;
|
||||
++forecastHistoryRequest;
|
||||
forecastHistoryKey = null;
|
||||
if (!plant || state.forecastPlantId !== plant.id) {
|
||||
state.forecastPlantId = plant ? plant.id : null;
|
||||
state.forecastResult = null;
|
||||
@@ -1300,27 +1280,51 @@
|
||||
state.forecastRangeEnd = null;
|
||||
}
|
||||
resetForecastChart();
|
||||
if (!plant) return setForecastState("Kein System verfügbar.");
|
||||
if (!plant) {
|
||||
byId("forecastPlanner").textContent = "Kein System verfügbar.";
|
||||
return;
|
||||
}
|
||||
|
||||
byId("forecastVariant").textContent = managerLabel(plant.manager_variant);
|
||||
byId("forecastConnection").textContent = plant.installation_id ? "Verbunden mit " + plant.installation_id : "Noch nicht verknüpft";
|
||||
renderForecastLicenseWarning(plant);
|
||||
if (!byId("view-forecast").classList.contains("active")) return;
|
||||
byId("forecastApiStatus").textContent = plant.installation_id
|
||||
? "Betriebsstatus aus der Manager-Rückmeldung"
|
||||
: "Manager-Verknüpfung erforderlich";
|
||||
if (plant.installation_id) void loadIntegratedPlanner(plant, requestId);
|
||||
else byId("forecastPlanner").textContent = "Verbinde den Manager zuerst mit dem Lizenzcode dieses Systems.";
|
||||
void loadForecastConfiguration(plant, requestId);
|
||||
void loadForecastHistory();
|
||||
}
|
||||
|
||||
void loadIntegratedPlanner(plant, requestId);
|
||||
function historicalForecastResult(result, now) {
|
||||
var keys = ["PV", "Hausverbrauch", "Netzleistung", "SOC", "prog_var_1", "prog_var_2", "prog_var_10", "prog_var_11", "prog_var_21", "prog_var_22"];
|
||||
return Object.assign({}, result, {
|
||||
scheduleSourceModel: null,
|
||||
series: (result.series || []).filter(function (series) { return keys.includes(series.key); }).map(function (series) {
|
||||
return Object.assign({}, series, { points: (series.points || []).filter(function (point) {
|
||||
var stamp = Date.parse(point.time);
|
||||
return Number.isFinite(stamp) && stamp <= now;
|
||||
}) });
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
setForecastState("Prognosedaten werden geladen.");
|
||||
byId("forecastApiStatus").textContent = "Datenverbindung wird geprüft";
|
||||
async function loadForecastHistory() {
|
||||
var plant = selectedPlant("forecastPlant"), requestId = state.forecastRequest;
|
||||
if (!plant || !byId("view-forecast").classList.contains("active") || !byId("forecastHistory").open) return;
|
||||
var key = plant.id + ":" + state.forecastHistoryDays;
|
||||
if (forecastHistoryKey === key) return;
|
||||
var historyId = ++forecastHistoryRequest;
|
||||
setForecastState("Historische Messwerte und Modellvergleiche werden geladen.");
|
||||
try {
|
||||
var result = await api("/api/plants/" + plant.id + "/prognosis?historyDays=" + encodeURIComponent(state.forecastHistoryDays));
|
||||
if (requestId !== state.forecastRequest) return;
|
||||
renderForecastResult(result);
|
||||
if (["available", "no_data"].includes(result.status)) {
|
||||
await loadForecastConfiguration(plant, requestId);
|
||||
}
|
||||
if (requestId !== state.forecastRequest || historyId !== forecastHistoryRequest) return;
|
||||
forecastHistoryKey = key;
|
||||
renderForecastResult(historicalForecastResult(result, Date.now()));
|
||||
} catch (error) {
|
||||
if (requestId !== state.forecastRequest) return;
|
||||
byId("forecastApiStatus").textContent = "Prognosedienst nicht erreichbar";
|
||||
if (requestId !== state.forecastRequest || historyId !== forecastHistoryRequest) return;
|
||||
setForecastState(error.message);
|
||||
}
|
||||
}
|
||||
@@ -2167,12 +2171,13 @@
|
||||
byId("peakUpgrade").addEventListener("change", updateOrder);
|
||||
byId("orderPlant").addEventListener("change", function () { prepareOrderEditor(); });
|
||||
byId("forecastPlant").addEventListener("change", updateForecast);
|
||||
byId("forecastHistory").addEventListener("toggle", loadForecastHistory);
|
||||
byId("forecastHistoryDays").addEventListener("change", function (event) {
|
||||
state.forecastHistoryDays = Number(event.target.value) || 1;
|
||||
state.forecastRangeStart = null;
|
||||
state.forecastRangeEnd = null;
|
||||
state.forecastVisibleSeries = null;
|
||||
updateForecast();
|
||||
loadForecastHistory();
|
||||
});
|
||||
byId("forecastSeriesControls").addEventListener("change", function (event) {
|
||||
var control = event.target.closest("[data-forecast-series]");
|
||||
|
||||
Reference in New Issue
Block a user