"""Native numeric record definitions and evidence-scoped requirement snapshots.""" import json,collections,copy,csv from pathlib import Path P=Path(__file__).parent source=json.loads((P/'route-plan-with-ids.json').read_text(encoding='utf8')) bound=json.loads((P/'binding-plan-with-ids.json').read_text(encoding='utf8')) snap=json.loads((P/'before-parameter-snapshot.json').read_text(encoding='utf8')) stationsnap=json.loads((P/'before-station-snapshot.json').read_text(encoding='utf8')) routes={r['id']:r for r in source['routes']} routes.update({r['id']:r for r in bound['routes']}) units=[('TEMP_C','摄氏度'),('TIME_MIN','分钟'),('TIME_SEC','秒'),('PRESS_MPA','兆帕'),('SPEED_RPM','转/分钟'),('LENGTH_MM','毫米'),('PERCENT','百分比'),('MESH','目')] defs=[('TEMP','工艺温度','TEMP_C'),('TIME','处理时间','TIME_MIN'),('SEC','瞬时处理时间','TIME_SEC'),('PRESS','均质压力','PRESS_MPA'),('VAC','脱气真空度','PRESS_MPA'),('RPM','工艺转速','SPEED_RPM'),('FILL','灌装净含量','ML'),('PACK','内包装净含量','GM'),('SOLIDS','可溶性固形物','PERCENT'),('MESH','筛网目数','MESH'),('APERTURE','筛网孔径','LENGTH_MM'),('FE','金属探测铁试块直径','LENGTH_MM'),('NONFE','金属探测非铁试块直径','LENGTH_MM'),('SS','金属探测不锈钢试块直径','LENGTH_MM'),('WEIGHT','实际投料重量','KG'),('VOLUME','配制体积','LT'),('ENV_TEMP','作业环境温度','TEMP_C'),('HUMIDITY','作业环境相对湿度','PERCENT'),('COUNT','包装数量','EA'),('EXTRACT','提取次数','CI'),('SAMPLE','检验抽样数量','EA')] definitions=[{'key':k,'param_code':'PP-STP-'+k,'param_name':n,'unit_code':u,'param_type':1,'required':False,'remark':'生产现场实际记录;具体标准按当前产品规程及工序模板执行,不预填实际值。'} for k,n,u in defs] defaults={ '原料验收':['WEIGHT'],'领料脱包':['WEIGHT'],'称量':['WEIGHT'], '风选':['TIME'],'静电除毛':['TIME'],'金属探测':['FE','NONFE','SS'],'人工选别':['WEIGHT'], '微波杀菌':['TEMP','TIME'],'冷却':['TEMP','TIME'],'内包装':['PACK','COUNT'],'外包装':['COUNT'], '成品检验':['SAMPLE'],'成品入库':['COUNT'],'温水解冻':['TEMP','TIME'],'鼓泡清洗':['TIME'],'淋干':['TIME'], '破碎':['RPM'],'打浆':['APERTURE'],'预热保温':['TEMP','TIME'],'搅拌乳化剪切':['RPM','TIME'], '真空脱气':['VAC'],'均质':['PRESS'],'瞬时灭菌':['TEMP','SEC'],'热灌装':['TEMP','FILL'],'水浴灭菌':['TEMP','TIME'], '冷却风干':['TEMP'],'贴标':['COUNT'],'复水':['TEMP','TIME'],'过滤':['MESH'],'混合':['TIME'], '浸泡':['TIME'],'高温杀菌':['TEMP','SEC'],'粉碎':['MESH'],'过筛':['MESH'],'提取过滤':['TEMP','TIME','EXTRACT','MESH'], '配制':['WEIGHT','VOLUME'],'高剪切':['TIME','RPM'],'混合定容':['VOLUME'],'熟制':['TEMP','TIME'], '红枸杞原浆制备':['SOLIDS'],'黑果枸杞原浆制备':['SOLIDS'],'定容':['VOLUME'],'加热':['TEMP'], '煮提':['TEMP','TIME'],'浓缩':['SOLIDS'],'配制高剪切':['VOLUME','RPM','TIME']} def requirements(r,st): n=st['name'];d=r['document_id'];norm={};notes=[] if n=='微波杀菌': norm['TEMP']='55~65℃' if d in [1,11] else '50~60℃' if d in [1,11]:norm['TIME']='13~15min' elif n=='真空脱气':norm['VAC']='-0.07MPa;原文真空度,保留负号' elif n=='均质':norm['PRESS']='18~20MPa' elif n=='瞬时灭菌':norm={'TEMP':'100℃','SEC':'30s'} elif n=='热灌装': norm['TEMP']='≥87℃' fill={14:('30ml' if r['variant']=='袋装' else '50ml'),16:'35ml',17:'35ml',20:'30ml',21:'35ml',22:'35ml',23:'50ml',24:'30ml',25:('35ml' if r['variant']=='袋装' else '50ml'),26:'35ml'}.get(d) if fill:norm['FILL']='≥'+fill else:notes.append('灌装量按当前工单的小包装规格记录,不能使用整盒总容量。') elif n=='水浴灭菌': norm={'TEMP':'90℃','TIME':('40min' if d in [17,20,21,22,23,24,26] else '30min')} if d==9:norm['SOLIDS']='≥10%' if d==14:norm['SOLIDS']='≥10.5%' elif n=='预热保温':norm={'TEMP':'85℃','TIME':'30min'} elif n=='提取过滤' and d==12:norm={'TEMP':'80±5℃','TIME':'每次15min','EXTRACT':'2次','MESH':'120目'} elif n=='高剪切' and d==12:norm={'TIME':'15~20min'} elif n=='熟制' and d==13:norm={'TEMP':'约60℃;以原文颜色终点判定,不当作精确恒温设定'} elif n in ['过筛','粉碎'] and d==13:norm={'MESH':'80目'} elif n=='混合': if d==7:notes.append('待核实:正文30min,流程图/监控表40min,未选择统一标准。') elif d==10:norm={'TIME':'正文混合30min;流程图前处理范围另行核实'} elif n=='高温杀菌' and d==10:notes.append('待核实:仅流程图列90~110℃、100~180s;正文缺详细操作,未确认为执行标准。') evidence=[] for e in st['evidence']: if any(t in e['text'] for t in ['℃','min','Mpa','MPa','30s','80 目','80目','120目']):evidence.append({'paragraph':e['paragraph'],'text':e['text']}) return norm,notes,evidence templates=collections.OrderedDict();bindings=[] for rid,r in sorted(routes.items()): for st in r['steps']: norm,notes,ev=requirements(r,st) keys=list(defaults[st['name']]) for k in norm: if k not in keys:keys.append(k) details=[{'param_key':k,'required':k in norm and bool(st['starred']),'standard':norm.get(k),'note':';'.join(notes) if k not in norm else ''} for k in keys] sig=json.dumps([st['process_id'],details],ensure_ascii=False,sort_keys=True) if sig not in templates: idx=len(templates)+1 templates[sig]={'key':idx,'template_code':f'PT-STP-{idx:03d}','template_name':st['name']+'参数模板-'+f'{idx:03d}','template_type':2 if st['name'] in ['金属探测','成品检验'] else 1,'process_id':st['process_id'],'process_name':st['name'],'details':details,'source_codes':[],'evidence':[],'notes':notes} t=templates[sig] if r['document_code'] not in t['source_codes']:t['source_codes'].append(r['document_code']) for e in ev: v={'document_code':r['document_code'],**e} if v not in t['evidence']:t['evidence'].append(v) bindings.append({'route_id':rid,'route_code':r['code'],'process_id':st['process_id'],'process_name':st['name'],'template_key':t['key']}) workshops={x['id']:x for x in stationsnap['mes_md_workshop']} station_rows=[] pre={4,5,6,7,14,15,16,17,28,31} food={8,9,10,30,32,33,34,39} extract={29,35,37,38,40,41,44,45,46} liquid={18,19,20,21,22,23,24,25,26,27,36,42,43} for m in source['processes']: pid=m['id'];wid=8 if pid in pre else 3 if pid in food else 6 if pid in extract else 2 if pid in liquid else 9 station_rows.append({'code':f'WS-STP-{pid:03d}','name':m['name']+'工作站','address':None,'workshop_id':wid,'process_id':pid,'warehouse_id':23,'location_id':8,'area_id':8,'status':0,'remark':'按工序设一个逻辑工作站;参考现有'+workshops[wid]['name']+'归属;来源规程岗位;实体位置、设备与人员待现场补录。'}) assert len(station_rows)==46 and len({x['process_id'] for x in station_rows})==46 plan={'units':units,'parameters':definitions,'templates':list(templates.values()),'route_template_bindings':bindings,'stations':station_rows,'process_parameter_bindings':[{'process_id':m['id'],'param_key':k,'sort':i+1,'required':False,'remark':'通用记录项;产品工艺限值以路线工序模板为准。'} for m in source['processes'] for i,k in enumerate(defaults[m['name']])], 'limitations':['系统模板没有结构化上下限或默认值字段;数值标准保存在备注,实际报工值不预填,系统不会自动判定超限。','不同规程的微波温度、灭菌时间独立模板;冲突值不设为强制标准。','工作站按工序一对一,车间归属按岗位职责推定;没有虚构设备、人员和现场地址。']} (P/'parameter-station-plan.json').write_text(json.dumps(plan,ensure_ascii=False,indent=2),encoding='utf8') def csvout(name,head,rows): with (P/name).open('w',encoding='utf-8-sig',newline='') as f:w=csv.writer(f);w.writerow(head);w.writerows(rows) csvout('工作站与工序车间对应.csv',['工作站编码','工作站名称','工序ID','所属车间','线边仓','状态','备注'],[[x['code'],x['name'],x['process_id'],workshops[x['workshop_id']]['name'],'虚拟线边仓','启用',x['remark']] for x in station_rows]) csvout('生产参数定义.csv',['参数编码','参数名称','类型','单位编码','默认必填','备注'],[[x['param_code'],x['param_name'],'数值格式',x['unit_code'],x['required'],x['remark']] for x in definitions]) names={x['key']:x['param_name'] for x in definitions} csvout('生产参数模板明细.csv',['模板编码','工序','参数名称','必须记录','原文标准或要求','待核实事项','来源规程'],[[t['template_code'],t['process_name'],names[d['param_key']],d['required'],d['standard'] or '实际记录;原文未给统一数值',d['note'],'、'.join(t['source_codes'])] for t in templates.values() for d in t['details']]) ts={t['key']:t for t in templates.values()} csvout('路线工序参数模板绑定.csv',['路线编码','工序','模板编码','来源规程'],[[b['route_code'],b['process_name'],ts[b['template_key']]['template_code'],'、'.join(ts[b['template_key']]['source_codes'])] for b in bindings]) print(json.dumps({k:len(v) for k,v in plan.items() if isinstance(v,list)},ensure_ascii=False))