"""Consolidate indicator master names; never delete inspection lines or results.""" import sys,json,csv from pathlib import Path from collections import defaultdict,Counter P=Path(__file__).parent sys.path.insert(0,str(P.parent/'iqc-import-20261012/deps')) import pymysql encode=lambda r:json.dumps(r,ensure_ascii=False,default=str,sort_keys=True) s=json.loads((P/'before-indicator-dedup.json').read_text(encoding='utf8')) keys={'mes_qc_template_indicator':['template_id'],'mes_qc_indicator_result_detail':['result_id','measure_no'], 'mes_qc_iqc_line':['iqc_id'],'mes_qc_oqc_line':['oqc_id'],'mes_qc_ipqc_line':['ipqc_id'],'mes_qc_rqc_line':['rqc_id']} mapping={};plan=[] for group in s['duplicates']: rows=sorted(group['rows'],key=lambda r:r['id']);keep=rows[0] assert {(r['item_type'],r['result_type']) for r in rows}=={(1,3)}, 'Incompatible result types' assert not any(r['parent_id'] in {x['id'] for x in rows} for r in rows) for duplicate in rows[1:]: mapping[duplicate['id']]=keep['id'] plan.append({'name':group['name'],'retained_id':keep['id'],'retained_code':keep['code'], 'duplicate_id':duplicate['id'],'duplicate_code':duplicate['code'], 'original_unit_text':duplicate['unit_text'],'original_result_specification':duplicate['result_specification']}) assert len(mapping)==80 and len(s['duplicates'])==29 ids=[r['id'] for group in s['duplicates'] for r in group['rows']] marks=','.join('%s' for _ in ids) updater='qc-name-dedup-20261013' c=pymysql.connect(host='36.213.150.237',port=10000,user='root',password=sys.stdin.readline().strip(),database='mom-hm',charset='utf8mb4',cursorclass=pymysql.cursors.DictCursor,autocommit=False) counts={};backups={} try: with c.cursor() as q: q.execute("SELECT DATABASE() db,NOW() now");state=q.fetchone();assert state['db']=='mom-hm';now=state['now'] q.execute("SELECT GET_LOCK('codex-qc-name-dedup',0) locked");assert q.fetchone()['locked']==1 q.execute('SELECT * FROM mes_qc_indicator ORDER BY id FOR UPDATE');masters=q.fetchall() assert encode(masters)==encode(s['indicators']), 'Indicator masters changed since inspection' # No group master is being removed, hence no children need moving. assert not any(r['parent_id'] in mapping for r in masters) for table in s['references']: assert table in keys q.execute('SELECT * FROM `'+table+'` WHERE indicator_id IN ('+marks+') ORDER BY id FOR UPDATE',ids) rows=q.fetchall();backups[table]=rows assert encode(rows)==encode(s['references'][table]['rows']), 'References changed: '+table # Consolidation must not create ambiguous repeated measurements or template rows. grouped=defaultdict(list) for row in rows: if row['deleted'] in (0,False,b'\x00'): grouped[tuple(row[k] for k in keys[table])+(mapping.get(row['indicator_id'],row['indicator_id']),)].append(row['id']) assert not [v for v in grouped.values() if len(v)>1], 'Duplicate target rows: '+table (P/'indicator-dedup-locked-backup.json').write_text(encode({'masters':masters,'references':backups,'mapping':mapping,'at':str(now)}),encoding='utf8') case='CASE indicator_id '+' '.join('WHEN %s THEN %s' for _ in mapping)+' ELSE indicator_id END' pairs=[v for pair in mapping.items() for v in pair];oldmarks=','.join('%s' for _ in mapping) for table,rows in backups.items(): q.execute('UPDATE `'+table+'` SET indicator_id='+case+',updater=%s,update_time=%s WHERE indicator_id IN ('+oldmarks+')',pairs+[updater,now]+list(mapping)) counts[table]=q.rowcount assert counts[table]==sum(r['indicator_id'] in mapping for r in rows) q.execute('UPDATE mes_qc_indicator SET deleted=1,updater=%s,update_time=%s WHERE deleted=0 AND id IN ('+oldmarks+')',[updater,now]+list(mapping)) assert q.rowcount==len(mapping) q.execute('SELECT TRIM(name) name,COUNT(*) n FROM mes_qc_indicator WHERE deleted=0 GROUP BY TRIM(name) HAVING COUNT(*)>1') assert not q.fetchall(), 'Duplicate names remain' q.execute('SELECT * FROM mes_qc_indicator ORDER BY id');aftermasters={r['id']:r for r in q.fetchall()} for original in masters: new=aftermasters[original['id']] if original['id'] in mapping: assert new['deleted']==b'\x01' assert all(new[k]==v for k,v in original.items() if k not in {'deleted','updater','update_time'}) else:assert encode(new)==encode(original) for table,rows in backups.items(): q.execute('SELECT * FROM `'+table+'` WHERE indicator_id IN ('+marks+') ORDER BY id',ids) current={r['id']:r for r in q.fetchall()} assert len(current)==len(rows) for original in rows: new=current[original['id']] assert new['indicator_id']==mapping.get(original['indicator_id'],original['indicator_id']) allowed={'indicator_id','updater','update_time'} if original['indicator_id'] in mapping else set() assert all(new[k]==v for k,v in original.items() if k not in allowed), 'Requirement or measured value changed' q.execute('SELECT COUNT(*) n FROM `'+table+'` WHERE indicator_id IN ('+oldmarks+')',list(mapping)) assert q.fetchone()['n']==0 c.commit() active_before=sum(r['deleted'] in (0,False,b'\x00') for r in masters) result={'duplicate_name_groups':len(s['duplicates']),'logically_deleted_duplicates':len(mapping), 'active_before':active_before,'active_after':active_before-len(mapping),'reference_rows_migrated':counts, 'total_reference_rows_migrated':sum(counts.values()),'requirements_and_measured_values_unchanged':True, 'all_retained_master_fields_unchanged':True,'executed_at':str(now)} (P/'indicator-name-dedup-result.json').write_text(encode(result),encoding='utf8') (P/'indicator-name-dedup-mapping.json').write_text(encode(plan),encoding='utf8') with (P/'检验项名称去重对应.csv').open('w',encoding='utf-8-sig',newline='') as f: w=csv.writer(f);w.writerow(['检验项名称','保留ID','保留编码','合并ID','合并编码','原单位文本','原结果值属性(历史定义保留在逻辑删除项)']) for r in plan:w.writerow([r['name'],r['retained_id'],r['retained_code'],r['duplicate_id'],r['duplicate_code'],r['original_unit_text'],r['original_result_specification']]) print(encode(result)) finally:c.close()