From bcf04da4a7712e269842675af2ba50a161734514 Mon Sep 17 00:00:00 2001 From: Thomas Hermine Date: Sun, 12 Nov 2023 22:40:07 +0100 Subject: [PATCH] feat(summary): Refactor slightly --- src/_helpers/ha.ts | 2 +- src/config.ts | 5 ++- src/index.ts | 57 ++++++++++++++++++++++++++- src/{summary.ts => summary.device.ts} | 27 ++++++++----- 4 files changed, 77 insertions(+), 14 deletions(-) rename src/{summary.ts => summary.device.ts} (73%) diff --git a/src/_helpers/ha.ts b/src/_helpers/ha.ts index 269036f..2a8f371 100644 --- a/src/_helpers/ha.ts +++ b/src/_helpers/ha.ts @@ -56,7 +56,7 @@ export function addDomainToEntities(states) { export function summarizeEntities(states) { return states.map(el => { const attributes = Object.keys(el.attributes).map(key => { return `${key}: ${el.attributes[key]}`}); - return `${el.entity_id}: ${el.state} (${attributes.join(', ')})`; + return `${el.attributes.friendly_name} — (id: ${el.entity_id}): ${el.state} (${attributes.join(', ')})`; }); } diff --git a/src/config.ts b/src/config.ts index 45108d8..ba122f9 100644 --- a/src/config.ts +++ b/src/config.ts @@ -6,7 +6,7 @@ export const { const DEFAULT_HOME_ASSISTANT_HOST = (NODE_ENV === 'production') ? 'http://supervisor/core' : 'http://homeassistant.local'; const DEFAULT_HOME_ASSISTANT_PORT = (NODE_ENV === 'production') ? 80 : 8123; const DEFAULT_HOME_ASSISTANT_TOKEN = process.env.SUPERVISOR_TOKEN; -const DEFAULT_UPDATE_INTERVAL = (NODE_ENV === 'production') ? 1000*60*60 : 5000; // Every hour in production, every 5 seconds in dev +const DEFAULT_UPDATE_INTERVAL = (NODE_ENV === 'production') ? 1000*60*60 : 10000; // Every hour in production, every 5 seconds in dev // ===================================================================================================================== // Config @@ -28,4 +28,5 @@ export const { } = process.env; export const SUMMARY_DEVICE_ENTITY_PREFIX = 'summary_device_'; // will be followed by the device name -export const SUMMARY_DEVICES_ENTITIES = ['hall_tablet','iphone_thomas','iphone_caroline','macbookpro_thomas','roborock','octoprint','litterbox','toothbrush']; \ No newline at end of file +export const SUMMARY_DEVICES_ENTITIES = ['hall_tablet','iphone_thomas','iphone_caroline','macbookpro_thomas','roborock','octoprint','litterbox','toothbrush','pet_feeder','pet_fountain']; +//export const SUMMARY_DEVICES_ENTITIES = ['roborock']; \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 707b8bb..f8ee1f7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,10 +5,11 @@ import {sleep} from './_helpers/sleep'; import { SUMMARY_DEVICES_ENTITIES, + SUMMARY_DEVICE_ENTITY_PREFIX, UPDATE_INTERVAL, } from './config'; import {init} from './init'; -import {updateDeviceSummary} from './summary' +import {updateDeviceSummary} from './summary.device' async function main() { @@ -28,9 +29,17 @@ async function main() { // Device Summaries // =================================================================================================================== log('app','info',`Updating summary for ${SUMMARY_DEVICES_ENTITIES.length} devices...`); - await Promise.allSettled(SUMMARY_DEVICES_ENTITIES.map(device => updateDeviceSummary(openai, ha, states, device))); + await Promise.all(SUMMARY_DEVICES_ENTITIES.map(device => updateDeviceSummary(openai, ha, states, device))); log('app','info',`Summary updated for ${SUMMARY_DEVICES_ENTITIES.length} devices.`); + // Device Summaries, Summary + // =================================================================================================================== + const deviceSummaries = states.filter(state => state.entity_id.startsWith('sensor.'+SUMMARY_DEVICE_ENTITY_PREFIX)); + const deviceSummariesStates = deviceSummaries.map(state => state.attributes.content); + console.log(deviceSummariesStates); + + + // Sleep till next run // =================================================================================================================== // TODO: Implement publish/subscribe pattern to avoid polling @@ -38,6 +47,50 @@ async function main() { await sleep(UPDATE_INTERVAL); } + //console.log(summary); + + //prompt.push(briefing); + //prompt.push( + + // const blocks = [ + // // All on lights + // { + // iterators: ["light"], + // filters : [{key: 'domain', value: 'iterator'}], + // returnProperty : 'attributes.friendly_name', + // }, + // // All on motions, door, windows + // { + // iterators: ["motion","door","window"], + // filters : [{key: 'attributes.device_class', value: 'iterator'}], + // returnProperty : 'attributes.friendly_name', + // }, + // // All vacuum and locks, no matter the state + // { + // iterators: ["vacuum","lock","weather"], + // filters : [{key: 'domain', value: 'iterator'}], + // returnProperty : 'attributes.friendly_name', + // }, + // // All temperature & humidity sensors + // { + // iterators: ["temperature",'humidity'], + // filters : [{key: 'domain', value: 'sensor'},{key: 'attributes.device_class', value: 'temperature'}], + // returnProperty : 'attributes.friendly_name', + // }, + // ]; + + // for (const block of blocks) { + // for (const iterator of block.iterators) { + // const cleanedFilter = block.filters.map(filter => { + // return {key: filter.key.replace('iterator', iterator), value: filter.value.replace('iterator', iterator)} + // }); + // const entities = getEntities(states, cleanedFilter, block.returnProperty, true); + // prompt.push(`${iterator} ${entities.join(',')}`); + // } + // } + //console.log(prompt.join('\n\n')); + + } main(); \ No newline at end of file diff --git a/src/summary.ts b/src/summary.device.ts similarity index 73% rename from src/summary.ts rename to src/summary.device.ts index 2d58aa3..44de37f 100644 --- a/src/summary.ts +++ b/src/summary.device.ts @@ -17,14 +17,16 @@ export function getPromptForDeviceSummary(states, deviceName, paramOptions) { if(!states || states.length === 0) { throw new Error('States must be an array'); } - const deviceEntities = states.filter(el => el.entity_id.includes(deviceName)); + const deviceEntities = states + .filter(el => el.entity_id.includes(deviceName)) + .filter(el => !el.entity_id.includes(SUMMARY_DEVICE_ENTITY_PREFIX)); // Ignoring ourselves if(deviceEntities.length === 0) { log('app','warn',`No entity found for device ${deviceName}`); return null; } const summary = summarizeEntities(deviceEntities); const defaultOptions = { - length: 200, + length: 100, customPrompt: '', } const options = { @@ -34,14 +36,19 @@ export function getPromptForDeviceSummary(states, deviceName, paramOptions) { let prompt = []; const briefing = ` - Following is an overview of the state of a device. - Summarize it in less than ${options.length} characters. - Do not start nor include the name of the device. Do not start by "summary" or "briefing" or "device". - Only keep the most important information, do not give everything you have. ALWAYS keep it short. - Prioritize errors, last state changes, and then only the rest. - Write a single sentence, as if your were do not use bullets point, separator, line breaks. - Ignore device containing "summary" or "briefing" or "device" in their name. + BRIEFING : + Following is an overview of the state of a device. + You must filter out the noise and respond with a single sentence, no bullets points summary of maximum ${options.length} characters. + Keep the summary short no matter the amount of data you are given. + Only keep errors, warnings, statuses, entities called "Dock Status" and other important information. + Dont write "error" or "warning" if you dont see any. + Do not introduce with "Summary" or "Résumé", just the content itself. + Round float value to integer. Transform any duration in seconds to minutes. + Ignore any "unknown" or "unavailable" state. ${options.customPrompt} + CONTEXT : + For relative date calculation, today is ${new Date().toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', timeZone: 'UTC' })}. + DATA : `; prompt.push(briefing); prompt = [...prompt, ...summary]; @@ -62,6 +69,7 @@ export function getPromptForDeviceSummary(states, deviceName, paramOptions) { export async function getDeviceSummary(openai, ha, states, deviceName, paramOptions) { const prompt = getPromptForDeviceSummary(states, deviceName, paramOptions); if(!prompt) { + log('app','warn',`No prompt found for device ${deviceName}`); return null; } const summary = getFirstChatResponse(openai, prompt); @@ -82,6 +90,7 @@ export async function getDeviceSummary(openai, ha, states, deviceName, paramOpti export async function updateDeviceSummary(openai, ha, states, deviceName, paramOptions) { const summary = await getDeviceSummary(openai, ha, states, deviceName, paramOptions); if(!summary) { + log('app','warn',`No summary found for device ${deviceName}`); return null; } return ha.states.update('sensor', `${SUMMARY_DEVICE_ENTITY_PREFIX}${deviceName}`, {