import sys,json,re,sqlite3,hashlib,time from pathlib import Path from datetime import datetime from collections import Counter sys.dont_write_bytecode=True;sys.path.insert(0,'C:/wechat_rpa') import review_assistant_contacts as contacts ctx=contacts.active_context();assert ctx['ready'];account=ctx['account_id'] base=Path('C:/Users/isard/AppData/Local/ZhenYangTangRPA') roots=[base/'wxwork_reply_cache',base/'archive_auto_backup/decrypted',base/'wxwork_decrypted'] out=Path('C:/kefu/deploy/my-customers-20260918');report={'inventory':[]} for root in roots: for name in ['user.db','session.db','message.db','user_extend.db']: p=root/account/name if not p.is_file():continue db=contacts._open(p) try: tables={r[0] for r in db.execute("SELECT name FROM sqlite_master WHERE type='table'")} counts={t:db.execute('SELECT count(*) FROM "'+t+'"').fetchone()[0] for t in ['user_table','external_user_relation_v3','external_user_ids','wx_friend','wechat_contactV1','message_table','conversation_table'] if t in tables} report['inventory'].append({'cache':str(root.relative_to(base)).replace('\\','/'),'database':name,'counts':counts,'modified':datetime.fromtimestamp(contacts._modified(p)/1e9).isoformat(timespec='seconds')}) finally:db.close() paths=contacts._databases(account,cache_roots=roots) u=contacts._open(paths['user.db']);s=contacts._open(paths['session.db']);m=contacts._open(paths['message.db']) try: relations={str(r[0]):(r[1],r[2],r[3]) for r in u.execute('SELECT user_id,status,stranger_type,add_customer_time FROM external_user_relation_v3')} external={str(r[0]) for r in u.execute('SELECT value FROM external_user_ids')} convs={str(r[0]) for r in s.execute('SELECT id FROM conversation_table')} msgs={str(r[0]) for r in m.execute('SELECT DISTINCT conversation_id FROM message_table')} report['selected']={name:str(path.parent.parent.relative_to(base)).replace('\\','/') for name,path in paths.items()} report['relations']={'count':len(relations),'status_distribution':dict(Counter(str(v[0]) for v in relations.values())),'stranger_distribution':dict(Counter(str(v[1]) for v in relations.values())),'positive_add_customer_time':sum(bool(v[2]) and v[2]>0 for v in relations.values()),'in_external_ids':len(set(relations)&external),'external_count':len(external)} report['conversation_prefixes']={tag:dict(Counter(c.split(':',1)[0] if ':' in c else 'unprefixed' for c in seq)) for tag,seq in [('session',convs),('message',msgs)]} maps={} for tag,seq in [('session',convs),('message',msgs)]: match={'S':set(),'M':set()} for c in seq: if re.fullmatch(r'S:\d+_\d+',c): p=c[2:].split('_') if account in p: other=[v for v in p if v!=account] if len(other)==1 and other[0] in relations:match['S'].add(other[0]) elif re.fullmatch(r'M:\d+',c) and c[2:] in relations:match['M'].add(c[2:]) maps[tag]={'S':len(match['S']),'M':len(match['M']),'unmapped':len(set(relations)-match['S']-match['M'])} report['relation_mappings']=maps report['relation_name_coverage']=[list(r) for r in u.execute("SELECT r.status,count(*),sum(length(trim(coalesce(u.name,'')))>0),sum(length(trim(coalesce(r.real_remarks,'')))>0),sum(length(trim(coalesce(r.remarks,'')))>0) FROM external_user_relation_v3 r LEFT JOIN user_table u ON u.id=r.user_id GROUP BY r.status")] # Existing persistent guard maps contain hashes only; match in memory and report counts. guard=[] for root in [Path('C:/wechat_rpa'),base]: p=root/'pending_replies.protocol.json' if not p.is_file():continue raw=json.loads(p.read_text(encoding='utf-8-sig'));blocked=raw.get('contactBlocked',{}) matching=0;active=0 for c in convs: key=hashlib.blake2b((account+'\0'+c).encode(),digest_size=40).hexdigest() if key in blocked: matching+=1 try:active+=int(0<=time.time()-float(blocked[key])<6*3600) except (ValueError,TypeError):pass guard.append({'location':'packaged' if root==base else 'source','total':len(blocked),'matching_current_conversations':matching,'active_6h':active}) report['contact_guard']=guard finally:u.close();s.close();m.close() (out/'packaged-aggregates.json').write_text(json.dumps(report,ensure_ascii=False,indent=2),encoding='utf-8') print(json.dumps(report,ensure_ascii=False))