| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566 |
- # -*- coding: utf-8 -*-
- """本体迁移 (一次性修补, 跑完留档不删 — 便于复现库的历史形态).
- M1 (F6): 清理位置索引 id 的旧对象 — populate 改内容派生 id 后, 旧 verdict/vib/WTG16/0
- 仍留在库里与新 id 并存 (store 是累加不是覆盖), 查询会取到两份同内容对象。
- 迁移前必须确认新 id 已存在且内容等价, 否则是删数据不是迁移。
- M2 (F7): 给存量 Decision 补 decision_kind (写入闸只在新 put 时生效, 存量对象不会自动带上)。
- 跑法: .venv/bin/python -m src.ontology.migrate [--dry]
- """
- from src import paths as P
- import json, pathlib, re, sys
- from .store import Store
- ONT = P.objects_json()
- _POS_ID = re.compile(r'^(verdict|evidence)/vib/(WTG\d+)/(\d+)$')
- def migrate(dry=False):
- s = Store(ONT)
- d = s.objects
- rep = dict(M1_dropped=[], M1_kept_no_new=[], M2_kinds={})
- # M1: 位置索引 id → 确认同台有内容派生 id 才删
- for oid in sorted(d):
- m = _POS_ID.match(oid)
- if not m:
- continue
- kind, t = m.group(1), m.group(2)
- peers = [k for k in d if k.startswith(f'{kind}/vib/{t}/') and not _POS_ID.match(k)]
- if peers:
- rep['M1_dropped'].append(oid)
- else:
- rep['M1_kept_no_new'].append(oid) # 没有对应新 id → 保留, 响亮报出
- if not dry:
- for oid in rep['M1_dropped']:
- d.pop(oid, None)
- # 清理指向已删 id 的悬空引用
- for o in d.values():
- for rel, ids in list((o.get('links') or {}).items()):
- keep = [x for x in ids if x in d]
- if len(keep) != len(ids):
- o['links'][rel] = keep
- # M2: 存量 Decision 补分型
- for o in d.values():
- if o['type'] != 'Decision':
- continue
- pr = o.setdefault('props', {})
- if not pr.get('decision_kind'):
- k = Store._decision_kind(pr)
- rep['M2_kinds'][o['id']] = k
- if not dry:
- pr['decision_kind'] = k
- if not dry:
- s.save()
- print(f"M1 位置索引 id: 删 {len(rep['M1_dropped'])} 个"
- + (f" · ★保留 {len(rep['M1_kept_no_new'])} 个 (无对应新 id, 不可删): {rep['M1_kept_no_new'][:5]}"
- if rep['M1_kept_no_new'] else ''))
- print(f"M2 Decision 补分型 {len(rep['M2_kinds'])} 个: {rep['M2_kinds']}")
- print(f"库对象数: {len(d)}")
- return rep
- if __name__ == '__main__':
- migrate(dry='--dry' in sys.argv)
|