"""Reduced-order structural screens and synthetic indexed-changeover trials."""
import itertools,json,math
import numpy as np
import matplotlib;matplotlib.use('Agg')
import matplotlib.pyplot as plt
from completion_util import ROOT,OUT,csv_file,check,table,finish

def bending(c,g,force):
 if force<0:raise ValueError('Force cannot be negative')
 span=max(g['station_x'])-min(g['station_x']);I=g['rail_height']*g['rail_thickness']**3/12
 rail=force*span**3/(48*c['steel_E_MPa']*I);stress=force*span/4*(g['rail_thickness']/2)/I
 Is=c['strap_width_mm']*c['strap_thickness_mm']**3/12
 strap=(force/2)*c['strap_length_mm']**3/(3*c['steel_E_MPa']*Is)
 Ip=c['post_x_width_mm']*c['post_y_thickness_mm']**3/12
 post=(force/2)*c['post_height_mm']**3/(3*c['steel_E_MPa']*Ip)
 # Axial elastic shaft stretch over conservative half span; lateral force is along shaft axis.
 shaft=(force/2)*(g['shaft_length']/2)/(c['steel_E_MPa']*math.pi*g['shaft_diameter']**2/4)
 clearance=g['running_clearance_per_side']-sum(g['tolerances'].values())-rail-strap-post-shaft
 return {'side_load_N':force,'rail_deflection_mm':rail,'strap_deflection_mm':strap,'post_deflection_mm':post,'shaft_axial_mm':shaft,'total_side_movement_mm':rail+strap+post+shaft,'rail_bending_MPa':stress,'remaining_side_clearance_mm':clearance,'clearance_accepted':clearance>=c['required_residual_clearance_mm'],'stress_accepted':stress<=c['steel_yield_MPa']/c['structural_design_factor']}

def matching(settings,selected):return len(settings)==4 and all(x==selected for x in settings)

