update:更新skill脚本

This commit is contained in:
lily 2026-09-24 02:01:52 +08:00
parent 0deb3cf57b
commit e1b0967cb3
3 changed files with 470 additions and 0 deletions

View File

@ -6,3 +6,6 @@ feishu:
hisense_geo:
base_url: "https://geo-api.hisense.com"
token: "" # 生产环境 x-token勿提交仓库
haoxian:
base_url: "https://resource-api.cemeta.cn"
token: "" # 好现 x-token勿提交仓库

View File

@ -0,0 +1,119 @@
---
name: hisense-sept-qa-raw-query
description: >-
Query Hisense September agentic projects for qa_raw and qa_product_raw
(HISENSE-HIST). Before any API call, must confirm BOTH batch date and project
id together. Returns id, project_name, total, qa_raw_list, qa_product_raw_list.
Use when user asks for 9月项目、问答底表、商品卡、qa_raw、qa_product_raw、
HISENSE-HIST、temp0923, or 批次日期 / 项目id.
---
# 海信 9 月 qa_raw / qa_product_raw 查询
`https://resource-api.cemeta.cn` 拉 9 月项目,再按批次查问答底表与商品卡表,组装返回 JSON。
配置:`config/config.dev.yaml``haoxian.base_url` / `haoxian.token`Header `x-token`)。本机可用 `http://localhost:9999` 代替 base路径相同
## 必选前置输入(缺一不可,须同时确认)
**使用本 skill 时,执行任何接口调用之前,必须先同时拿到下面 1 和 2。缺任意一项都停下来两项齐了再继续后续流程。不要猜、不要沿用上次对话默认值、不要只拿到一个就先跑一半。**
| # | 必选项 | 示例 |
|---|--------|------|
| 1 | 查询的批次日期 | `20260919``YYYYMMDD` |
| 2 | 项目 id | `1096` |
**一次性原文提示用户(同一条消息里同时确认两项):**
> 请同时确认后回复:
> 1. 查询的批次日期示例20260919
> 2. 项目id示例1096
用户在同一轮或连续回复中给出 1 和 2 后,再执行下方「执行流程」。若只给了一项,用同样提示补要缺失项,仍不要开查。
## 执行流程
### 1. 拉 9 月项目列表
```
GET {base_url}/api/agentic-project/project?page=1&page_size=999&listed=all
Header: x-token
```
过滤(必须同时满足):
- `listed = 1`
- `status = 'running'`
- `end_month = '2026-09'`
取出全部 `id``project_name`。用用户输入的项目 id 在列表中解析 `project_name`找不到也继续查表name 可为空并注明)。
### 2. 拼接 crawl_batch_id
```
HISENSE-HIST-{project_id}-{批次日期}
```
例:`HISENSE-HIST-1096-20260919`
### 3. 查问答底表 qa_raw
```
GET {base_url}/api/geo_label/l1/qa_raw?project_id={id}&crawl_batch_id={crawl_batch_id}&page=1&page_size=50
Header: x-token
```
翻页直到收齐 `data.total`。接口列表字段为 `data.items`
### 4. 查商品卡表 qa_product_raw
```
GET {base_url}/api/geo_label/l1/qa_product_raw?project_id={id}&crawl_batch_id={crawl_batch_id}&page=1&page_size=50
Header: x-token
```
同样翻页收齐;接口列表字段为 `data.items`
### 5. 组装返回 JSON
**对外不要输出 `items`。** `data.items` 分别映射为:
- qa_raw → `qa_raw_list`
- qa_product_raw → `qa_product_raw_list`
`total`**qa_raw**`data.total`
```json
{
"id": 1096,
"project_name": "500L左右冰箱推荐",
"total": 227,
"code": 0,
"ok": true,
"crawl_batch_id": "HISENSE-HIST-1096-20260919",
"qa_raw_list": [],
"qa_product_raw_list": []
}
```
必须包含:`id``project_name``total``qa_raw_list`(全量明细)、`qa_product_raw_list`(全量明细)。体积大时落盘并告知路径,例如:
`haoxian/skills/temp0923/data/qa_raw_{批次}/{id}_response.json`
## 推荐脚本
```bash
# 可选:刷新 9 月项目缓存
python3 haoxian/skills/temp0923/scripts/query_qa_raw.py list-projects
# 必选参数:--batch、--id
python3 haoxian/skills/temp0923/scripts/query_qa_raw.py project --batch 20260919 --id 1096
```
脚本 stdout 即为上述返回结构。本地缓存目录:`haoxian/skills/temp0923/data/`
## 注意
- Token 只读配置,勿写入 skill、勿提交仓库。
- `total=0` 或空列表均为有效结果。
- 翻页建议 `page_size=50`,失败重试,避免一次过大导致断连。

