Paging all DESSies: Who wants to actively participate in a community DESS fork effort?

I never tried it but I am pretty sure pushing schedules in works the same way as fetching schedules. You can try this fetch flow and adapt it to ‘Modify Dynamic ESS Schedules’ instead of ‘Fetch Dynamic ESS Schedules’ after changing the fetched object to your liking of course.

I use fetch to store the schedule in the global context, then function nodes to extract metric, store them in the global context as well and use the msg.timeslot for further extraction of values of interest of the current timeslot. At the moment we use it to set corrected target SoC values for DESS execution process and to force the scheduler to reach a maxSoC target (when the new prices roll in, read the scheduled maxSoc, then increase/decrease minSoC every hour until maxSoC matches a set targetMaxSoC (of 95% for instance) or other conditions such a forward SoC average, depending on forward price (low price high average, high price low average). It lets DESS do the heavy lifting of calculating the short term (quart hourly) buy and sell slots, while keeping control over the medium term (1 to 2 days) general strategy. I call it a Node-RED SoC Monkey :wink:,

/**
 * Processes schedule object to compute length, start/end times, min/max with indices, and averages.
 * Outputs single msg.payload with all metrics; stores in flow context.
 * @param {Object} msg Node-RED message with payload.schedule (array of [timestamp, value] pairs)
 * @returns {Object} msg with payload {length, startTime, endTime, minIndex, minTime, minValue, maxIndex, maxTime, maxValue, currentIndex, currentTime, currentValue, fullAverage, forwardAverage}
 */

let payload = msg.payload   //usually timestamp

let schedules_key = 'vrm_soc_plan'
let topic = 'vrmdess_' + schedules_key

let global_get = 'installations.fetch-dynamic-ess-schedules'

let get_schedules = global.get( global_get )
let is_schedules = !( get_schedules == undefined || get_schedules == null )

let get_records = null

if ( is_schedules )
{
    get_records = get_schedules.records[schedules_key]
}
let schedule = get_records

//

const length = schedule.length; 
 
if (!length || !Array.isArray(schedule)) {
    node.warn("Invalid or empty schedule array");
    msg.payload = { error: "Invalid or empty schedule array" };
    return msg;
}

const currentTimeSec = Math.floor(Date.now() );  // / 1000);
let startTime = null;
let endTime = null;
let minIndex = null;
let minTime = null;
let minValue = null;
let maxIndex = null;
let maxTime = null;
let maxValue = null;
let minfwdIndex = null;
let minfwdTime = null;
let minfwdValue = null;
let maxfwdIndex = null;
let maxfwdTime = null;
let maxfwdValue = null;
let currentIndex = null;
let currentTime = null;
let currentValue = null;
let fullSum = 0;
let fullCount = 0;
let forwardSum = 0;
let forwardCount = 0;

for (const [idx, [timestamp, value]] of schedule.entries()) {
    // Validate data pair
    if (!Array.isArray([timestamp, value]) || typeof timestamp !== "number" || !Number.isInteger(timestamp) || typeof value !== "number" || isNaN(value)) {
        node.warn(`Invalid data at schedule[${idx}]`);
        continue;
    }
    // Update start/end times
    if (startTime === null || timestamp < startTime) startTime = timestamp;
    if (endTime === null || timestamp > endTime) endTime = timestamp;
    // Update min/max
    if (minValue === null || value < minValue) {
        minIndex = idx;
        minTime = timestamp;
        minValue = value;
    }
    if (maxValue === null || value > maxValue) {
        maxIndex = idx;
        maxTime = timestamp;
        maxValue = value;
    }
    // Update averages
    fullSum += value;
    fullCount++;
    if ( (timestamp + 900*1000) >= currentTimeSec) {
        if (currentIndex === null) {
            currentIndex = idx;
            currentTime = timestamp;
            currentValue = value;
        }
        // Update minfwd/maxfwd
        if (minfwdValue === null || value < minfwdValue) {
            minfwdIndex = idx;
            minfwdTime = timestamp;
            minfwdValue = value;
        }
        if (maxfwdValue === null || value > maxfwdValue) {
            maxfwdIndex = idx;
            maxfwdTime = timestamp;
            maxfwdValue = value;
        }
        forwardSum += value;
        forwardCount++;
    }
}

// Compute metrics
const fullAverage = fullCount > 0 ? fullSum / fullCount : 0;
const forwardAverage = forwardCount > 0 ? forwardSum / forwardCount : 0;

// Validate results
if (length === 0 || startTime === null || minValue === null) {
    node.warn("No valid data found");
    msg.payload = { error: "No valid data found" };
    return msg;
}

// Set output
const result = {
    length,
    startTime,
    endTime,
    minIndex,
    minTime,
    minValue,
    maxIndex,
    maxTime,
    maxValue,
    minfwdIndex,
    minfwdTime,
    minfwdValue,
    maxfwdIndex,
    maxfwdTime,
    maxfwdValue,
    currentIndex,
    currentTime,
    currentValue,
    fullAverage,
    forwardAverage
};
//msg.payload = result;

// Store in flow context
//flow.set("scheduleMetrics", result);
global.set("vrm_soc_plan", result);

msg.timeslot = currentIndex;
msg.timestamp = payload;
msg.topic = topic;
return msg;
//