From 6bc0f1600636a7e474df7eb84766a881c25ec75d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E6=B8=85?= Date: Thu, 20 Aug 2026 17:07:55 +0800 Subject: [PATCH] feat: expose acceptance Link AI aggregate data --- acceptance_service.py | 59 +++++++++++++++++++++++++++++++++++++++++++ app.py | 8 ++++++ crawler_client.py | 9 +++++++ 3 files changed, 76 insertions(+) diff --git a/acceptance_service.py b/acceptance_service.py index 807fa39..552326f 100644 --- a/acceptance_service.py +++ b/acceptance_service.py @@ -22,6 +22,7 @@ from crawler_client import ( collect_keyword_sources, fetch_content_detail, get_wen_resources, + match_feishu_notes, query_collection, schedule_xapi_screenshots, ) @@ -965,3 +966,61 @@ def list_acceptance_notes(public_id: str) -> dict[str, Any]: item["parsed_data"] = json.loads(item.pop("raw_json")) if item.get("raw_json") else None items.append(item) return {"items": items, "total": len(items)} + + +def get_acceptance_link_ai_data(public_id: str) -> dict[str, Any]: + """返回 Link AI 所需的笔记、AI 采集结果和逐关键词比对结果。""" + with connection() as conn: + task = conn.execute( + "SELECT * FROM acceptance_tasks WHERE public_task_id=?", (public_id,), + ).fetchone() + if not task: + raise TaskError("ACCEPTANCE_TASK_NOT_FOUND", "验收任务不存在", 404) + task_data = _task_dict(conn, task) + rows = conn.execute( + """SELECT a.keyword,a.credential_id,a.attempt_number,a.crawler_task_id, + a.result_json,a.completed_at + FROM acceptance_cookie_attempts a + WHERE a.task_id=? AND a.status='completed' AND a.result_json IS NOT NULL + ORDER BY a.keyword,a.id""", + (task["id"],), + ).fetchall() + + ai_results = [] + for row in rows: + try: + result = json.loads(row["result_json"]) + except (TypeError, json.JSONDecodeError): + continue + ai_results.append({ + "keyword": row["keyword"], + "credential_id": row["credential_id"], + "attempt_number": row["attempt_number"], + "crawler_task_id": row["crawler_task_id"], + "completed_at": row["completed_at"], + "result": result, + }) + + comparisons = [] + for keyword in task_data["keywords"]: + try: + comparisons.append({ + "keyword": keyword, + "status": "completed", + "data": match_feishu_notes(keyword), + }) + except Exception as exc: + comparisons.append({ + "keyword": keyword, + "status": "failed", + "error": str(exc), + "data": None, + }) + + return { + "task": task_data, + "notes": list_acceptance_notes(public_id), + "ai_results": {"items": ai_results, "total": len(ai_results)}, + "note_matches": {"items": comparisons, "total": len(comparisons)}, + "data_scope": "仅包含当前已完成并已入库的数据", + } diff --git a/app.py b/app.py index 5587ab5..e95448a 100644 --- a/app.py +++ b/app.py @@ -18,6 +18,7 @@ from database import init_database from acceptance_service import ( create_acceptance_task, get_acceptance_resources, + get_acceptance_link_ai_data, get_acceptance_task, list_acceptance_notes, list_acceptance_tasks, @@ -286,6 +287,13 @@ def create_app() -> Flask: def get_project_acceptance_notes(task_id: str): return jsonify({"data": list_acceptance_notes(task_id), "request_id": g.request_id}) + @app.route("/api/v1/acceptance-tasks//link-ai-data", methods=["GET"]) + def get_project_acceptance_link_ai_data(task_id: str): + return jsonify({ + "data": get_acceptance_link_ai_data(task_id), + "request_id": g.request_id, + }) + @app.route("/api/v1/acceptance-tasks//resume", methods=["POST"]) def resume_project_acceptance_task(task_id: str): return jsonify({"data": resume_acceptance_task(task_id), "request_id": g.request_id}) diff --git a/crawler_client.py b/crawler_client.py index 33a092e..8366a2b 100644 --- a/crawler_client.py +++ b/crawler_client.py @@ -338,6 +338,15 @@ def collect_keyword_sources(keyword: str, credential_id: str | None = None) -> d ) +def match_feishu_notes(keyword: str, task_limit: int = 100) -> dict[str, Any]: + """调用爬虫服务,比对飞书笔记和问一问采集来源。""" + return _post( + "/api/v1/xhs/feishu-notes/matches", + {"keyword": keyword, "task_limit": task_limit}, + retry_transient=True, + ) + + def resume_collection(crawler_task_id: str) -> dict[str, Any]: """沿用原爬虫任务 ID,从 framework 保存的检查点继续采集。"""