Add actuator sensor weighting controls
This commit is contained in:
@@ -271,6 +271,7 @@ let cachedActuators = null;
|
||||
let cachedEntities = null;
|
||||
let cachedDiscovery = null;
|
||||
let discoveryLoadPromise = null;
|
||||
let currentSensorWeightGroups = [];
|
||||
const ACTUATOR_RESULT_LIMIT = 50;
|
||||
const STATUS_TIMEOUT_MS = 2000;
|
||||
const DASHBOARD_TIMEOUT_MS = 4500;
|
||||
@@ -803,6 +804,62 @@ async function showActuator(actuatorId, evaluationMessage = "") {
|
||||
.filter(candidate => contexts.includes(candidate.entity_id))
|
||||
.map(candidate => `<li><strong>${escapeHtml(candidate.friendly_name || candidate.entity_id)}</strong>: ${uniqueValues(candidate.evidence).map(escapeHtml).join(", ") || "statistisch relevanter Kandidat"}</li>`)
|
||||
.join("");
|
||||
const weightedCandidates = [...record.numeric_candidates, ...record.context_candidates]
|
||||
.filter(candidate => contexts.includes(candidate.entity_id));
|
||||
const weightGroups = record.manual_override?.sensor_weight_groups || [];
|
||||
currentSensorWeightGroups = weightGroups;
|
||||
const weightControls = weightedCandidates.length ? `
|
||||
<div class="card-list">
|
||||
${weightedCandidates.map(candidate => {
|
||||
const relevance = Math.round((candidate.confidence ?? 0) * 100);
|
||||
const effective = Math.round((candidate.effective_weight ?? 1) * 100);
|
||||
const manual = candidate.manual_weight == null ? effective : Math.round(candidate.manual_weight * 100);
|
||||
return `
|
||||
<article class="actuator-card">
|
||||
<div class="card-title">
|
||||
<div>
|
||||
<div><strong>${escapeHtml(candidate.friendly_name || candidate.entity_id)}</strong></div>
|
||||
<div class="entity-id">${escapeHtml(candidate.entity_id)}</div>
|
||||
</div>
|
||||
<span class="chip">${relevance} % relevant</span>
|
||||
</div>
|
||||
<div class="metric-grid">
|
||||
<div class="metric"><strong>Automatische Relevanz</strong>${relevance} %</div>
|
||||
<div class="metric"><strong>Aktive Gewichtung</strong>${effective} %</div>
|
||||
<div class="metric"><strong>Score</strong>${escapeHtml(candidate.score)}</div>
|
||||
</div>
|
||||
<label for="weight-${escapeHtml(candidate.entity_id)}">Gewichtung korrigieren</label>
|
||||
<input id="weight-${escapeHtml(candidate.entity_id)}" data-weight-entity="${escapeHtml(candidate.entity_id)}" type="number" min="0" max="100" step="5" value="${manual}">
|
||||
</article>
|
||||
`;
|
||||
}).join("")}
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button onclick="saveWeightOverrides('${escapeHtml(record.actuator_entity_id)}')">Gewichtungen speichern</button>
|
||||
</div>
|
||||
` : "<p class='muted'>Noch keine verwendeten Sensoren oder Zustände für eine Gewichtung ausgewählt.</p>";
|
||||
const weightGroupControls = `
|
||||
<details class="manual-context">
|
||||
<summary>Gruppen-Gewichtung</summary>
|
||||
${weightGroups.length ? `<ul>${weightGroups.map(group => `
|
||||
<li><strong>${escapeHtml(group.name)}</strong>: ${Math.round(group.weight * 100)} %
|
||||
<span class="muted">${group.entity_ids.map(escapeHtml).join(", ")}</span></li>
|
||||
`).join("")}</ul>` : "<p class='muted'>Noch keine Gruppe gespeichert.</p>"}
|
||||
<div class="inline-controls">
|
||||
<div>
|
||||
<label for="weight-group-name">Gruppenname</label>
|
||||
<input id="weight-group-name" placeholder="z. B. Flur Bewegung + Helligkeit">
|
||||
</div>
|
||||
<div>
|
||||
<label for="weight-group-value">Gruppen-Gewicht in %</label>
|
||||
<input id="weight-group-value" type="number" min="0" max="100" step="5" value="100">
|
||||
</div>
|
||||
</div>
|
||||
<label for="weight-group-entities">Entity-IDs der Gruppe</label>
|
||||
<textarea id="weight-group-entities" class="manual-entry" placeholder="Eine oder mehrere Entity-IDs">${escapeHtml(contexts.join("\n"))}</textarea>
|
||||
<button class="secondary" onclick="saveWeightOverrides('${escapeHtml(record.actuator_entity_id)}', true)">Als Gruppe speichern</button>
|
||||
</details>
|
||||
`;
|
||||
const currentContextControls = contexts.length
|
||||
? `<ul>${contexts.map(entityId => `
|
||||
<li>
|
||||
@@ -927,6 +984,10 @@ async function showActuator(actuatorId, evaluationMessage = "") {
|
||||
${automationControls}
|
||||
<h3>Welche Zusammenhänge automatisch verwendet werden</h3>
|
||||
${evidence ? `<ul>${evidence}</ul>` : "<p class='warn'>Noch kein geeigneter Kontext erkannt. SillyHome prüft bei neuen HA-Daten erneut.</p>"}
|
||||
<h3>Sensor-Gewichtung</h3>
|
||||
<p class="muted">Automatische Relevanz kommt aus der Zuordnung. Die aktive Gewichtung kannst du korrigieren; Gruppen bündeln mehrere Sensoren/Zustände.</p>
|
||||
${weightControls}
|
||||
${weightGroupControls}
|
||||
<h3>Verwendete Sensoren/Zustände ändern</h3>
|
||||
${currentContextControls}
|
||||
${manualAssignment}
|
||||
@@ -986,6 +1047,45 @@ async function hydrateCurrentContextOptions(actuatorId) {
|
||||
await hydrateContextOptions(record);
|
||||
}
|
||||
|
||||
async function saveWeightOverrides(actuatorId, includeNewGroup = false) {
|
||||
const sensorWeights = {};
|
||||
for (const input of document.querySelectorAll("[data-weight-entity]")) {
|
||||
const value = Number(input.value);
|
||||
if (Number.isFinite(value)) {
|
||||
sensorWeights[input.dataset.weightEntity] = Math.max(0, Math.min(100, value)) / 100;
|
||||
}
|
||||
}
|
||||
const groups = [...currentSensorWeightGroups];
|
||||
if (includeNewGroup) {
|
||||
const name = document.getElementById("weight-group-name")?.value.trim();
|
||||
const value = Number(document.getElementById("weight-group-value")?.value || 100);
|
||||
const entityIds = parseEntityIds(document.getElementById("weight-group-entities")?.value || "");
|
||||
if (name && entityIds.length) {
|
||||
groups.push({
|
||||
group_id: name.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 64) || "gruppe",
|
||||
name,
|
||||
entity_ids: entityIds,
|
||||
weight: Math.max(0, Math.min(100, Number.isFinite(value) ? value : 100)) / 100,
|
||||
});
|
||||
}
|
||||
}
|
||||
try {
|
||||
await api(`v1/actuators/${encodeURIComponent(actuatorId)}/weights`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
sensor_weights: sensorWeights,
|
||||
sensor_weight_groups: groups,
|
||||
note: "Gewichtung im Dashboard korrigiert",
|
||||
}),
|
||||
});
|
||||
invalidateDashboardCache();
|
||||
await loadConfiguredActuators();
|
||||
await showActuator(actuatorId, "Sensor-Gewichtung gespeichert.");
|
||||
} catch (error) {
|
||||
alert(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function saveManualAssignment(actuatorId) {
|
||||
const numericEntityId = document.getElementById("manual-numeric-select").value || null;
|
||||
const selectedContextIds = Array.from(
|
||||
|
||||
Reference in New Issue
Block a user