云
18 小时以前 9dc4057ee86abf7a69e7d7a8720a41efa8dabdcc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
"""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()