Add actuator simulation tuning
This commit is contained in:
@@ -1252,6 +1252,42 @@ async function showActuator(actuatorId, evaluationMessage = "") {
|
||||
<button class="secondary" onclick="saveWeightOverrides('${escapeHtml(record.actuator_entity_id)}', true)">Als Gruppe speichern</button>
|
||||
</details>
|
||||
`;
|
||||
const simulationControls = weightedCandidates.length ? `
|
||||
<details class="manual-context" open>
|
||||
<summary>Aktor-Simulation</summary>
|
||||
<p class="muted">Teste Sensorzustände und Gewichtungen, ohne Home Assistant zu schalten.</p>
|
||||
<div class="card-list">
|
||||
${weightedCandidates.map(candidate => {
|
||||
const effective = Math.round((candidate.effective_weight ?? 1) * 100);
|
||||
const currentState = candidate.state || "";
|
||||
const stateOptions = candidate.domain === "binary_sensor"
|
||||
? "off,on"
|
||||
: currentState;
|
||||
return `
|
||||
<article class="actuator-card">
|
||||
<div class="card-title">
|
||||
<div>
|
||||
<strong>${escapeHtml(candidate.friendly_name || candidate.entity_id)}</strong>
|
||||
<div class="entity-id">${escapeHtml(candidate.entity_id)}</div>
|
||||
</div>
|
||||
<span class="chip">Simulation</span>
|
||||
</div>
|
||||
<label for="sim-state-${escapeHtml(candidate.entity_id)}">Simulierter Zustand</label>
|
||||
<input id="sim-state-${escapeHtml(candidate.entity_id)}" data-sim-state-entity="${escapeHtml(candidate.entity_id)}" value="${escapeHtml(currentState)}" placeholder="on, off, 12 ...">
|
||||
<label for="sim-options-${escapeHtml(candidate.entity_id)}">Zustände vergleichen</label>
|
||||
<input id="sim-options-${escapeHtml(candidate.entity_id)}" data-sim-options-entity="${escapeHtml(candidate.entity_id)}" value="${escapeHtml(stateOptions)}" placeholder="on,off">
|
||||
<label for="sim-weight-${escapeHtml(candidate.entity_id)}">Simulierte Gewichtung in %</label>
|
||||
<input id="sim-weight-${escapeHtml(candidate.entity_id)}" data-sim-weight-entity="${escapeHtml(candidate.entity_id)}" type="number" min="0" max="100" step="5" value="${effective}">
|
||||
</article>
|
||||
`;
|
||||
}).join("")}
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button class="secondary" onclick="simulateActuator('${escapeHtml(record.actuator_entity_id)}')">Bestes Szenario berechnen</button>
|
||||
</div>
|
||||
<div id="simulation-result" class="decision-list"></div>
|
||||
</details>
|
||||
` : "<p class='muted'>Für die Simulation müssen zuerst Kontextsensoren ausgewählt sein.</p>";
|
||||
const currentContextControls = contexts.length
|
||||
? `<ul>${contexts.map(entityId => `
|
||||
<li>
|
||||
@@ -1510,6 +1546,7 @@ async function showActuator(actuatorId, evaluationMessage = "") {
|
||||
<h3>Sensor-Gewichtung</h3>
|
||||
${weightControls}
|
||||
${weightGroupControls}
|
||||
${simulationControls}
|
||||
<h3>Verwendete Sensoren/Zustände ändern</h3>
|
||||
${currentContextControls}
|
||||
${manualAssignment}
|
||||
@@ -1610,6 +1647,58 @@ async function saveWeightOverrides(actuatorId, includeNewGroup = false) {
|
||||
}
|
||||
}
|
||||
|
||||
async function simulateActuator(actuatorId) {
|
||||
const sensorStates = {};
|
||||
const sensorWeights = {};
|
||||
const stateOptions = {};
|
||||
for (const input of document.querySelectorAll("[data-sim-state-entity]")) {
|
||||
const value = input.value.trim();
|
||||
if (value) sensorStates[input.dataset.simStateEntity] = value;
|
||||
}
|
||||
for (const input of document.querySelectorAll("[data-sim-weight-entity]")) {
|
||||
const value = Number(input.value);
|
||||
if (Number.isFinite(value)) {
|
||||
sensorWeights[input.dataset.simWeightEntity] = Math.max(0, Math.min(100, value)) / 100;
|
||||
}
|
||||
}
|
||||
for (const input of document.querySelectorAll("[data-sim-options-entity]")) {
|
||||
const values = input.value.split(/[,\s]+/).map(value => value.trim()).filter(Boolean);
|
||||
if (values.length) stateOptions[input.dataset.simOptionsEntity] = values;
|
||||
}
|
||||
const box = document.getElementById("simulation-result");
|
||||
box.innerHTML = "<p class='muted'>Simulation läuft ...</p>";
|
||||
try {
|
||||
const results = await api(`v1/actuators/${encodeURIComponent(actuatorId)}/simulate`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
sensor_states: sensorStates,
|
||||
sensor_weights: sensorWeights,
|
||||
state_options: stateOptions,
|
||||
max_results: 6,
|
||||
}),
|
||||
});
|
||||
box.innerHTML = results.length ? results.map((result, index) => {
|
||||
const prediction = result.prediction;
|
||||
const factors = result.decision_factors || [];
|
||||
return `
|
||||
<div class="decision-row">
|
||||
<header>
|
||||
<strong>${index === 0 ? "Bestes Szenario" : `Szenario ${index + 1}`}</strong>
|
||||
<span class="chip">${prediction ? `${Math.round(prediction.confidence * 100)} % · ${escapeHtml(prediction.target_state)}` : "keine Vorhersage"}</span>
|
||||
</header>
|
||||
<p>${escapeHtml(result.recommendation || "")}</p>
|
||||
<p class="muted">Zustände: ${Object.entries(result.sensor_states || {}).map(([entity, state]) => `${escapeHtml(entity)}=${escapeHtml(state)}`).join(", ") || "keine"}</p>
|
||||
<p class="muted">Gewichtung: ${Object.entries(result.sensor_weights || {}).map(([entity, weight]) => `${escapeHtml(entity)}=${Math.round(weight * 100)} %`).join(", ") || "Standard"}</p>
|
||||
${result.blockers?.length ? `<p class="warn">${result.blockers.map(escapeHtml).join(" ")}</p>` : "<p class='ok'>Würde nach Sicherheitsprüfung schalten.</p>"}
|
||||
${factors.length ? `<ul>${factors.slice(0, 4).map(factor => `<li>${escapeHtml(factor.label)}: ${Math.round((factor.contribution || 0) * 100)} % Beitrag</li>`).join("")}</ul>` : ""}
|
||||
</div>
|
||||
`;
|
||||
}).join("") : "<p class='muted'>Keine Simulationsergebnisse.</p>";
|
||||
} catch (error) {
|
||||
box.innerHTML = `<p class="bad">${escapeHtml(error.message)}</p>`;
|
||||
}
|
||||
}
|
||||
|
||||
async function saveManualAssignment(actuatorId) {
|
||||
const numericEntityId = document.getElementById("manual-numeric-select").value || null;
|
||||
const selectedContextIds = Array.from(
|
||||
|
||||
Reference in New Issue
Block a user