// Storage dispatch review as a MajorityVoting swarm: three forecasters each
// propose a charge and discharge plan from the same forecast, and the API
// returns the majority plan with the dissent. The storage operator reviews
// it before anything is scheduled.
const SWARMS_API = "https://api.swarms.world/v1/swarm/completions"
type Forecast = { date: string; priceByHour: number[]; netLoadByHour: number[]; socPct: number; capacityMwh: number }
const forecaster = (name: string, model: string, focus: string) => ({
agent_name: name,
system_prompt:
"You plan a grid battery's day. Focus: " + focus + " " +
"Return a 24-row table of hour, action (CHARGE, HOLD, DISCHARGE) and MW, then two sentences on why. " +
"The plan is a recommendation for the storage operator, who schedules it.",
model_name: model,
max_loops: 1,
})
export async function reviewStoragePlan(f: Forecast) {
const res = await fetch(SWARMS_API, {
method: "POST",
headers: {
"x-api-key": process.env.SWARMS_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "Storage Dispatch Review",
description: "Three independent plans for one battery day, majority wins.",
swarm_type: "MajorityVoting",
task:
"Plan " + f.date + " for a " + f.capacityMwh + " MWh battery starting at " + f.socPct + " percent state of charge.\n" +
"Day-ahead price by hour ($/MWh): " + f.priceByHour.join(", ") + "\n" +
"Net load forecast by hour (MW): " + f.netLoadByHour.join(", "),
agents: [
forecaster("Price Forecaster", "gpt-5.4", "capture the price spread between the midday trough and the evening peak."),
forecaster("Load Forecaster", "gemini-2.5-pro", "discharge where the net load ramp is steepest, charge where it is flattest."),
forecaster("Degradation Reviewer", "claude-sonnet-5-5", "keep cycles and depth of discharge inside the warranty limits even if revenue is lower."),
],
max_loops: 1,
}),
})
if (!res.ok) throw new Error("Swarms API " + res.status + ": " + (await res.text()))
const data = await res.json()
return { plan: data.output as string, usage: data.usage }
}
const price = [38, 35, 33, 32, 33, 41, 58, 72, 61, 44, 29, 18, 12, 11, 15, 27, 52, 118, 164, 142, 96, 71, 55, 44]
const netLoad = [21, 20, 19.5, 19.2, 19.6, 21.5, 24, 26, 25, 22, 18, 15, 13.5, 13, 14, 17, 23, 29, 31, 30, 27, 25, 23, 22]
reviewStoragePlan({ date: "2026-11-08", priceByHour: price, netLoadByHour: netLoad, socPct: 42, capacityMwh: 400 }).then((r) => {
console.log(r.plan)
console.log(r.usage)
})