# Предметные проверки во встроенном Python; новых полевых решений не заменяет.
with open('case-data.json') as f: data=json.load(f)
cid=data['id']
def load(name):
    with open(name) as f:return json.load(f)
def rows(name):
    # В некоторых сборках MicroPython отсутствует csv: таблицы проверяются
    # простым разбором числового CSV без кавычек.
    with open(name) as f:lines=[line.strip() for line in f.read().split('\n') if line.strip()]
    names=lines[0].split(',')
    return [{k:float(v) for k,v in zip(names,line.split(','))} for line in lines[1:] if line]
if cid=='spm-airgap-losses':
    rs=rows('reference/losses.csv'); ts=rows('reference/torque.csv')
    check('Число скоростей',len(rs),80,0);check('Число положений',len(ts),180,0)
    for r in rs:check('Сумма потерь '+str(r['speed_rpm']),r['total_W'],sum(r[k] for k in ('rotor_W','stator_W','magnet_W','ohmic_W','proximity_W')),1e-10)
elif cid=='mytransformer-120v':
    r=load('reference/base_result.json')
    check('Активный баланс',r['input_power_W']-r['load_power_W']-r['volume_losses']['all']['total_W'],0,.001)
    check('Амплитуда/действующее U',r['primary_voltage_V']['peak_magnitude']/math.sqrt(2),r['primary_voltage_V']['rms_magnitude'],1e-10)
elif cid=='meeker-twelve-frequency':
    rs=rows('reference/sweep.csv');check('Число частот',len(rs),12,0)
    for r in rs:
        check('Секторный момент '+str(r['frequency_Hz']),r['full_torque_Nm'],4*r['quadrant_torque_Nm'],1e-12)
        check('Секторное Psi '+str(r['frequency_Hz']),r['full_flux_re_Wbturn'],4*r['flux_re_Wbturn'],1e-12)
elif cid=='spm-full-sector':
    rs=load('reference/measurements.json')['full_period']['rows']
    for expected in data['table']['rows']:
        n=expected['N'];v=[r['circuits']['Фаза А'][2] for r in rs[::len(rs)//n]]
        for k,key in ((1,'psi1'),(3,'psi3'),(5,'psi5')):
            s=sum(vj*complex(math.cos(-2*math.pi*k*j/n),math.sin(-2*math.pi*k*j/n)) for j,vj in enumerate(v))
            check('Независимое ДПФ '+str(n)+' '+str(k),2*abs(s)/n,expected[key],1e-10)
elif cid=='incremental-frozen-inductance':
    x=load('reference/measurements.json')
    for p,q in zip(x['native_matrix']['points'],x['derived_checks']['points']):
        for s,t in zip(p['steps'],q['steps']):
            for j in range(2):
                for k in range(2):
                    name=('Первичная обмотка','Вторичная обмотка')[j]
                    actual=(s['raw'][2*k+1]['circuits'][name][2]-s['raw'][2*k]['circuits'][name][2])/(2*t['h_A'])
                    check('Центральная производная '+str(j)+str(k),actual,t['L_H'][j][k],1e-12)
elif cid=='nonlinear-transformer-newton':
    x=load('reference/measurements.json')
    for name in ('first','continuation'):
        s=x['two_voltages'][name]
        check('Цепной остаток '+name,max(abs(r) for r in s['final_residual_components_V'])/s['source_amplitude_V'],0,1e-5)
elif cid=='induction-identification':
    x=load('reference/measurements.json')['identification']
    for name in ('constant_rotor_branch','frequency_corrected_rotor_branch'):
        s=x[name];p=s['parameters']
        for r in s['heldout']:
            f=r['frequency_Hz'];w=2*math.pi*f;L=p['L_magnetizing_sector_H'];R=p['R_rotor_referred_sector_ohm']*(1+p['R_frequency_coefficient_per_Hz2']*f*f)
            pred=1j*w*p['L_leak_sector_H']+(1j*w*L*R)/(R+1j*w*L)
            check('Независимый прогноз Z '+name+' '+str(f),pred,r['predicted_Z_ohm'],1e-9)
elif cid=='ipm-nonlinear-mtpa':
    x=load('reference/pm-data.json')
    for c in x['mtpa']['curves']:
        for s in c['fine_angular_checks']:
            check('Средний момент MTPA',sum(r['torque_Nm'] for r in s['rows'])/len(s['rows']),s['average_torque_Nm'],1e-12)
            for r in s['rows']:
                v=list(r['requested_currents_A'].values());check('Окружность тока',2/3*sum(q*q for q in v),c['I_A']**2,1e-10)
elif cid=='steel-loss-calibrated':
    with open('steel-cells.csv') as f:lines=[line.strip() for line in f.read().split('\n') if line.strip()]
    keys=lines[0].split(',');sums={}
    alpha=math.log(2.5/1.05)/math.log(1.5)
    for line in lines[1:]:
        r=dict(zip(keys,line.split(',')));v=float(r['mass_kg'])*1.05*float(r['B50_major_peak_T'])**alpha
        check('Удельные потери ячейки',v,float(r['P50_estimate_W']),1e-10)
        sums[r['regime']]=sums.get(r['regime'],0)+v
    for r in data['table']['rows']:
        if r['N']==48:check('Интеграл по стали '+r['mode'],sums['nominal' if r['mode']=='Номинальный' else 'demagnetizing'],r['p'],1e-8)
elif cid=='pm-demagnetization-margin':
    for r in data['table']['rows']:check('Графический запас '+str(r['temp']),r['hmin']-r['hk']-20,r['margin'],1e-9)
elif cid=='copper-skin-proximity':
    delta=math.sqrt(2/(2*math.pi*5000*4*math.pi*1e-7*58e6))*1000
    for r in data['table']['rows']:check('Сетка от глубины проникновения',delta/r['division'],r['mesh'],1e-12)
elif cid=='srm-map-dynamics':
    x=load('reference/measurements.json')
    for s in x['derived']['srm_commutation']:
        r=s['simulation']['rows'][-1]
        bal=r['source_energy_J']-r['copper_energy_J']-r['magnetic_energy_J']-r['mechanical_work_J']
        check('Баланс динамики SRM',bal,r['energy_residual_J'],1e-9)
