|
@@ -73,6 +73,16 @@ def _category_tree(source_type):
|
|
|
return data
|
|
return data
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+SCORE_CACHE_DIR = HERE / ".cache" / "query_score"
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def _query_sel_hash(sel):
|
|
|
|
|
+ """对评分选择 + 矩阵指纹算稳定短哈希,作为缓存文件名(同选择不重复付费)。"""
|
|
|
|
|
+ matrix_sig = hashlib.md5(MATRIX_FILE.read_bytes()).hexdigest()[:8]
|
|
|
|
|
+ canon = json.dumps([sel, matrix_sig], ensure_ascii=False, sort_keys=True)
|
|
|
|
|
+ return hashlib.md5(canon.encode("utf-8")).hexdigest()[:16]
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
LOG_DIR = HERE / "runs" / "logs"
|
|
LOG_DIR = HERE / "runs" / "logs"
|
|
|
# 工序步骤 via 字段的「无具体工具」占位符,不计入工序提及工具 TOP 榜
|
|
# 工序步骤 via 字段的「无具体工具」占位符,不计入工序提及工具 TOP 榜
|
|
|
_VIA_PLACEHOLDERS = {"-", "—", "-", "--", "/", "无", "n/a", "none"}
|
|
_VIA_PLACEHOLDERS = {"-", "—", "-", "--", "/", "无", "n/a", "none"}
|
|
@@ -461,6 +471,12 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
self._json_etag(_matrix())
|
|
self._json_etag(_matrix())
|
|
|
elif u.path == "/api/category_tree":
|
|
elif u.path == "/api/category_tree":
|
|
|
self._json_etag(_category_tree(qs.get("source_type", "实质")))
|
|
self._json_etag(_category_tree(qs.get("source_type", "实质")))
|
|
|
|
|
+ elif u.path == "/api/query_score":
|
|
|
|
|
+ cache = SCORE_CACHE_DIR / f"{qs.get('sel', '')}.json"
|
|
|
|
|
+ if cache.is_file():
|
|
|
|
|
+ self._json_etag(json.loads(cache.read_text(encoding="utf-8")))
|
|
|
|
|
+ else:
|
|
|
|
|
+ self._json({"pending": True}, 202)
|
|
|
elif u.path == "/api/dashboard":
|
|
elif u.path == "/api/dashboard":
|
|
|
self._json_etag(_dashboard_cached())
|
|
self._json_etag(_dashboard_cached())
|
|
|
elif u.path == "/api/queries":
|
|
elif u.path == "/api/queries":
|
|
@@ -564,6 +580,28 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
_release_cases(mode, claimed) # 起进程失败也要释放认领,避免卡住
|
|
_release_cases(mode, claimed) # 起进程失败也要释放认领,避免卡住
|
|
|
raise
|
|
raise
|
|
|
self._json({"task_id": task_id, "skipped": skipped})
|
|
self._json({"task_id": task_id, "skipped": skipped})
|
|
|
|
|
+ elif u.path == "/api/query_score":
|
|
|
|
|
+ sel = {
|
|
|
|
|
+ "tool_type": payload.get("tool_type", ""),
|
|
|
|
|
+ "modality": payload.get("modality", ""),
|
|
|
|
|
+ "suffix": payload.get("suffix", ""),
|
|
|
|
|
+ "substance_path": payload.get("substance_path") or [],
|
|
|
|
|
+ "form_path": payload.get("form_path") or [],
|
|
|
|
|
+ "model": payload.get("model", "anthropic/claude-sonnet-4-6"),
|
|
|
|
|
+ }
|
|
|
|
|
+ sel_hash = _query_sel_hash(sel)
|
|
|
|
|
+ cache = SCORE_CACHE_DIR / f"{sel_hash}.json"
|
|
|
|
|
+ if cache.is_file() and not payload.get("force"):
|
|
|
|
|
+ return self._json({"sel": sel_hash, "cached": True})
|
|
|
|
|
+ cmd = [sys.executable, "stages/query_score.py", "--sel", sel_hash,
|
|
|
|
|
+ "--tool-type", sel["tool_type"], "--modality", sel["modality"],
|
|
|
|
|
+ "--suffix", sel["suffix"],
|
|
|
|
|
+ "--substance-path", ",".join(sel["substance_path"]),
|
|
|
|
|
+ "--form-path", ",".join(sel["form_path"]),
|
|
|
|
|
+ "--model", sel["model"]]
|
|
|
|
|
+ if payload.get("force"):
|
|
|
|
|
+ cmd += ["--force"]
|
|
|
|
|
+ self._json({"sel": sel_hash, "task_id": _spawn_task("score", cmd), "cached": False})
|
|
|
elif u.path == "/api/run_search":
|
|
elif u.path == "/api/run_search":
|
|
|
query = (payload.get("query") or "").strip()
|
|
query = (payload.get("query") or "").strip()
|
|
|
if not query:
|
|
if not query:
|