def main():
 c=json.loads((ROOT/'completion-inputs.json').read_text());g=json.loads((ROOT/'inputs.json').read_text());rng=np.random.default_rng(c['seed']);checks=[]
 cases=[bending(c,g,F) for F in [0,15,c['service_side_load_N'],60,100,c['jam_side_load_N']]];csv_file('lateral-load-envelope.csv',cases)
 service=bending(c,g,c['service_side_load_N']);jam=bending(c,g,c['jam_side_load_N'])
 checks.append(check('service-clearance','Nominal side load retains allocated carton clearance',f">= {c['required_residual_clearance_mm']} mm",service['remaining_side_clearance_mm'],service['clearance_accepted'],'Linear beam superposition: rail, two hangers, frame posts and shaft axial stretch'))
 checks.append(check('service-stress','Reference 304 rail stays below declared elastic screening allowable',f"<= {c['steel_yield_MPa']/c['structural_design_factor']:.4f} MPa",service['rail_bending_MPa'],service['stress_accepted'],'Simply supported rail under central side load'))
 checks.append(check('jam-rejection','Large jam load is outside the clearance envelope','reject 150 N',jam['remaining_side_clearance_mm'],not jam['clearance_accepted'],'Same model with explicit overload'))
 preload=c['clamp_torque_Nm']*1000/(c['nut_factor']*c['bolt_diameter_mm']);hold=2*preload*c['minimum_friction'];margin=hold/(c['slip_design_factor']*c['service_side_load_N'])
 checks.append(check('clamp-slip','Two clamps per rail resist nominal side load with factor 2','margin >= 1',margin,margin>=1,'F=T/(Kd); no credit for plungers carrying service load'))
 # Radial shaft bending from vertical load is separate from the Y-directed guide load.
 Ishaft=math.pi*g['shaft_diameter']**4/64;vertical=2*c['vertical_carriage_load_N']*g['shaft_length']**3/(48*c['steel_E_MPa']*Ishaft)
 checks.append(check('shaft-vertical','Conservative midspan vertical shaft sag fits the existing 8 mm overhead margin','< 8 mm',vertical,vertical<8,'Two 25 N carriage weights conservatively combined at shaft midspan'))
 trial=[];t=g['tolerances'];static_keys=['index_hole_position','index_pin_position','carriage_to_rail','wear_strip_thickness'];dynamic_keys=['pin_hole_radial_clearance','wear_allowance'];trialnum=0
 for width in g['carton_widths']:
  # Independent left/right supports, two stations per rail; static offsets do not redraw at each changeover.
  fixed=[sum(rng.uniform(-t[k],t[k]) for k in static_keys) for _ in range(4)]
  for op in range(1,c['operators']+1):
   for direction in c['directions']:
    for cycle in range(1,c['cycles_each_direction']+1):
     trialnum+=1;errors=[fixed[i]+direction*t['pin_hole_radial_clearance']+rng.uniform(-t['wear_allowance'],t['wear_allowance']) for i in range(4)]
     carton=rng.uniform(-t['carton_half_width'],t['carton_half_width']);minimum=g['running_clearance_per_side']-max(errors)-carton-service['total_side_movement_mm']
     trial.append({'trial':trialnum,'carton_width_mm':width,'operator':op,'approach_direction':direction,'cycle':cycle,**{f'support_{i+1}_offset_mm':float(e) for i,e in enumerate(errors)},'max_rail_skew_mm':float(max(abs(errors[0]-errors[1]),abs(errors[2]-errors[3]))),'minimum_loaded_side_clearance_mm':float(minimum),'synthetic':True})
 csv_file('synthetic-changeovers.csv',trial)
 checks.append(check('repeatability-count','Three widths, three operators, two approach directions, ten cycles','180 changeovers',len(trial),len(trial)==180,'Seeded bounded synthetic setup sequence'))
 checks.append(check('repeatability-clearance','Generated loaded setup trials retain positive clearance',f">= {c['required_residual_clearance_mm']} mm",min(r['minimum_loaded_side_clearance_mm'] for r in trial),min(r['minimum_loaded_side_clearance_mm'] for r in trial)>=c['required_residual_clearance_mm'],'Fixed manufacturing error plus directional pin contact and bounded reseating'))
 settings=[]
 for requested in g['carton_widths']:
  for values in itertools.product(g['carton_widths'],repeat=4):settings.append({'requested_width_mm':requested,'left_front':values[0],'left_rear':values[1],'right_front':values[2],'right_rear':values[3],'restart_permitted_by_procedure':matching(values,requested)})
 csv_file('indicator-state-table.csv',settings);allowed=sum(r['restart_permitted_by_procedure'] for r in settings)
 checks.append(check('wrong-setting-table','Every mixed or wrong-size indication is rejected by setup rule','3 allowed states out of 243',{'allowed':allowed,'total':len(settings)},allowed==3 and len(settings)==243,'Exhaustive Cartesian enumeration; this is a written gate, not an installed interlock'))
 worn=service['remaining_side_clearance_mm']-c['worn_extra_radial_mm'];checks.append(check('wear-rejection','Added 0.8 mm guide error consumes loaded clearance','reject worn state',worn,worn<c['required_residual_clearance_mm'],'Worst-case tolerance plus load deflection'))
 compliance=bending(c,g,1)['total_side_movement_mm'];maxload=(g['running_clearance_per_side']-sum(t.values())-c['required_residual_clearance_mm'])/compliance
 # A distinct ideal beam benchmark guards units and section orientation.
 ref=100*100**3/(48*200000*(10*10**3/12));checks.append(check('beam-reference','100 mm square-section test beam hand value','0.0125 mm',ref,abs(ref-.0125)<1e-12,'Euler-Bernoulli exact simply supported point-load formula'))
 fig,ax=plt.subplots(figsize=(8,4));xx=np.linspace(0,160,101);yy=[bending(c,g,F)['remaining_side_clearance_mm'] for F in xx];ax.plot(xx,yy,color='#396554');ax.axhline(.25,color='#9a563a',ls='--',label='Allocated minimum 0.25 mm');ax.axvline(30,color='#7d6b86',ls=':',label='30 N reference service load');ax.set(xlabel='Central side load per guide rail (N)',ylabel='Remaining worst-case side clearance (mm)');ax.legend();fig.tight_layout();fig.savefig(OUT/'load-envelope.png',dpi=140);plt.close(fig)
 limits=['Loads, friction and material data are declared assumptions. These results do not establish carton impact, conveyor speed, measured operator repeatability or changeover-time savings.','Linear beam models omit local carriage deformation, slot weakening, weld and bolt flexibility, nonlinear contact, fatigue and load dynamics. The calculated load envelope is a screening result, not a machine rating.','The indicator truth table tests a written restart rule. No installed sensor or electrical interlock prevents a wrong setting in this mechanical design.','The ground shaft and purchased clamps remain dimensioned reference envelopes. Procurement, retaining features and assembly details must match supplier hardware if fabricated.','The synthetic trials keep fixed errors correlated across repetitions; their bounded distributions are assumed and cannot establish production capability.']
 sections=[('Decision',f'<p>At the assumed 30 N central side load, the worst-case remaining carton clearance is {service["remaining_side_clearance_mm"]:.3f} mm, above the allocated 0.25 mm. The reduced-order clearance boundary is {maxload:.1f} N. A 150 N jam and the added 0.8 mm wear state are rejected. The force result does not establish an impact-load or machine safety rating.</p>'),('Inputs',table([{'parameter':k,'value':v} for k,v in c.items()])),('Structural calculation','<p>The 304 stainless rail is a simply supported beam with 360 mm support spacing. Lateral bending uses I = 30 x 6 cubed / 12 mm to the fourth, rather than the strong vertical axis. Central load deflection is F L cubed / (48 E I). The hanging straps and posts are conservative cantilevers, each carrying half the rail load. The shaft sees axial side load; a separate vertical sag case is evaluated. Joint compliance is omitted and retained as a model boundary.</p>'+table(cases)+'<img src="load-envelope.png" alt="Side load versus remaining carton clearance"/>'),('Clamping and repeatability',f'<p>At 3 N m and K = 0.25, each M8 clamp preload is {preload:.0f} N. Two clamps on a rail provide {hold:.0f} N nominal minimum-friction resistance, a margin of {margin:.2f} against twice the 30 N reference load. The 180 synthetic changeovers include both approach directions, fixed support manufacturing errors and reseating variation. The minimum synthetic loaded clearance is {min(r["minimum_loaded_side_clearance_mm"] for r in trial):.3f} mm. No elapsed time or measured savings are invented.</p>'),('Wrong-setting exercise','<p>All 3 to the fourth four-indicator combinations are enumerated for each of three requested carton sizes. Only the three exact matches are released by the setup rule. The other 240 rows are rejected. This verifies the procedure logic; an operator can still bypass a written procedure.</p>'),('Data files','<p>All loads are in lateral-load-envelope.csv; the 180 setup observations are in synthetic-changeovers.csv; every indicator combination is in indicator-state-table.csv.</p>')]
 finish('Conveyor load and changeover study',checks,sections,limits,['complete_validation.py','completion_util.py','completion-inputs.json','inputs.json','model.py'],{'synthetic_changeovers':len(trial),'indicator_states':len(settings),'service_clearance_mm':service['remaining_side_clearance_mm'],'screening_side_load_boundary_N':maxload})
if __name__=='__main__':main()
