|
@@ -177,6 +177,25 @@ CREATE TABLE IF NOT EXISTS mode_tools (
|
|
|
"""
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+# 工序知识「已导入知识库」台账:防重复上传(import_process_knowledge.py 用)。
|
|
|
|
|
+# 每条知识 = 某 case 的某个工序(proc_index 1-based)。记录导入时的 mode_process 版本:
|
|
|
|
|
+# 版本变了(重解构)说明内容已变,应重导;版本不变即视为「已传过」,跳过。
|
|
|
|
|
+# 选 DB 台账而非本地文件,是为了换机器/换链接后也不会重复写知识库。
|
|
|
|
|
+DDL_INGEST_LOG = """
|
|
|
|
|
+CREATE TABLE IF NOT EXISTS knowledge_ingest_log (
|
|
|
|
|
+ id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
|
|
|
|
+ case_id VARCHAR(128) NOT NULL,
|
|
|
|
|
+ proc_index INT NOT NULL COMMENT '工序序号(1-based),对齐导入脚本枚举',
|
|
|
|
|
+ version VARCHAR(32) NULL COMMENT '导入时 mode_process 版本;变了应重导',
|
|
|
|
|
+ knowledge_id VARCHAR(128) NULL COMMENT '接口返回的 knowledge_id',
|
|
|
|
|
+ api_url VARCHAR(255) NULL,
|
|
|
|
|
+ ingested_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
|
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
|
|
|
+ UNIQUE KEY uk_case_proc (case_id, proc_index)
|
|
|
|
|
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='工序知识已导入台账(防重复上传)';
|
|
|
|
|
+"""
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
def init_tables():
|
|
def init_tables():
|
|
|
conn = _conn()
|
|
conn = _conn()
|
|
|
try:
|
|
try:
|
|
@@ -185,11 +204,12 @@ 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)
|
|
|
|
|
+ cur.execute(DDL_INGEST_LOG)
|
|
|
# 历史库迁移:version 由 VARCHAR(16) 放宽到 32,容纳 link_v_mopN_* 复制版本。
|
|
# 历史库迁移:version 由 VARCHAR(16) 放宽到 32,容纳 link_v_mopN_* 复制版本。
|
|
|
# MODIFY 幂等(已是 32 则 MySQL 元数据无操作),建表后表必存在,可安全执行。
|
|
# MODIFY 幂等(已是 32 则 MySQL 元数据无操作),建表后表必存在,可安全执行。
|
|
|
for t in ("mode_process", "mode_tools"):
|
|
for t in ("mode_process", "mode_tools"):
|
|
|
cur.execute(f"ALTER TABLE {t} MODIFY COLUMN version VARCHAR(32) NULL")
|
|
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, knowledge_ingest_log")
|
|
|
finally:
|
|
finally:
|
|
|
conn.close()
|
|
conn.close()
|
|
|
|
|
|
|
@@ -269,9 +289,21 @@ def _recency_hard(date_str):
|
|
|
return 1
|
|
return 1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+def _fixed_dim_score(evaluation, name):
|
|
|
|
|
+ """取 质量.固定维度.<name>.得分 标量,缺失/非数值返回 None(不参与判定)。"""
|
|
|
|
|
+ v = (((evaluation or {}).get("质量") or {}).get("固定维度") or {}).get(name)
|
|
|
|
|
+ if isinstance(v, dict):
|
|
|
|
|
+ v = v.get("得分")
|
|
|
|
|
+ try:
|
|
|
|
|
+ return float(v) if v is not None else None
|
|
|
|
|
+ except (TypeError, ValueError):
|
|
|
|
|
+ return None
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
def is_adopted(overall, evaluation, publish_time):
|
|
def is_adopted(overall, evaluation, publish_time):
|
|
|
"""采纳/命中判定,口径对齐 mode_procedure 的 decision=="report":
|
|
"""采纳/命中判定,口径对齐 mode_procedure 的 decision=="report":
|
|
|
- 制作相关性<4、发布超两年、综合分<6 —— 任一命中即不采纳;指标缺失不参与判定。"""
|
|
|
|
|
|
|
+ 制作相关性<4、可复现性<4、发布超两年、综合分<6 —— 任一命中即不采纳;指标缺失不参与判定。
|
|
|
|
|
+ (意图可控性暂只采分不设门槛,留待阈值标定后再开。)"""
|
|
|
rel = None
|
|
rel = None
|
|
|
v = ((evaluation or {}).get("相关性") or {}).get("和内容制作知识相关")
|
|
v = ((evaluation or {}).get("相关性") or {}).get("和内容制作知识相关")
|
|
|
if isinstance(v, dict):
|
|
if isinstance(v, dict):
|
|
@@ -282,6 +314,9 @@ def is_adopted(overall, evaluation, publish_time):
|
|
|
rel = None
|
|
rel = None
|
|
|
if rel is not None and rel < 4:
|
|
if rel is not None and rel < 4:
|
|
|
return False
|
|
return False
|
|
|
|
|
+ repro = _fixed_dim_score(evaluation, "可复现性")
|
|
|
|
|
+ if repro is not None and repro < 4:
|
|
|
|
|
+ return False
|
|
|
rh = _recency_hard(publish_time)
|
|
rh = _recency_hard(publish_time)
|
|
|
if rh is not None and rh < 2:
|
|
if rh is not None and rh < 2:
|
|
|
return False
|
|
return False
|
|
@@ -290,8 +325,8 @@ def is_adopted(overall, evaluation, publish_time):
|
|
|
return True
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
|
-def is_adopted_rel(overall, rel, publish_time):
|
|
|
|
|
- """is_adopted 的轻量版:相关性得分(rel)已由 SQL JSON_EXTRACT 直接取出,
|
|
|
|
|
|
|
+def is_adopted_rel(overall, rel, publish_time, repro=None):
|
|
|
|
|
+ """is_adopted 的轻量版:相关性得分(rel)、可复现性(repro)已由 SQL JSON_EXTRACT 直接取出,
|
|
|
无需传输/解析整块 llm_evaluation。判定口径与 is_adopted 完全一致。"""
|
|
无需传输/解析整块 llm_evaluation。判定口径与 is_adopted 完全一致。"""
|
|
|
try:
|
|
try:
|
|
|
rel = float(rel) if rel is not None else None
|
|
rel = float(rel) if rel is not None else None
|
|
@@ -299,6 +334,12 @@ def is_adopted_rel(overall, rel, publish_time):
|
|
|
rel = None
|
|
rel = None
|
|
|
if rel is not None and rel < 4:
|
|
if rel is not None and rel < 4:
|
|
|
return False
|
|
return False
|
|
|
|
|
+ try:
|
|
|
|
|
+ repro = float(repro) if repro is not None else None
|
|
|
|
|
+ except (TypeError, ValueError):
|
|
|
|
|
+ repro = None
|
|
|
|
|
+ if repro is not None and repro < 4:
|
|
|
|
|
+ return False
|
|
|
rh = _recency_hard(publish_time)
|
|
rh = _recency_hard(publish_time)
|
|
|
if rh is not None and rh < 2:
|
|
if rh is not None and rh < 2:
|
|
|
return False
|
|
return False
|
|
@@ -697,6 +738,11 @@ _REL_SQL = ("JSON_UNQUOTE(COALESCE("
|
|
|
"JSON_EXTRACT(llm_evaluation,'$.\"相关性\".\"和内容制作知识相关\".\"得分\"'),"
|
|
"JSON_EXTRACT(llm_evaluation,'$.\"相关性\".\"和内容制作知识相关\".\"得分\"'),"
|
|
|
"JSON_EXTRACT(llm_evaluation,'$.\"相关性\".\"和内容制作知识相关\"')))")
|
|
"JSON_EXTRACT(llm_evaluation,'$.\"相关性\".\"和内容制作知识相关\"')))")
|
|
|
|
|
|
|
|
|
|
+# 可复现性门槛同样需要标量直取(口径同 is_adopted 的 _fixed_dim_score)。
|
|
|
|
|
+_REPRO_SQL = ("JSON_UNQUOTE(COALESCE("
|
|
|
|
|
+ "JSON_EXTRACT(llm_evaluation,'$.\"质量\".\"固定维度\".\"可复现性\".\"得分\"'),"
|
|
|
|
|
+ "JSON_EXTRACT(llm_evaluation,'$.\"质量\".\"固定维度\".\"可复现性\"')))")
|
|
|
|
|
+
|
|
|
|
|
|
|
|
def fetch_adopted_process_cases(query_id=None):
|
|
def fetch_adopted_process_cases(query_id=None):
|
|
|
"""返回「已采纳且有工序解构」的 case_id 列表(供知识上传脚本用)。
|
|
"""返回「已采纳且有工序解构」的 case_id 列表(供知识上传脚本用)。
|
|
@@ -707,7 +753,7 @@ def fetch_adopted_process_cases(query_id=None):
|
|
|
query_id 给定时只看该搜索任务下的 case。返回去重、按 case_id 排序的列表。
|
|
query_id 给定时只看该搜索任务下的 case。返回去重、按 case_id 排序的列表。
|
|
|
"""
|
|
"""
|
|
|
sql = (f"SELECT DISTINCT s.case_id, s.overall_score, s.publish_time, "
|
|
sql = (f"SELECT DISTINCT s.case_id, s.overall_score, s.publish_time, "
|
|
|
- f"{_REL_SQL} AS rel "
|
|
|
|
|
|
|
+ f"{_REL_SQL} AS rel, {_REPRO_SQL} AS repro "
|
|
|
"FROM search_process s "
|
|
"FROM search_process s "
|
|
|
"JOIN (SELECT DISTINCT case_id FROM mode_process) m ON s.case_id = m.case_id")
|
|
"JOIN (SELECT DISTINCT case_id FROM mode_process) m ON s.case_id = m.case_id")
|
|
|
params = ()
|
|
params = ()
|
|
@@ -722,10 +768,63 @@ def fetch_adopted_process_cases(query_id=None):
|
|
|
finally:
|
|
finally:
|
|
|
conn.close()
|
|
conn.close()
|
|
|
cases = [r["case_id"] for r in rows
|
|
cases = [r["case_id"] for r in rows
|
|
|
- if is_adopted_rel(r["overall_score"], r["rel"], r["publish_time"])]
|
|
|
|
|
|
|
+ if is_adopted_rel(r["overall_score"], r["rel"], r["publish_time"], r["repro"])]
|
|
|
return sorted(set(cases))
|
|
return sorted(set(cases))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+# ── 评估去重:复用 query 无关分,只重算 query 相关分(search_eval.py 用)──────────
|
|
|
|
|
+
|
|
|
|
|
+def fetch_existing_eval(case_id, table="search_process"):
|
|
|
|
|
+ """返回该 case 在搜索表里最近一条「有效」评估 blob(任意 query)。
|
|
|
|
|
+ 评估去重用:同帖在别的相似 query 下评过时,复用其 query 无关分(质量/通用相关/时效),
|
|
|
|
|
+ 只重算「和 query 相关」。无有效评估(全是 _error 或没评过)返回 None。
|
|
|
|
|
+ 取最近若干条逐一挑出首个非 error、结构完整的 blob。"""
|
|
|
|
|
+ table = _search_table(table)
|
|
|
|
|
+ conn = _conn()
|
|
|
|
|
+ try:
|
|
|
|
|
+ with conn.cursor() as cur:
|
|
|
|
|
+ cur.execute(f"""SELECT llm_evaluation FROM {table}
|
|
|
|
|
+ WHERE case_id=%s AND llm_evaluation IS NOT NULL
|
|
|
|
|
+ ORDER BY updated_at DESC, id DESC LIMIT 5""", (case_id,))
|
|
|
|
|
+ rows = cur.fetchall()
|
|
|
|
|
+ finally:
|
|
|
|
|
+ conn.close()
|
|
|
|
|
+ for r in rows:
|
|
|
|
|
+ e = _loads(r["llm_evaluation"])
|
|
|
|
|
+ if isinstance(e, dict) and not e.get("_error") and isinstance(e.get("相关性"), dict):
|
|
|
|
|
+ return e
|
|
|
|
|
+ return None
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+# ── 上传去重:知识库已导入台账(import_process_knowledge.py 用)────────────────
|
|
|
|
|
+
|
|
|
|
|
+def fetch_ingested_map(case_id):
|
|
|
|
|
+ """返回 {proc_index: version} —— 该 case 各工序已导入知识库的版本。空表示没传过。"""
|
|
|
|
|
+ conn = _conn()
|
|
|
|
|
+ try:
|
|
|
|
|
+ with conn.cursor() as cur:
|
|
|
|
|
+ cur.execute("SELECT proc_index, version FROM knowledge_ingest_log WHERE case_id=%s",
|
|
|
|
|
+ (case_id,))
|
|
|
|
|
+ return {r["proc_index"]: r["version"] for r in cur.fetchall()}
|
|
|
|
|
+ finally:
|
|
|
|
|
+ conn.close()
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def mark_ingested(case_id, proc_index, version, knowledge_id=None, api_url=None):
|
|
|
|
|
+ """记一条「已导入」台账(case_id+proc_index 唯一,重导同序号则更新版本/knowledge_id)。"""
|
|
|
|
|
+ conn = _conn()
|
|
|
|
|
+ try:
|
|
|
|
|
+ with conn.cursor() as cur:
|
|
|
|
|
+ cur.execute("""INSERT INTO knowledge_ingest_log
|
|
|
|
|
+ (case_id, proc_index, version, knowledge_id, api_url)
|
|
|
|
|
+ VALUES (%s,%s,%s,%s,%s)
|
|
|
|
|
+ ON DUPLICATE KEY UPDATE version=VALUES(version),
|
|
|
|
|
+ knowledge_id=VALUES(knowledge_id), api_url=VALUES(api_url)""",
|
|
|
|
|
+ (case_id, proc_index, version, knowledge_id, api_url))
|
|
|
|
|
+ finally:
|
|
|
|
|
+ conn.close()
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
def fetch_dashboard_rows():
|
|
def fetch_dashboard_rows():
|
|
|
"""拉 Dashboard 计算所需的轻量行。数据量级:百~千行,Python 聚合足够。
|
|
"""拉 Dashboard 计算所需的轻量行。数据量级:百~千行,Python 聚合足够。
|
|
|
优化:① 不传 llm_evaluation 整块,SQL 只取采纳判定要的相关性得分;
|
|
优化:① 不传 llm_evaluation 整块,SQL 只取采纳判定要的相关性得分;
|
|
@@ -734,7 +833,8 @@ def fetch_dashboard_rows():
|
|
|
try:
|
|
try:
|
|
|
with conn.cursor() as cur:
|
|
with conn.cursor() as cur:
|
|
|
# 进度分母走「采纳」口径;mode 标方向(工序帖来自 search_process)。
|
|
# 进度分母走「采纳」口径;mode 标方向(工序帖来自 search_process)。
|
|
|
- cols = f"query_id, case_id, platform, overall_score, publish_time, {_REL_SQL} AS rel"
|
|
|
|
|
|
|
+ cols = (f"query_id, case_id, platform, overall_score, publish_time, "
|
|
|
|
|
+ f"{_REL_SQL} AS rel, {_REPRO_SQL} AS repro")
|
|
|
cur.execute(f"SELECT {cols} FROM search_process")
|
|
cur.execute(f"SELECT {cols} FROM search_process")
|
|
|
posts = cur.fetchall()
|
|
posts = cur.fetchall()
|
|
|
for p in posts:
|
|
for p in posts:
|
|
@@ -761,7 +861,7 @@ def fetch_dashboard_rows():
|
|
|
conn.close()
|
|
conn.close()
|
|
|
for p in posts:
|
|
for p in posts:
|
|
|
# 采纳判定:口径同帖子列表(is_adopted),作为「需解构」分母依据
|
|
# 采纳判定:口径同帖子列表(is_adopted),作为「需解构」分母依据
|
|
|
- p["adopted"] = is_adopted_rel(p["overall_score"], p["rel"], p["publish_time"])
|
|
|
|
|
|
|
+ p["adopted"] = is_adopted_rel(p["overall_score"], p["rel"], p["publish_time"], p["repro"])
|
|
|
for r in procs:
|
|
for r in procs:
|
|
|
r["steps"] = _loads(r["steps"], [])
|
|
r["steps"] = _loads(r["steps"], [])
|
|
|
r["cost_usd"] = float(r["cost_usd"]) if r["cost_usd"] is not None else None
|
|
r["cost_usd"] = float(r["cost_usd"]) if r["cost_usd"] is not None else None
|