# -*- coding: utf-8 -*- """票 05 补采:quick-create 相似分支真值(needConfirm=true + similarHits 非空)。 夹具前缀 e2c-t05qc-(独立于 r3d),DB 正门清扫;产物更新 specimens-doc-truth.json 的 POST /api/customer/quick-create entry(原 entry 为 67001 参数缺失错误信封,不可用作示例)。 """ from __future__ import annotations import io import json import sys from pathlib import Path import pymysql import requests sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8') BASE = 'http://localhost:8080' ADMIN = '739564171091247104' PFX = 'e2c-t05qc' OUT = Path('.scratch/customer-integration-ready/specimens-doc-truth.json') S = requests.Session() def get_token(uid): r = requests.get(f'{BASE}/api/auth/debug/token', params={'userId': uid}, timeout=10) r.raise_for_status() return r.json()['data'] def api(method, path, form=None, params=None, step='', uid=ADMIN): headers = {'Authorization': f'Bearer {get_token(uid)}'} kw: dict = {'headers': headers, 'timeout': 60} if method == 'POST': kw['data'] = form or {} else: kw['params'] = params or {} r = S.request(method, BASE + path, **kw) try: body = r.json() except Exception: body = {'_raw': r.text[:300], '_status': r.status_code} print(f" [{step or path}] http={r.status_code} code={body.get('code') if isinstance(body, dict) else '?'}") return r, body def db(): return pymysql.connect(host='8.129.84.155', port=3306, user='root', password='Itc@123456', database='crm', charset='utf8mb4', autocommit=True, connect_timeout=10, cursorclass=pymysql.cursors.DictCursor) def dbq(sql, args=None): with db() as conn, conn.cursor() as cur: cur.execute(sql, args) return cur.fetchall() def dbx(sql, args=None): with db() as conn, conn.cursor() as cur: cur.execute(sql, args) return cur.rowcount def sweep(): rows = dbq("SELECT id FROM customer WHERE customer_name LIKE %s", (PFX + '%',)) for row in rows: cid = row['id'] for t in ['customer_contact_edge', 'customer_contact_graph', 'customer_contact_reveal_log', 'contact_import_task', 'customer_contact', 'customer_oplog', 'customer_transfer_detail', 'customer_transfer', 'team_member', 'customer_focus', 'customer_import_fail', 'customer_import_task']: try: dbx(f"DELETE FROM {t} WHERE customer_id=%s", (cid,)) except Exception: pass dbx("DELETE FROM customer WHERE id=%s", (cid,)) print(f'sweep: 清扫夹具客户 {len(rows)} 组') # ---------- 1) 源客户 ---------- sweep() SRC_NAME = f'{PFX}-相似源甲科技有限公司' r, src = api('POST', '/api/customer/create', form={ 'customerName': SRC_NAME, 'customerType': 'customer_type_01', 'provinceCode': '440000', 'cityCode': '440100', 'districtCode': '440103', 'industryCode': 'other', 'customerStarLevel': 3, 'relationStarLevel': 3, 'isBizNegotiated': 0, 'isChild': 0, 'ownerUserId': ADMIN, }, step='create 源客户') assert (src.get('data') or {}).get('id'), f'源客户创建失败: {str(src)[:200]}' print(f" 源客户 id={src['data']['id']}") # ---------- 2) 相似探测(check-name 三层) ---------- QC_NAME = f'{SRC_NAME}B' # 仅差尾字符,相似度最高 r, probe = api('GET', '/api/customer/check-name', params={'name': QC_NAME}, step='check-name 探测') print(f" 探测返回: {json.dumps(probe, ensure_ascii=False)[:300]}") # ---------- 3) quick-create 相似分支 ---------- qc_form = {'customerName': QC_NAME, 'customerType': 'customer_type_01', 'provinceCode': '440000', 'cityCode': '440100', 'industryCode': 'other', 'customerStarLevel': 3, 'relationStarLevel': 3} r, qc = api('POST', '/api/customer/quick-create', form=qc_form, step='quick-create 相似分支') d = qc.get('data') or {} print(f" needConfirm={d.get('needConfirm')} similarHits={json.dumps(d.get('similarHits'), ensure_ascii=False)[:300]}") if not d.get('needConfirm'): # 兜底:源名原样重发(精确重复必然命中) QC_NAME = SRC_NAME qc_form = {'customerName': QC_NAME, 'customerType': 'customer_type_01', 'provinceCode': '440000', 'cityCode': '440100', 'industryCode': 'other', 'customerStarLevel': 3, 'relationStarLevel': 3} r, qc = api('POST', '/api/customer/quick-create', form=qc_form, step='quick-create 精确重复兑底') d = qc.get('data') or {} print(f" 兜底 needConfirm={d.get('needConfirm')} similarHits={json.dumps(d.get('similarHits'), ensure_ascii=False)[:300]}") # needConfirm=true 未落库;若直落库了(兜底路径)sweep 会清掉 captured = {'request': {'form': qc_form}, 'response': qc} if not d.get('needConfirm'): print(' ⚠ 两次尝试均未触发 needConfirm=true——保留实测返回仍写入(直落库分支真值)') # ---------- 4) 更新 specimens-doc-truth.json ---------- doc = json.loads(OUT.read_text(encoding='utf-8')) doc['POST /api/customer/quick-create'] = captured OUT.write_text(json.dumps(doc, ensure_ascii=False, indent=1) + '\n', encoding='utf-8') print(f" doc-truth 更新: POST /api/customer/quick-create resp_len=" f"{len(json.dumps(qc, ensure_ascii=False))}") # ---------- 5) 清扫 ---------- sweep() print('== 补采完成 ==')