"""Integrate wall-power CSV time_s,power_W. One continuous phase per file. python3 power_log.py server-power-synthetic.csv Reject missing/nonmonotonic samples; power must be finite and nonnegative. The default max gap 2 seconds assumes a 1 Hz logger; change it explicitly. """ import argparse,csv,json,math def integrate(rows,max_gap=2.): t=[float(r['time_s']) for r in rows];p=[float(r['power_W']) for r in rows] if len(t)<2 or not all(math.isfinite(x) for x in t+p) or min(p)<0:raise ValueError('Need >=2 finite time/power samples; power >=0') gaps=[b-a for a,b in zip(t,t[1:])] if min(gaps)<=0 or max(gaps)>max_gap:raise ValueError('Nonmonotonic time or data gap; split the file') energy_Wh=sum(dt*(a+b)/2 for dt,a,b in zip(gaps,p,p[1:]))/3600 return {'duration_s':t[-1]-t[0],'energy_Wh':energy_Wh,'mean_W':energy_Wh*3600/(t[-1]-t[0]),'peak_sampled_W':max(p),'samples':len(t)} if __name__=='__main__': p=argparse.ArgumentParser();p.add_argument('csv');p.add_argument('--max-gap',type=float,default=2.);a=p.parse_args() with open(a.csv,newline='') as f:rows=list(csv.DictReader(f)) print(json.dumps(integrate(rows,a.max_gap),indent=2))