"""Независимые μr/σ-пробы открытых пазов: встроенный MicroPython FEMM 5.

Откройте этот файл через Run Script. По умолчанию читается точная опубликованная
контрольная пара этапа05. TG_AUDIT_RESULT_FILE можно задать абсолютным путём
к собственному result.json завершённого этапа05/all с той же топологией24+12.
TG_AUDIT_OUTPUT_ROOT задаёт существующий каталог нового audit-calculated-NN.
Геометрия не строится и поле не решается; audit_open_slots.py читает готовую ANS.
"""
import os
import sys
import math
import json
import hashlib
import binascii

saved=globals().get('__file__','') or (sys.argv[0] if sys.argv else '')
TG_ROOT=globals().get('TG_ROOT',os.path.dirname(os.path.abspath(saved)) if saved else os.getcwd())
RESULT_FILE=globals().get('TG_AUDIT_RESULT_FILE',TG_ROOT+'/verified/result.json')
DEST=globals().get('TG_AUDIT_OUTPUT_ROOT',TG_ROOT)


def sha(path):
    h=hashlib.sha256()
    with open(path,'rb') as f:
        while True:
            data=f.read(65536)
            if not data:
                break
            h.update(data)
    return binascii.hexlify(h.digest()).decode()


with open(RESULT_FILE) as f:
    result=json.load(f)
if result.get('geometry_revision')!='open-slot-wedges-v2' or '05' not in result.get('results',{}):
    raise ValueError('Нужен result.json этапа05/all открытой редакции open-slot-wedges-v2')
contract=result['geometry_contract']
checkpoint=result['results']['05']
source=os.path.dirname(os.path.abspath(RESULT_FILE))
model=source+'/'+checkpoint['model']
solution=source+'/'+checkpoint['solution']
core=TG_ROOT+'/audit_open_slots.py'
before={'model':sha(model),'solution':sha(solution),'result':sha(RESULT_FILE),'core':sha(core)}
for key in ['model','solution']:
    expected=checkpoint.get(key+'_sha256')
    if expected and before[key]!=expected:
        raise ValueError('SHA-256 опубликованного входа не совпал: '+key)
points=[]
for kind in ['stator','rotor']:
    centres=contract['stator_slot_centres_deg'] if kind=='stator' else contract['rotor_slot_centres_local_deg']
    profile=contract['profiles'][kind]
    for k,base_angle in enumerate(centres):
        angle=base_angle if kind=='stator' else base_angle+checkpoint['rotor_angle_deg']
        radians=angle*math.pi/180
        eccentricity=0 if kind=='stator' else checkpoint['eccentricity_mm']
        for region in ['mouth','neck','wedge','copper']:
            local=profile['probes_local_mm'][region]
            x,y=local
            # Жёсткий поворот локального профиля; эксцентриситет добавляется
            # только по неподвижной оси x. Точка шлица остаётся связанной с зазором.
            world=[x*math.cos(radians)-y*math.sin(radians)+eccentricity,
                   x*math.sin(radians)+y*math.cos(radians)]
            points.append({'kind':kind,'region':region,'slot_index_1based':k+1,
                           'slot_centre_local_deg':base_angle,'slot_centre_absolute_deg':angle,
                           'local_point_mm':local,'point_mm':world,
                           'expected_relative_permeability':1,
                           'expected_conductivity_MSm':58 if region=='copper' else 0})
if len(points)!=144:
    raise ValueError('Этот контрольный маршрут рассчитан на24+12пазов и144точки')
index=1
while os.path.exists(DEST+'/audit-calculated-%02d'%index):
    index+=1
OUT=DEST+'/audit-calculated-%02d'%index
os.mkdir(OUT)
INPUT_SPEC=OUT+'/input-spec.json'
OUTPUT_RECEIPT=OUT+'/native-receipt.json'
spec={'geometry_revision':'open-slot-wedges-v2','rotor_angle_deg':checkpoint['rotor_angle_deg'],
      'eccentricity_mm':checkpoint['eccentricity_mm'],'model_absolute_path':os.path.abspath(model),
      'solution_absolute_path':os.path.abspath(solution),'points':points}
with open(INPUT_SPEC,'w') as f:
    json.dump(spec,f)
# Независимое ядро содержит только opendocument/mi_loadsolution и чтение свойств;
# его точные байты связаны с исходной нативной квитанцией через manifest/receipt.
exec(open(core).read(),globals())
after={'model':sha(model),'solution':sha(solution),'result':sha(RESULT_FILE),'core':sha(core)}
if before!=after:
    raise RuntimeError('Чтение изменило входные файлы')
with open(OUTPUT_RECEIPT) as f:
    native=json.load(f)
with open(OUT+'/replay-receipt.json','w') as f:
    json.dump({'schema':'femm5-open-slot-audit-replay-v1','geometry_revision':'open-slot-wedges-v2',
               'point_count':len(points),'input_sha256_before':before,'input_sha256_after':after,
               'inputs_unchanged':True,'field_rerun':False,'passed':native['passed'],
               'native_receipt_sha256':sha(OUTPUT_RECEIPT)},f)
print('AUDIT_REPLAY',OUT,native['passed'])
if not native['passed']:
    raise RuntimeError('Проверки материала открытых пазов не пройдены')