View File

@ -0,0 +1,348 @@
#!/usr/bin/env python3
"""海信 9 月项目 + qa_raw / qa_product_raw 查询resource-api / localhost"""
from __future__ import annotations
import argparse
import json
import sys
import time
import urllib.request
from pathlib import Path
ROOT = Path(__file__).resolve().parents[4] # ai_script
SKILL_DATA = Path(__file__).resolve().parents[1] / "data"
DEFAULT_BASE = "https://resource-api.cemeta.cn"
REQUIRED_END_MONTH = "2026-09"
def load_haoxian_config() -> dict:
cfg_path = ROOT / "config" / "config.dev.yaml"
if not cfg_path.exists():
raise SystemExit(f"缺少配置: {cfg_path}")
section = None
hao: dict[str, str] = {}
for line in cfg_path.read_text().splitlines():
if line.startswith("haoxian:"):
section = "hao"
continue
if line and not line.startswith(" ") and line.endswith(":"):
section = None
continue
if section == "hao" and ":" in line and line.startswith(" "):
k, v = line.split(":", 1)
hao[k.strip()] = v.strip().strip('"').strip("'")
if not hao.get("token"):
raise SystemExit("config.dev.yaml 缺少 haoxian.token")
if not hao.get("base_url"):
hao["base_url"] = DEFAULT_BASE
return hao
def http_get(url: str, token: str, retries: int = 4) -> dict:
last: Exception | None = None
for i in range(retries):
try:
req = urllib.request.Request(url, headers={"x-token": token})
with urllib.request.urlopen(req, timeout=180) as resp:
return json.loads(resp.read())
except Exception as e: # noqa: BLE001
last = e
time.sleep(1.5 * (i + 1))
raise RuntimeError(f"GET failed after retries: {url}: {last}")
def list_sept_projects(base: str, token: str) -> list[dict]:
url = f"{base.rstrip('/')}/api/agentic-project/project?page=1&page_size=999&listed=all"
data = http_get(url, token)
items = (data.get("data") or {}).get("list") or []
out = []
for p in items:
if (
p.get("listed") == 1
and str(p.get("status") or "").lower() == "running"
and p.get("end_month") == REQUIRED_END_MONTH
):
out.append(
{
"id": p.get("id"),
"project_name": p.get("project_name"),
"brand": p.get("brand"),
"category": p.get("category"),
"end_month": p.get("end_month"),
"status": p.get("status"),
"listed": p.get("listed"),
}
)
out.sort(key=lambda x: int(x["id"]) if str(x["id"]).isdigit() else 0, reverse=True)
return out
def fetch_l1_table(
base: str,
token: str,
table: str,
project_id: int | str,
batch: str,
page_size: int = 50,
) -> dict:
"""分页拉取 /api/geo_label/l1/{table},返回 {total, code, ok, crawl_batch_id, items}。"""
crawl = f"HISENSE-HIST-{project_id}-{batch}"
all_items: list = []
total = None
code = 0
page = 1
while True:
url = (
f"{base.rstrip('/')}/api/geo_label/l1/{table}"
f"?project_id={project_id}&crawl_batch_id={crawl}"
f"&page={page}&page_size={page_size}"
)
data = http_get(url, token)
code = data.get("code", 0)
if code != 0:
return {
"id": int(project_id) if str(project_id).isdigit() else project_id,
"total": 0,
"code": code,
"ok": False,
"crawl_batch_id": crawl,
"items": [],
"message": data.get("message"),
}
d = data.get("data") or {}
if total is None:
total = d.get("total", 0) or 0
page_items = d.get("items") or []
all_items.extend(page_items)
if len(all_items) >= total or not page_items:
break
page += 1
if page > 100:
break
return {
"id": int(project_id) if str(project_id).isdigit() else project_id,
"total": total,
"code": code,
"ok": True,
"crawl_batch_id": crawl,
"fetched": len(all_items),
"items": all_items,
}
def fetch_qa_raw(base: str, token: str, project_id: int | str, batch: str, page_size: int = 50) -> dict:
return fetch_l1_table(base, token, "qa_raw", project_id, batch, page_size)
def fetch_qa_product_raw(base: str, token: str, project_id: int | str, batch: str, page_size: int = 50) -> dict:
return fetch_l1_table(base, token, "qa_product_raw", project_id, batch, page_size)
def cmd_list_projects(args: argparse.Namespace) -> None:
cfg = load_haoxian_config()
base = args.base or cfg["base_url"]
projects = list_sept_projects(base, cfg["token"])
SKILL_DATA.mkdir(parents=True, exist_ok=True)
out = SKILL_DATA / "sept_projects_202609.json"
out.write_text(json.dumps(projects, ensure_ascii=False, indent=2))
print(json.dumps({"count": len(projects), "path": str(out), "projects": projects}, ensure_ascii=False, indent=2))
def cmd_batch_totals(args: argparse.Namespace) -> None:
cfg = load_haoxian_config()
base = args.base or cfg["base_url"]
batch = args.batch
projects = list_sept_projects(base, cfg["token"])
cache_dir = SKILL_DATA / f"qa_raw_{batch}"
cache_dir.mkdir(parents=True, exist_ok=True)
rows = []
for i, p in enumerate(projects, 1):
pid = p["id"]
cache = cache_dir / f"{pid}.json"
if cache.exists() and not args.refresh:
payload = json.loads(cache.read_text())
total = payload.get("total", 0)
items = payload.get("items") or []
row = {
"id": pid,
"project_name": p["project_name"],
"total": total,
"code": 0,
"ok": True,
"crawl_batch_id": payload.get("crawl_batch_id") or f"HISENSE-HIST-{pid}-{batch}",
"fetched": len(items),
}
else:
try:
raw = fetch_qa_raw(base, cfg["token"], pid, batch, page_size=args.page_size)
except Exception as e: # noqa: BLE001
row = {
"id": pid,
"project_name": p["project_name"],
"total": -1,
"code": None,
"ok": False,
"err": str(e),
}
rows.append(row)
print(f"[{i}/{len(projects)}] FAIL id={pid}: {e}", file=sys.stderr)
continue
items = raw.get("items") or []
payload = {
"project_id": pid,
"project_name": p["project_name"],
"crawl_batch_id": raw["crawl_batch_id"],
"batch_date": batch,
"total": raw["total"],
"fetched": raw.get("fetched", len(items)),
"items": items,
}
cache.write_text(json.dumps(payload, ensure_ascii=False))
row = {
"id": pid,
"project_name": p["project_name"],
"total": raw["total"],
"code": raw.get("code", 0),
"ok": raw.get("ok", True),
"crawl_batch_id": raw["crawl_batch_id"],
"fetched": payload["fetched"],
}
rows.append(row)
if i % 20 == 0 or i == len(projects):
print(f"[{i}/{len(projects)}] ...", file=sys.stderr)
summary = {
"batch_date": batch,
"base_url": base,
"count": len(rows),
"ok": sum(1 for r in rows if r.get("ok")),
"total_sum": sum(r["total"] for r in rows if r.get("ok") and r["total"] >= 0),
"projects": rows,
}
(SKILL_DATA / f"qa_raw_{batch}_summary.json").write_text(json.dumps(summary, ensure_ascii=False, indent=2))
tsv = ["project_id\tproject_name\ttotal\tok\tcrawl_batch_id"]
for r in rows:
tsv.append(
f"{r.get('id')}\t{r.get('project_name')}\t{r.get('total')}\t{r.get('ok')}\t{r.get('crawl_batch_id','')}"
)
(SKILL_DATA / f"qa_raw_{batch}_totals.tsv").write_text("\n".join(tsv) + "\n")
print(json.dumps({"batch_date": batch, "count": len(rows), "projects": rows}, ensure_ascii=False, indent=2))
def cmd_project(args: argparse.Namespace) -> None:
cfg = load_haoxian_config()
base = args.base or cfg["base_url"]
batch = args.batch
pid = args.id
cache_dir = SKILL_DATA / f"qa_raw_{batch}"
cache = cache_dir / f"{pid}.json"
product_cache = cache_dir / f"{pid}_product_raw.json"
name = None
projects_path = SKILL_DATA / "sept_projects_202609.json"
if projects_path.exists():
for p in json.loads(projects_path.read_text()):
if str(p.get("id")) == str(pid):
name = p.get("project_name")
break
if cache.exists() and not args.refresh:
payload = json.loads(cache.read_text())
qa_raw_list = payload.get("items") or payload.get("qa_raw_list") or []
crawl = payload.get("crawl_batch_id") or f"HISENSE-HIST-{pid}-{batch}"
total = payload.get("total")
code = 0
ok = True
if not name:
name = payload.get("project_name")
else:
raw = fetch_qa_raw(base, cfg["token"], pid, batch, page_size=args.page_size)
qa_raw_list = raw.get("items") or []
crawl = raw["crawl_batch_id"]
total = raw["total"]
code = raw.get("code", 0)
ok = raw.get("ok", True)
cache_dir.mkdir(parents=True, exist_ok=True)
cache.write_text(
json.dumps(
{
"project_id": raw["id"],
"project_name": name,
"crawl_batch_id": crawl,
"batch_date": batch,
"total": total,
"fetched": len(qa_raw_list),
"items": qa_raw_list,
},
ensure_ascii=False,
)
)
if product_cache.exists() and not args.refresh:
prod_payload = json.loads(product_cache.read_text())
qa_product_raw_list = (
prod_payload.get("items") or prod_payload.get("qa_product_raw_list") or []
)
else:
prod = fetch_qa_product_raw(base, cfg["token"], pid, batch, page_size=args.page_size)
qa_product_raw_list = prod.get("items") or []
if not prod.get("ok", True):
ok = False
code = prod.get("code", code)
cache_dir.mkdir(parents=True, exist_ok=True)
product_cache.write_text(
json.dumps(
{
"project_id": int(pid) if str(pid).isdigit() else pid,
"project_name": name,
"crawl_batch_id": crawl,
"batch_date": batch,
"total": prod.get("total", len(qa_product_raw_list)),
"fetched": len(qa_product_raw_list),
"items": qa_product_raw_list,
},
ensure_ascii=False,
)
)
out = {
"id": int(pid) if str(pid).isdigit() else pid,
"project_name": name,
"total": total,
"code": code,
"ok": ok,
"crawl_batch_id": crawl,
"qa_raw_list": qa_raw_list,
"qa_product_raw_list": qa_product_raw_list,
}
print(json.dumps(out, ensure_ascii=False, indent=2))
def main() -> None:
parser = argparse.ArgumentParser(description="Hisense Sept qa_raw query")
parser.add_argument("--base", default=None, help="API base, default from config / resource-api")
parser.add_argument("--page-size", type=int, default=50)
sub = parser.add_subparsers(dest="cmd", required=True)
p1 = sub.add_parser("list-projects", help="List Sept running projects")
p1.set_defaults(func=cmd_list_projects)
p2 = sub.add_parser("batch-totals", help="Fetch/totals for all Sept projects in a batch")
p2.add_argument("--batch", required=True, help="Batch date YYYYMMDD, e.g. 20260919")
p2.add_argument("--refresh", action="store_true")
p2.set_defaults(func=cmd_batch_totals)
p3 = sub.add_parser("project", help="Fetch qa_raw + qa_product_raw; stdout uses qa_raw_list")
p3.add_argument("--batch", required=True)
p3.add_argument("--id", required=True)
p3.add_argument("--refresh", action="store_true")
p3.set_defaults(func=cmd_project)
args = parser.parse_args()
args.func(args)
if __name__ == "__main__":
main()