"""Bounded browser adapter; original one-minute synthetic balance model.""" import json,math,time from pathlib import Path from moisture_kernel import simulate CONTROLS = {'transpiration_lit_kg_per_h': {'type': 'number', 'min': 0, 'max': 4, 'default': 2, 'step': 0.1, 'unit': 'kg/h'}, 'transpiration_dark_kg_per_h': {'type': 'number', 'min': 0, 'max': 1, 'default': 0.35, 'step': 0.05, 'unit': 'kg/h'}, 'dehumidifier_capacity_kg_per_h': {'type': 'number', 'min': 0, 'max': 5, 'default': 2.5, 'step': 0.1, 'unit': 'kg/h'}, 'ventilation_m3_per_s': {'type': 'number', 'min': 0, 'max': 0.2, 'default': 0.08, 'step': 0.01, 'unit': 'm³/s'}, 'outside_rh_pct': {'type': 'number', 'min': 20, 'max': 90, 'default': 60, 'step': 5, 'unit': '%'}} SPEC = {'state': 'rh_pct', 'initial': 'initial_rh_pct', 'unit': '%', 'metrics': [('minimum_rh_pct', '%'), ('maximum_rh_pct', '%'), ('final_rh_pct', '%'), ('condensed_kg', 'kg'), ('transpiration_latent_load_kwh', 'kWh latent'), ('absolute_water_balance_residual_kg', 'kg')], 'totals': ['transpired_kg', 'vented_net_kg', 'dehumidified_kg', 'condensed_kg'], 'total_unit': 'kg'} def run(name,p): if name != 'moisture' or set(p)!=set(CONTROLS):raise ValueError('Unknown experiment or parameters') for key,c in CONTROLS.items(): v=p[key] if type(v) not in (int,float) or not math.isfinite(v) or not c['min']<=v<=c['max']:raise ValueError('Parameter out of range: '+key) ticks=(v-c['min'])/c['step'] if abs(ticks-round(ticks))>1e-8:raise ValueError('Parameter off step: '+key) start=time.perf_counter() config=json.loads(Path(__file__).with_name('config.json').read_text()) config.update(p);result=simulate(config) hours=[0.]+[r['hour'] for r in result['hourly']] values=[config[SPEC['initial']]]+[r[SPEC['state']] for r in result['hourly']] series=[dict(id='room_state',x=hours,y=values,x_unit='h',y_unit=SPEC['unit']),dict(id='daily_flows',x=list(range(1,len(SPEC['totals'])+1)),y=[result[k] for k in SPEC['totals']],x_unit='term',y_unit=SPEC['total_unit'])] if name=='thermal': from thermal_kernel import outside_temperature # The kernel's hourly outside samples are from the last step start. series.append(dict(id='outside_state',x=hours,y=[outside_temperature(config,0)]+[r['outside_temp_c'] for r in result['hourly']],x_unit='h',y_unit='°C')) return dict(metrics=[dict(id=k,value=result[k],unit=u) for k,u in SPEC['metrics']],series=series,stdout='',provenance=dict(data_type='synthetic',compute_ms=(time.perf_counter()-start)*1000)) if __name__=='__main__': import argparse parser=argparse.ArgumentParser(description=__doc__);parser.add_argument('--parameters',default=json.dumps({k:c['default'] for k,c in CONTROLS.items()})) print(json.dumps(run('moisture',json.loads(parser.parse_args().parameters)),allow_nan=False))