A portable power station says "1,000 Wh" on the box, but nobody gets 1,000 Wh of AC output from it. While building a runtime calculator, I had to decide how to turn a spec sheet into a number people can plan around. Here is the model I ended up with, and why every choice leans conservative.
The core idea: subtract losses, then divide
Runtime is just energy divided by power. The work is in being honest about both sides of that division.
Energy side. The rated watt-hours are measured at the cell level. You can't pull all of it out: the battery management system stops discharge before the cells are truly empty. I default to using 90% of rated capacity.
Power side. If your load is an AC device, the inverter has to convert DC from the battery. Inverters aren't perfect, so I assume 88% efficiency. The inverter also burns power just by being switched on, even with nothing plugged in. I assume a 10 W idle draw and add it to the load.
The formula
function runtimeHours({ ratedWh, loadW, usableShare = 0.9, inverterEff = 0.88, idleW = 10 }) {
const usableWh = ratedWh * usableShare;
const drawW = loadW / inverterEff + idleW; // what the battery really supplies
return Math.floor((usableWh / drawW) * 10) / 10; // always round down
}
runtimeHours({ ratedWh: 1000, loadW: 100 }); // 7.2 h, not 10 h
For a 100 W load on a 1,000 Wh unit: usable energy is 900 Wh, the battery supplies about 123.6 W (100 / 0.88 + 10), so you get 7.28 hours, which floors to 7.2. It is nowhere near the 10 hours the naive division suggests.
Fridges are not constant loads
A fridge's nameplate wattage is what it draws while the compressor runs. The compressor cycles, so over a day it runs about a third of the time. I model that as a duty cycle of roughly 33%:
const fridgeAvgW = fridgeRunningW * 0.33;
Treating a 150 W fridge as a constant 150 W would underestimate runtime by a factor of three. Treating it as 50 W forever ignores startup surge, which is a separate check on whether the inverter can start the compressor at all.
One number is a lie, so show a range
Real conditions vary: temperature, battery age, how hard devices actually pull. Instead of a single figure I output three:
- Cautious: pushes the assumptions the wrong way (lower usable share, lower efficiency, higher idle draw).
- Typical: the defaults above.
- Optimistic: assumptions nudged the other way.
const scenarios = {
cautious: { usableShare: 0.85, inverterEff: 0.85, idleW: 15 },
typical: { usableShare: 0.90, inverterEff: 0.88, idleW: 10 },
optimistic: { usableShare: 0.95, inverterEff: 0.92, idleW: 8 },
};
The spread is the useful part. If the cautious case still covers your outage, you're fine. If only the optimistic one does, you know to buy more capacity or cut loads.
Why always round down
Rounding a runtime up by even half an hour can mean a CPAP machine or a router dies at 3 a.m. Rounding down costs the user nothing but a little pessimism. I apply Math.floor at the very last step, never in the middle of the calculation, so errors don't compound.
Things I deliberately don't do
- I don't invent numbers. If a manufacturer doesn't publish a spec, the field stays empty rather than being guessed.
- I don't hide defaults. Every assumption is printed next to the result so you can disagree with it.
Try it
I put this model into a free calculator, Kilohour, so you can check the math against your own devices. If you think any default is off, I'd like to hear which one and why. These are defaults, not laws of physics, and measured data from real units would improve them.
Top comments (0)