2026-09-24 02:01:52 +08:00

349 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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()