|
@@ -48,6 +48,7 @@ def _conn():
|
|
|
# ── DDL ──────────────────────────────────────────────────────────────────────
|
|
# ── DDL ──────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
SEARCH_TABLES = {"process": "search_process", "tools": "search_tools"}
|
|
SEARCH_TABLES = {"process": "search_process", "tools": "search_tools"}
|
|
|
|
|
+MODE_TABLES = {"process": "mode_process", "tools": "mode_tools"}
|
|
|
|
|
|
|
|
|
|
|
|
|
def _search_table(mode_or_table):
|
|
def _search_table(mode_or_table):
|
|
@@ -58,6 +59,14 @@ def _search_table(mode_or_table):
|
|
|
return t
|
|
return t
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+def _mode_table(mode_or_table):
|
|
|
|
|
+ """mode(process/tools)或表名 → 合法解构表名(白名单,防 SQL 注入)。"""
|
|
|
|
|
+ t = MODE_TABLES.get(mode_or_table, mode_or_table)
|
|
|
|
|
+ if t not in MODE_TABLES.values():
|
|
|
|
|
+ raise ValueError(f"未知解构表/模式: {mode_or_table!r}")
|
|
|
|
|
+ return t
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
def _ddl_search(table, direction):
|
|
def _ddl_search(table, direction):
|
|
|
return f"""
|
|
return f"""
|
|
|
CREATE TABLE IF NOT EXISTS {table} (
|
|
CREATE TABLE IF NOT EXISTS {table} (
|
|
@@ -106,7 +115,7 @@ CREATE TABLE IF NOT EXISTS mode_process (
|
|
|
step_count INT NULL,
|
|
step_count INT NULL,
|
|
|
tools_used JSON NULL COMMENT '从 steps[].via 去重提取',
|
|
tools_used JSON NULL COMMENT '从 steps[].via 去重提取',
|
|
|
model VARCHAR(64) NULL,
|
|
model VARCHAR(64) NULL,
|
|
|
- version VARCHAR(16) NULL COMMENT 'v_MMDDHHMM,保留历史',
|
|
|
|
|
|
|
+ version VARCHAR(32) NULL COMMENT 'v_MMDDHHMM,保留历史;link_* 为跨 query 复制(cost=0)',
|
|
|
cost_usd DECIMAL(10,6) NULL COMMENT '本次解构调用成本(同版本各行相同,聚合需按 case+version 去重)',
|
|
cost_usd DECIMAL(10,6) NULL COMMENT '本次解构调用成本(同版本各行相同,聚合需按 case+version 去重)',
|
|
|
duration_s FLOAT NULL,
|
|
duration_s FLOAT NULL,
|
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
@@ -134,7 +143,7 @@ CREATE TABLE IF NOT EXISTS mode_tools (
|
|
|
defects_json JSON NULL,
|
|
defects_json JSON NULL,
|
|
|
updated_time VARCHAR(64) NULL COMMENT '工具最新更新时间',
|
|
updated_time VARCHAR(64) NULL COMMENT '工具最新更新时间',
|
|
|
model VARCHAR(64) NULL,
|
|
model VARCHAR(64) NULL,
|
|
|
- version VARCHAR(16) NULL,
|
|
|
|
|
|
|
+ version VARCHAR(32) NULL COMMENT 'v_MMDDHHMM;link_* 为跨 query 复制(cost=0)',
|
|
|
cost_usd DECIMAL(10,6) NULL COMMENT '同 mode_process,聚合按 case+version 去重',
|
|
cost_usd DECIMAL(10,6) NULL COMMENT '同 mode_process,聚合按 case+version 去重',
|
|
|
duration_s FLOAT NULL,
|
|
duration_s FLOAT NULL,
|
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
@@ -153,6 +162,10 @@ def init_tables():
|
|
|
cur.execute(_ddl_search("search_tools", "工具方向"))
|
|
cur.execute(_ddl_search("search_tools", "工具方向"))
|
|
|
cur.execute(DDL_PROCESS)
|
|
cur.execute(DDL_PROCESS)
|
|
|
cur.execute(DDL_TOOLS)
|
|
cur.execute(DDL_TOOLS)
|
|
|
|
|
+ # 历史库迁移:version 由 VARCHAR(16) 放宽到 32,容纳 link_v_mopN_* 复制版本。
|
|
|
|
|
+ # MODIFY 幂等(已是 32 则 MySQL 元数据无操作),建表后表必存在,可安全执行。
|
|
|
|
|
+ for t in ("mode_process", "mode_tools"):
|
|
|
|
|
+ cur.execute(f"ALTER TABLE {t} MODIFY COLUMN version VARCHAR(32) NULL")
|
|
|
print("✅ 建表完成:search_process, search_tools, mode_process, mode_tools")
|
|
print("✅ 建表完成:search_process, search_tools, mode_process, mode_tools")
|
|
|
finally:
|
|
finally:
|
|
|
conn.close()
|
|
conn.close()
|
|
@@ -541,6 +554,63 @@ def fetch_tools(case_id, version=None):
|
|
|
"tool_count": len(tools), "tools": tools}
|
|
"tool_count": len(tools), "tools": tools}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+# ── 跨 query 去重 / link 复制(方案A:解构前先去重,避免重复花钱)──────────────
|
|
|
|
|
+# case_id 是帖子物理身份(platform_channelContentId),与 query 无关。同一帖被多个
|
|
|
|
|
+# query 搜到时只需真实解构一次;其余 query 用 link_* 复制行补齐关联(cost=0)。
|
|
|
|
|
+
|
|
|
|
|
+def latest_real_version(case_id, mode="process"):
|
|
|
|
|
+ """该 case 是否已有「真实」解构(任意 query;link_* 是复制品,不算源)。
|
|
|
|
|
+ 返回最新一行 {"version","query_id"} 或 None。给解构前去重判定用。"""
|
|
|
|
|
+ table = _mode_table(mode)
|
|
|
|
|
+ conn = _conn()
|
|
|
|
|
+ try:
|
|
|
|
|
+ with conn.cursor() as cur:
|
|
|
|
|
+ cur.execute(f"""SELECT version, query_id FROM {table}
|
|
|
|
|
+ WHERE case_id=%s AND LEFT(version,5) <> 'link_'
|
|
|
|
|
+ ORDER BY version DESC, id DESC LIMIT 1""", (case_id,))
|
|
|
|
|
+ return cur.fetchone()
|
|
|
|
|
+ finally:
|
|
|
|
|
+ conn.close()
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def link_process(query_id, case_id, mode="process"):
|
|
|
|
|
+ """把 case 在别处最新「真实」版本的解构行复制到目标 query
|
|
|
|
|
+ (version='link_'+源版本, cost_usd=0)。幂等(先删目标同版本)。
|
|
|
|
|
+ 返回复制行数;该 case 从未真实解构过则返回 0(无源可复制)。"""
|
|
|
|
|
+ table = _mode_table(mode)
|
|
|
|
|
+ conn = _conn()
|
|
|
|
|
+ try:
|
|
|
|
|
+ with conn.cursor() as cur:
|
|
|
|
|
+ cur.execute(f"""SELECT version FROM {table}
|
|
|
|
|
+ WHERE case_id=%s AND LEFT(version,5) <> 'link_'
|
|
|
|
|
+ ORDER BY version DESC, id DESC LIMIT 1""", (case_id,))
|
|
|
|
|
+ r = cur.fetchone()
|
|
|
|
|
+ if not r:
|
|
|
|
|
+ return 0
|
|
|
|
|
+ srcver = r["version"]
|
|
|
|
|
+ newver = ("link_" + srcver)[:32] # version 列 VARCHAR(32)
|
|
|
|
|
+ # 复制除自增 id / 时间戳外的全部列,改写 query_id / version / cost。
|
|
|
|
|
+ cur.execute(f"SHOW COLUMNS FROM {table}")
|
|
|
|
|
+ cols = [c["Field"] for c in cur.fetchall()
|
|
|
|
|
+ if c["Field"] not in ("id", "created_at", "updated_at")]
|
|
|
|
|
+ cur.execute(f"SELECT {','.join(cols)} FROM {table} WHERE case_id=%s AND version=%s",
|
|
|
|
|
+ (case_id, srcver))
|
|
|
|
|
+ rows = cur.fetchall()
|
|
|
|
|
+ cur.execute(f"DELETE FROM {table} WHERE query_id=%s AND case_id=%s AND version=%s",
|
|
|
|
|
+ (query_id, case_id, newver))
|
|
|
|
|
+ for row in rows:
|
|
|
|
|
+ row = dict(row)
|
|
|
|
|
+ row["query_id"] = query_id
|
|
|
|
|
+ row["version"] = newver
|
|
|
|
|
+ row["cost_usd"] = 0
|
|
|
|
|
+ cur.execute(
|
|
|
|
|
+ f"INSERT INTO {table} ({','.join(cols)}) VALUES ({','.join(['%s']*len(cols))})",
|
|
|
|
|
+ [row[k] for k in cols])
|
|
|
|
|
+ return len(rows)
|
|
|
|
|
+ finally:
|
|
|
|
|
+ conn.close()
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
# ── Dashboard 原始行(指标计算在 server.py)─────────────────────────────────────
|
|
# ── Dashboard 原始行(指标计算在 server.py)─────────────────────────────────────
|
|
|
|
|
|
|
|
def fetch_dashboard_rows():
|
|
def fetch_dashboard_rows():
|