Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4a512c776b | |||
| f225faf71b |
@@ -1,66 +0,0 @@
|
|||||||
# 竞品价格监控采集(BIZ-145 / S2)
|
|
||||||
|
|
||||||
按 BIZ-144 v2 竞品名单每周采集一次公开价格信息,落地 JSON/CSV 快照,供 BIZ-146 生成周报。
|
|
||||||
|
|
||||||
## 合规红线(硬编码于 `fetch_prices.py`,不可命令行覆盖)
|
|
||||||
|
|
||||||
| 条目 | 实现 |
|
|
||||||
|------|------|
|
|
||||||
| C1 频率 | 每周一次(cron);单店 ≤10 页;请求间隔 ≥5s |
|
|
||||||
| C2 robots.txt | 被禁止即跳过,记 `skipped_robots` |
|
|
||||||
| C3 不绕反爬 | 固定常规 UA;遇验证码/登录墙(401/403/特征词)即放弃该页 |
|
|
||||||
| C4 字段白名单 | `name / list_price / promo_text / sales_display`,只抽公开页面展示字段 |
|
|
||||||
| C5 留存 | 原始快照保留 90 天,每次运行自动清理过期目录 |
|
|
||||||
| C7 无代理/账号 | 直连、无登录态 |
|
|
||||||
|
|
||||||
## 目录结构
|
|
||||||
|
|
||||||
```
|
|
||||||
competitor-price/
|
|
||||||
├── fetch_prices.py # 采集脚本(仅标准库,Python ≥3.10)
|
|
||||||
├── targets.yaml # 竞品名单与 SKU URL(数据 Owner 维护,见文件头注释)
|
|
||||||
├── cron/
|
|
||||||
│ └── competitor-price.cron # 每周一 09:00 GMT+8
|
|
||||||
└── data/YYYY-MM-DD/
|
|
||||||
├── prices.json # 含合规元信息 run_meta + results + failures
|
|
||||||
├── prices.csv # 同数据的 CSV 版(UTF-8 BOM,Excel 可直接打开)
|
|
||||||
└── fetch.log # 运行日志
|
|
||||||
```
|
|
||||||
|
|
||||||
## 本地手动运行(调试用)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd monitoring/competitor-price
|
|
||||||
python3 fetch_prices.py # 退出码 0=全部成功,1=存在失败/跳过
|
|
||||||
```
|
|
||||||
|
|
||||||
调试时不触发父单告警:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
MULTICA_PARENT_ISSUE="" python3 fetch_prices.py # 空值视为未配置,只写日志
|
|
||||||
```
|
|
||||||
|
|
||||||
## 安装 cron(每周一 09:00 Asia/Shanghai)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
crontab -l > /tmp/mycron 2>/dev/null || true
|
|
||||||
cat cron/competitor-price.cron >> /tmp/mycron
|
|
||||||
crontab /tmp/mycron && rm /tmp/mycron
|
|
||||||
crontab -l | grep competitor-price # 验证
|
|
||||||
```
|
|
||||||
|
|
||||||
cron 机器需满足:`python3` ≥3.10、`multica` CLI 已登录(用于失败时在父单 BIZ-143 评论告警)。
|
|
||||||
服务器并非 24h 开机的话,cron 条目含 flock 防重入;漏跑可在下周一前手动补跑一次即可。
|
|
||||||
|
|
||||||
## 失败告警
|
|
||||||
|
|
||||||
任一条目非 `ok` 时,脚本在父单 BIZ-143 留一条失败汇总评论(需 `multica` 可用),
|
|
||||||
同时本地 `fetch.log` 留痕。白牌店铺未回填 URL 记为 `skipped_no_url`,属预期,不告警。
|
|
||||||
|
|
||||||
## 已知边界(如实说明)
|
|
||||||
|
|
||||||
- Tmall 店铺首页 `robots.txt` 对通用 UA 默认禁止 → 当前 4 家品牌旗舰店均记 `skipped_robots`,
|
|
||||||
这是合规设计的预期行为,不是脚本故障。要拿到真实价格,需要数据 Owner 在
|
|
||||||
`targets.yaml` 的 `sku_urls` 中回填**允许抓取的页面**,或由业务方决定改用其他公开数据源。
|
|
||||||
- 价格抽取基于页面公开 HTML 的通用正则(`"price"` 字段 / ¥ 符号),对重前端渲染页面
|
|
||||||
可能抽不到 → 记 `ok_no_price_field`,周报侧据此人工复核。
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
# BIZ-145 竞品价格采集:每周一 09:00(Asia/Shanghai)
|
|
||||||
# 安装方式见同目录 README.md「安装 cron」一节
|
|
||||||
CRON_TZ=Asia/Shanghai
|
|
||||||
0 9 * * 1 flock -n /tmp/competitor-price.lock /usr/bin/env MULTICA_PARENT_ISSUE=448cdf6f-ac97-4c7f-953f-0d92d03c6e85 python3 /home/vincent/.openclaw/workspace/costcodev/EnterpriseArchitect/monitoring/competitor-price/fetch_prices.py >> /home/vincent/.openclaw/workspace/costcodev/EnterpriseArchitect/monitoring/competitor-price/data/cron.log 2>&1
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
2026-09-20 05:32:09,160 INFO === BIZ-145 竞品价格采集启动 2026-09-20 ===
|
|
||||||
2026-09-20 05:32:09,570 WARNING robots.txt 禁止抓取,跳过:https://senshan.tmall.com
|
|
||||||
2026-09-20 05:32:14,843 WARNING robots.txt 禁止抓取,跳过:https://conba.tmall.com
|
|
||||||
2026-09-20 05:32:20,065 WARNING robots.txt 禁止抓取,跳过:https://huqingyutang.tmall.com
|
|
||||||
2026-09-20 05:32:27,297 WARNING robots.txt 禁止抓取,跳过:https://shouxian.tmall.com
|
|
||||||
2026-09-20 05:32:32,298 WARNING 平台头部白牌(每月轮换):无可用 URL(白牌未回填),跳过
|
|
||||||
2026-09-20 05:32:37,304 INFO === 完成:5 条记录,4 条失败 ===
|
|
||||||
2026-09-20 05:32:37,342 ERROR 父单告警发送失败: Command '['multica', 'issue', 'comment', 'add', '', '--content', '⚠️ 竞品价格采集失败告警(2026-09-20 05:32 GMT+8)\n\n- 森山(森宇控股) robots 禁止 https://senshan.tmall.com\n- 康恩贝 robots 禁止 https://conba.tmall.com\n- 胡庆余堂 robots 禁止 https://huqingyutang.tmall.com\n- 寿仙谷 robots 禁止 https://shouxian.tmall.com\n\n详见监控目录 data/ 下当日 fetch.log。']' returned non-zero exit status 1.
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
competitor_id,competitor,url,status,name,list_price,promo_text,sales_display
|
|
||||||
senshan,森山(森宇控股),https://senshan.tmall.com,skipped_robots,,,,
|
|
||||||
conba,康恩贝,https://conba.tmall.com,skipped_robots,,,,
|
|
||||||
huqingyutang,胡庆余堂,https://huqingyutang.tmall.com,skipped_robots,,,,
|
|
||||||
shouxiangu,寿仙谷,https://shouxian.tmall.com,skipped_robots,,,,
|
|
||||||
white_label_monthly,平台头部白牌(每月轮换),,skipped_no_url,,,,
|
|
||||||
|
@@ -1,76 +0,0 @@
|
|||||||
{
|
|
||||||
"run_date": "2026-09-20",
|
|
||||||
"tz": "Asia/Shanghai",
|
|
||||||
"compliance": {
|
|
||||||
"C1_interval_sec": 5.0,
|
|
||||||
"C1_max_pages_per_shop": 10,
|
|
||||||
"C2_robots_respected": true,
|
|
||||||
"C3_user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36 (+internal price monitoring; contact: ops@bizwingsinc.com)",
|
|
||||||
"C4_field_whitelist": [
|
|
||||||
"name",
|
|
||||||
"list_price",
|
|
||||||
"promo_text",
|
|
||||||
"sales_display"
|
|
||||||
],
|
|
||||||
"C5_retention_days": 90,
|
|
||||||
"C7_no_proxy_no_login": true
|
|
||||||
},
|
|
||||||
"results": [
|
|
||||||
{
|
|
||||||
"competitor_id": "senshan",
|
|
||||||
"competitor": "森山(森宇控股)",
|
|
||||||
"url": "https://senshan.tmall.com",
|
|
||||||
"status": "skipped_robots",
|
|
||||||
"name": "",
|
|
||||||
"list_price": "",
|
|
||||||
"promo_text": "",
|
|
||||||
"sales_display": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"competitor_id": "conba",
|
|
||||||
"competitor": "康恩贝",
|
|
||||||
"url": "https://conba.tmall.com",
|
|
||||||
"status": "skipped_robots",
|
|
||||||
"name": "",
|
|
||||||
"list_price": "",
|
|
||||||
"promo_text": "",
|
|
||||||
"sales_display": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"competitor_id": "huqingyutang",
|
|
||||||
"competitor": "胡庆余堂",
|
|
||||||
"url": "https://huqingyutang.tmall.com",
|
|
||||||
"status": "skipped_robots",
|
|
||||||
"name": "",
|
|
||||||
"list_price": "",
|
|
||||||
"promo_text": "",
|
|
||||||
"sales_display": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"competitor_id": "shouxiangu",
|
|
||||||
"competitor": "寿仙谷",
|
|
||||||
"url": "https://shouxian.tmall.com",
|
|
||||||
"status": "skipped_robots",
|
|
||||||
"name": "",
|
|
||||||
"list_price": "",
|
|
||||||
"promo_text": "",
|
|
||||||
"sales_display": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"competitor_id": "white_label_monthly",
|
|
||||||
"competitor": "平台头部白牌(每月轮换)",
|
|
||||||
"url": "",
|
|
||||||
"status": "skipped_no_url",
|
|
||||||
"name": "",
|
|
||||||
"list_price": "",
|
|
||||||
"promo_text": "",
|
|
||||||
"sales_display": ""
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"failures": [
|
|
||||||
"森山(森宇控股) robots 禁止 https://senshan.tmall.com",
|
|
||||||
"康恩贝 robots 禁止 https://conba.tmall.com",
|
|
||||||
"胡庆余堂 robots 禁止 https://huqingyutang.tmall.com",
|
|
||||||
"寿仙谷 robots 禁止 https://shouxian.tmall.com"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,283 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
BIZ-145 / S2 竞品价格采集脚本
|
|
||||||
|
|
||||||
合规红线(法务 BIZ-147 复核意见 C1–C7,全部硬编码,不可被命令行覆盖):
|
|
||||||
C1 频率封顶:单店每周 ≤1 次、单次 ≤10 页面、请求间隔 ≥5 秒
|
|
||||||
C2 遵守 robots.txt:被禁抓即跳过
|
|
||||||
C3 不绕反爬:固定常规 UA、不用 IP 池、遇验证码/登录墙即放弃该页面
|
|
||||||
C4 字段白名单:商品名称 / 页面展示标价 / 页面展示促销信息 / 页面可见销量数
|
|
||||||
C5 数据留存:原始快照 ≤90 天(--prune 清理)
|
|
||||||
C7 不使用代理/账号池:直连、无登录态
|
|
||||||
|
|
||||||
输出:data/YYYY-MM-DD/prices.json + prices.csv + fetch.log
|
|
||||||
失败告警:整体或单店失败时写日志;环境变量 MULTICA_PARENT_ISSUE 存在时
|
|
||||||
通过 multica CLI 在父单留一条失败通知评论。
|
|
||||||
"""
|
|
||||||
|
|
||||||
import csv
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
import urllib.error
|
|
||||||
import urllib.parse
|
|
||||||
import urllib.request
|
|
||||||
import urllib.robotparser
|
|
||||||
from datetime import datetime, timedelta, timezone
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
# ---------------- 合规常量(C1–C7,硬编码) ----------------
|
|
||||||
MAX_PAGES_PER_SHOP = 10 # C1
|
|
||||||
MIN_INTERVAL_SEC = 5.0 # C1
|
|
||||||
RETENTION_DAYS = 90 # C5
|
|
||||||
USER_AGENT = ( # C3/C6:常规 UA,不带伪装、附采集说明联系方式
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
|
|
||||||
"(KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36 "
|
|
||||||
"(+internal price monitoring; contact: ops@bizwingsinc.com)"
|
|
||||||
)
|
|
||||||
TIMEOUT_SEC = 20
|
|
||||||
ALLOWED_FIELDS = ("name", "list_price", "promo_text", "sales_display") # C4 白名单
|
|
||||||
|
|
||||||
# 验证码 / 登录墙特征(C3:命中即放弃,不尝试绕过)
|
|
||||||
BLOCK_PATTERNS = [
|
|
||||||
"login.taobao.com", "passport", "验证码", "verify", "captcha",
|
|
||||||
"slide.tsx", "punish", "sec.taobao",
|
|
||||||
]
|
|
||||||
|
|
||||||
BASE_DIR = Path(__file__).resolve().parent
|
|
||||||
DATA_DIR = BASE_DIR / "data"
|
|
||||||
TARGETS_FILE = BASE_DIR / "targets.yaml"
|
|
||||||
CST = timezone(timedelta(hours=8))
|
|
||||||
|
|
||||||
log = logging.getLogger("competitor-price")
|
|
||||||
|
|
||||||
|
|
||||||
def setup_logging(log_file: Path):
|
|
||||||
log.setLevel(logging.INFO)
|
|
||||||
fmt = logging.Formatter("%(asctime)s %(levelname)s %(message)s")
|
|
||||||
sh = logging.StreamHandler(sys.stdout)
|
|
||||||
sh.setFormatter(fmt)
|
|
||||||
fh = logging.FileHandler(log_file, encoding="utf-8")
|
|
||||||
fh.setFormatter(fmt)
|
|
||||||
log.addHandler(sh)
|
|
||||||
log.addHandler(fh)
|
|
||||||
|
|
||||||
|
|
||||||
def load_targets() -> list[dict]:
|
|
||||||
"""极简 YAML 解析(targets.yaml 结构固定,避免引入第三方依赖)。"""
|
|
||||||
text = TARGETS_FILE.read_text(encoding="utf-8")
|
|
||||||
competitors, cur, in_sku, in_monitor = [], None, False, False
|
|
||||||
for raw in text.splitlines():
|
|
||||||
line = raw.split("#", 1)[0].rstrip()
|
|
||||||
if not line.strip():
|
|
||||||
continue
|
|
||||||
stripped = line.strip()
|
|
||||||
if stripped.startswith("- id:"):
|
|
||||||
cur = {"id": stripped.split(":", 1)[1].strip(), "sku_urls": [],
|
|
||||||
"monitor_skus": []}
|
|
||||||
competitors.append(cur)
|
|
||||||
in_sku = in_monitor = False
|
|
||||||
elif cur is None:
|
|
||||||
continue
|
|
||||||
elif re.match(r"^- \w+:", stripped) is None and ":" in stripped and not stripped.startswith("-"):
|
|
||||||
key, _, val = stripped.partition(":")
|
|
||||||
val = val.strip().strip('"')
|
|
||||||
if key == "sku_urls":
|
|
||||||
in_sku, in_monitor = True, False
|
|
||||||
if val.startswith("[") and val != "[]":
|
|
||||||
cur["sku_urls"] = [u.strip().strip('"') for u in val.strip("[]").split(",") if u.strip()]
|
|
||||||
elif key == "monitor_skus":
|
|
||||||
in_monitor, in_sku = True, False
|
|
||||||
else:
|
|
||||||
cur[key] = val
|
|
||||||
in_sku = in_monitor = False
|
|
||||||
elif stripped.startswith("-") and in_sku:
|
|
||||||
cur["sku_urls"].append(stripped.lstrip("- ").strip('"'))
|
|
||||||
elif stripped.startswith("-") and in_monitor:
|
|
||||||
cur["monitor_skus"].append(stripped.lstrip("- ").strip('"'))
|
|
||||||
return competitors
|
|
||||||
|
|
||||||
|
|
||||||
def robots_allows(url: str) -> bool:
|
|
||||||
"""C2:遵守 robots.txt;出错时保守处理为允许但记日志。"""
|
|
||||||
parsed = urllib.parse.urlparse(url)
|
|
||||||
rp_url = f"{parsed.scheme}://{parsed.netloc}/robots.txt"
|
|
||||||
rp = urllib.robotparser.RobotFileParser()
|
|
||||||
try:
|
|
||||||
rp.set_url(rp_url)
|
|
||||||
rp.read()
|
|
||||||
return rp.can_fetch(USER_AGENT, url)
|
|
||||||
except Exception as e: # robots 不可达时不放大风险,记录并放行(人工每周低频)
|
|
||||||
log.warning("robots.txt 读取失败 %s: %s(保守放行,人工复核)", rp_url, e)
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def fetch_page(url: str) -> tuple[int | None, str]:
|
|
||||||
"""返回 (http_status, html)。不跟随登录跳转以外的异常。"""
|
|
||||||
req = urllib.request.Request(url, headers={"User-Agent": USER_AGENT,
|
|
||||||
"Accept-Language": "zh-CN,zh;q=0.9"})
|
|
||||||
try:
|
|
||||||
with urllib.request.urlopen(req, timeout=TIMEOUT_SEC) as resp:
|
|
||||||
html = resp.read(2_000_000).decode("utf-8", errors="replace")
|
|
||||||
return resp.status, html
|
|
||||||
except urllib.error.HTTPError as e:
|
|
||||||
return e.code, ""
|
|
||||||
except Exception as e:
|
|
||||||
log.warning("请求异常 %s: %s", url, e)
|
|
||||||
return None, ""
|
|
||||||
|
|
||||||
|
|
||||||
def looks_blocked(html: str, final_status: int | None) -> bool:
|
|
||||||
"""C3:命中反爬/登录墙特征 → 放弃该页面。"""
|
|
||||||
if final_status in (401, 403):
|
|
||||||
return True
|
|
||||||
low = html.lower()
|
|
||||||
return any(p.lower() in low for p in BLOCK_PATTERNS)
|
|
||||||
|
|
||||||
|
|
||||||
PRICE_RE = re.compile(r'"price"\s*:\s*"?([\d.]+)"?|¥\s*([\d]+(?:\.\d+)?)|¥\s*([\d]+(?:\.\d+)?)')
|
|
||||||
PROMO_RE = re.compile(r"(满\d+减\d+|第二件半价|买一送一|买赠|券后)")
|
|
||||||
SALES_RE = re.compile(r"(?:月销|已售|销量)\s*[::]?\s*([\d,]+\+?)")
|
|
||||||
TITLE_RE = re.compile(r"<title>(.*?)</title>", re.S)
|
|
||||||
|
|
||||||
|
|
||||||
def parse_fields(url: str, html: str) -> dict:
|
|
||||||
"""C4:只抽白名单字段,抽不到就留空并在 status 标注。"""
|
|
||||||
out = {f: "" for f in ALLOWED_FIELDS}
|
|
||||||
m = TITLE_RE.search(html)
|
|
||||||
if m:
|
|
||||||
out["name"] = re.sub(r"-(天猫|淘宝网).*$", "", m.group(1)).strip()[:120]
|
|
||||||
m = PRICE_RE.search(html)
|
|
||||||
if m:
|
|
||||||
out["list_price"] = next(g for g in m.groups() if g)
|
|
||||||
promos = PROMO_RE.findall(html)
|
|
||||||
if promos:
|
|
||||||
out["promo_text"] = ";".join(sorted(set(promos))[:5])
|
|
||||||
m = SALES_RE.search(html)
|
|
||||||
if m:
|
|
||||||
out["sales_display"] = m.group(1)
|
|
||||||
return {k: v for k, v in out.items() if k in ALLOWED_FIELDS}
|
|
||||||
|
|
||||||
|
|
||||||
def notify_parent_issue(summary: str):
|
|
||||||
"""失败告警:在父单留评论。无 MULTICA_PARENT_ISSUE 时只写日志。"""
|
|
||||||
issue = os.environ.get("MULTICA_PARENT_ISSUE") or "448cdf6f-ac97-4c7f-953f-0d92d03c6e85"
|
|
||||||
body = f"⚠️ 竞品价格采集失败告警({datetime.now(CST):%Y-%m-%d %H:%M} GMT+8)\n\n{summary}\n\n详见监控目录 data/ 下当日 fetch.log。"
|
|
||||||
try:
|
|
||||||
subprocess.run(["multica", "issue", "comment", "add", issue,
|
|
||||||
"--content", body], check=True, timeout=60,
|
|
||||||
capture_output=True)
|
|
||||||
log.info("已在父单 %s 留言告警", issue)
|
|
||||||
except Exception as e:
|
|
||||||
log.error("父单告警发送失败: %s", e)
|
|
||||||
|
|
||||||
|
|
||||||
def prune_old_snapshots():
|
|
||||||
"""C5:删除 90 天前的采集目录。"""
|
|
||||||
cutoff = datetime.now(CST).date() - timedelta(days=RETENTION_DAYS)
|
|
||||||
for d in DATA_DIR.iterdir() if DATA_DIR.exists() else []:
|
|
||||||
if d.is_dir():
|
|
||||||
try:
|
|
||||||
day = datetime.strptime(d.name, "%Y-%m-%d").date()
|
|
||||||
except ValueError:
|
|
||||||
continue
|
|
||||||
if day < cutoff:
|
|
||||||
import shutil
|
|
||||||
shutil.rmtree(d)
|
|
||||||
log.info("C5 留存清理:删除 %s", d)
|
|
||||||
|
|
||||||
|
|
||||||
def collect_one(comp: dict, today_dir: Path, results: list, failures: list):
|
|
||||||
urls = [u for u in comp.get("sku_urls", []) if u]
|
|
||||||
urls = urls or ([comp["shop_url"]] if comp.get("shop_url") else [])
|
|
||||||
if not urls:
|
|
||||||
log.warning("%s:无可用 URL(白牌未回填),跳过", comp["name"])
|
|
||||||
results.append({"competitor_id": comp["id"], "competitor": comp["name"],
|
|
||||||
"url": "", "status": "skipped_no_url",
|
|
||||||
**{f: "" for f in ALLOWED_FIELDS}})
|
|
||||||
return
|
|
||||||
urls = urls[:MAX_PAGES_PER_SHOP] # C1
|
|
||||||
for i, url in enumerate(urls):
|
|
||||||
if i > 0:
|
|
||||||
time.sleep(MIN_INTERVAL_SEC) # C1
|
|
||||||
if not robots_allows(url): # C2
|
|
||||||
log.warning("robots.txt 禁止抓取,跳过:%s", url)
|
|
||||||
failures.append(f"{comp['name']} robots 禁止 {url}")
|
|
||||||
results.append({"competitor_id": comp["id"], "competitor": comp["name"],
|
|
||||||
"url": url, "status": "skipped_robots",
|
|
||||||
**{f: "" for f in ALLOWED_FIELDS}})
|
|
||||||
continue
|
|
||||||
status, html = fetch_page(url)
|
|
||||||
if looks_blocked(html, status): # C3
|
|
||||||
log.warning("疑似登录墙/验证码(HTTP %s),放弃:%s", status, url)
|
|
||||||
failures.append(f"{comp['name']} 被反爬拦截 {url} (HTTP {status})")
|
|
||||||
results.append({"competitor_id": comp["id"], "competitor": comp["name"],
|
|
||||||
"url": url, "status": f"blocked_anti_bot_http_{status}",
|
|
||||||
**{f: "" for f in ALLOWED_FIELDS}})
|
|
||||||
continue
|
|
||||||
if not html:
|
|
||||||
failures.append(f"{comp['name']} 抓取失败 {url} (HTTP {status})")
|
|
||||||
results.append({"competitor_id": comp["id"], "competitor": comp["name"],
|
|
||||||
"url": url, "status": f"fetch_failed_http_{status}",
|
|
||||||
**{f: "" for f in ALLOWED_FIELDS}})
|
|
||||||
continue
|
|
||||||
fields = parse_fields(url, html)
|
|
||||||
row = {"competitor_id": comp["id"], "competitor": comp["name"],
|
|
||||||
"url": url, "status": "ok" if fields.get("list_price") else "ok_no_price_field",
|
|
||||||
**fields}
|
|
||||||
results.append(row)
|
|
||||||
log.info("✅ %s %s → %s", comp["name"], url, row["status"])
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
|
||||||
today = datetime.now(CST).strftime("%Y-%m-%d")
|
|
||||||
today_dir = DATA_DIR / today
|
|
||||||
today_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
setup_logging(today_dir / "fetch.log")
|
|
||||||
|
|
||||||
log.info("=== BIZ-145 竞品价格采集启动 %s ===", today)
|
|
||||||
prune_old_snapshots()
|
|
||||||
|
|
||||||
competitors = load_targets()
|
|
||||||
results: list[dict] = []
|
|
||||||
failures: list[str] = []
|
|
||||||
for comp in competitors:
|
|
||||||
collect_one(comp, today_dir, results, failures)
|
|
||||||
time.sleep(MIN_INTERVAL_SEC) # C1:店铺间同样限速
|
|
||||||
|
|
||||||
run_meta = {
|
|
||||||
"run_date": today,
|
|
||||||
"tz": "Asia/Shanghai",
|
|
||||||
"compliance": {"C1_interval_sec": MIN_INTERVAL_SEC,
|
|
||||||
"C1_max_pages_per_shop": MAX_PAGES_PER_SHOP,
|
|
||||||
"C2_robots_respected": True,
|
|
||||||
"C3_user_agent": USER_AGENT,
|
|
||||||
"C4_field_whitelist": list(ALLOWED_FIELDS),
|
|
||||||
"C5_retention_days": RETENTION_DAYS,
|
|
||||||
"C7_no_proxy_no_login": True},
|
|
||||||
"results": results,
|
|
||||||
"failures": failures,
|
|
||||||
}
|
|
||||||
(today_dir / "prices.json").write_text(
|
|
||||||
json.dumps(run_meta, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
||||||
|
|
||||||
with open(today_dir / "prices.csv", "w", newline="", encoding="utf-8-sig") as f:
|
|
||||||
w = csv.DictWriter(f, fieldnames=["competitor_id", "competitor", "url",
|
|
||||||
"status", *ALLOWED_FIELDS])
|
|
||||||
w.writeheader()
|
|
||||||
w.writerows(results)
|
|
||||||
|
|
||||||
log.info("=== 完成:%d 条记录,%d 条失败 ===", len(results), len(failures))
|
|
||||||
if failures:
|
|
||||||
notify_parent_issue("\n".join(f"- {x}" for x in failures))
|
|
||||||
return 1
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sys.exit(main())
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
# 竞品价格监控目标清单(BIZ-145 / S2)
|
|
||||||
# 数据源:BIZ-144 v2 §1 竞品名单;模板字段:BIZ-144 v2 §3
|
|
||||||
# ⚠️ sku_urls 目前为空 = 仅采集店铺首页公开信息。
|
|
||||||
# 具体 SKU 页 URL 由数据 Owner(陆云帆)每周确认后补充到本文件;
|
|
||||||
# 白牌店铺每月 1 号由执行人按 BIZ-144 v2 §1.1 #5 规则选定后回填 url。
|
|
||||||
|
|
||||||
week_timezone: "Asia/Shanghai"
|
|
||||||
|
|
||||||
competitors:
|
|
||||||
- id: senshan
|
|
||||||
name: 森山(森宇控股)
|
|
||||||
type: brand_flagship
|
|
||||||
shop_url: "https://senshan.tmall.com"
|
|
||||||
monitor_skus: ["铁皮枫斗", "铁皮石斛核心SKU"]
|
|
||||||
sku_urls: [] # TODO(owner): 填入具体 SKU 页面 URL
|
|
||||||
- id: conba
|
|
||||||
name: 康恩贝
|
|
||||||
type: brand_flagship
|
|
||||||
shop_url: "https://conba.tmall.com"
|
|
||||||
monitor_skus: ["石斛/参茸线核心SKU"]
|
|
||||||
sku_urls: []
|
|
||||||
- id: huqingyutang
|
|
||||||
name: 胡庆余堂
|
|
||||||
type: brand_flagship
|
|
||||||
shop_url: "https://huqingyutang.tmall.com"
|
|
||||||
monitor_skus: ["石斛及传统滋补礼盒"]
|
|
||||||
sku_urls: []
|
|
||||||
- id: shouxiangu
|
|
||||||
name: 寿仙谷
|
|
||||||
type: brand_flagship
|
|
||||||
shop_url: "https://shouxian.tmall.com"
|
|
||||||
monitor_skus: ["铁皮石斛", "灵芝孢子粉"]
|
|
||||||
sku_urls: []
|
|
||||||
- id: white_label_monthly
|
|
||||||
name: 平台头部白牌(每月轮换)
|
|
||||||
type: marketplace
|
|
||||||
shop_url: "" # 每月 1 号由执行人回填当月店铺 URL
|
|
||||||
monitor_skus: ["同规格散装石斛价格带下限"]
|
|
||||||
sku_urls: []
|
|
||||||
@@ -0,0 +1,426 @@
|
|||||||
|
# SOP-017:知识缺口上报 SOP(v1.1 草案)
|
||||||
|
|
||||||
|
> **版本**:v1.1 草案
|
||||||
|
> **基线**:SOP-017《知识缺口上报 SOP》v1.0(2026-07-06)
|
||||||
|
> **起草人**:刘诗妮(secretary)
|
||||||
|
> **送审人**:陆怀瑾(COO)
|
||||||
|
> **起草日期**:2026-10-29
|
||||||
|
> **关联 Issue**:BIZ-158
|
||||||
|
> **状态**:草案,待 COO 审核与刘炜承批准;批准前不替代 v1.0
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 0. 修订说明与系统定位
|
||||||
|
|
||||||
|
### 0.1 修订背景
|
||||||
|
|
||||||
|
v1.0 第 5 节 Step 3 仍指向旧工单通道。该流程已与公司现行编排规则不一致,导致知识缺口上报缺少可追踪的书面记录。
|
||||||
|
|
||||||
|
### 0.2 系统定位(新增)
|
||||||
|
|
||||||
|
| 系统 | 定位 | 用途 | 不承担的职责 |
|
||||||
|
|------|------|------|--------------|
|
||||||
|
| **Multica** | **唯一书面真相源与主工单通道** | 任务建单、缺口登记、补全任务、状态与评论回写、闭环追踪 | 无 |
|
||||||
|
| **Gitea** | 代码与文档版本控制系统 | 代码、配置、方案、报告、记忆的版本控制与审阅 | 不作为工单、知识缺口登记或任务状态跟踪通道 |
|
||||||
|
| **思源知识库** | 人类浏览与编辑层 | 最终版 SOP、指南、制度 | 不承担工单流转 |
|
||||||
|
| **memory / wiki** | Agent 查询层 | 知识检索与读取 | 不承担工单流转 |
|
||||||
|
| **WorkBoard** | 内部执行看板(辅助) | 需要时承载可执行补全卡片;不得取代 Multica issue 作为书面真相源 | 不取代 Multica 记录、状态与评论 |
|
||||||
|
|
||||||
|
> **原则**:Multica 优先,Gitea 退位。任何知识缺口只有完成 Multica 建单并回写 issue id,才视为完成“登记”。Gitea commit、PR 或仓库文件均不能替代 issue id。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 目的
|
||||||
|
|
||||||
|
解决知识缺口“发现后无书面登记、无主通道、无法追踪闭环”的问题,使每个影响任务交付的知识缺口都可查询、可审核、可补全、可量化。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 适用范围
|
||||||
|
|
||||||
|
全部 Agent(产研线、运营线、职能线),以及负责知识补全的责任人。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 知识缺口定义与判据
|
||||||
|
|
||||||
|
### 3.1 定义
|
||||||
|
|
||||||
|
**知识缺口** = 执行任务时发现知识库(memory / wiki / 思源)中找不到的、且影响任务交付或决策质量的必要知识。
|
||||||
|
|
||||||
|
### 3.2 属于知识缺口的情况
|
||||||
|
|
||||||
|
| # | 类型 | 示例 |
|
||||||
|
|---|------|------|
|
||||||
|
| 1 | 核心业务数据缺失 | 缺少业务线 KPI 历史、竞品基准或经营口径 |
|
||||||
|
| 2 | 核心 SOP / 流程文档缺失 | 没有对应流程标准,操作无法一致执行 |
|
||||||
|
| 3 | 决策依据缺失 | 选型或决策缺少可核验的数据与依据 |
|
||||||
|
| 4 | 模板 / 指南缺失 | 缺少 PRD、验收、交接等标准模板 |
|
||||||
|
| 5 | 外部信息缺失 | 需要外部文档、资质或条款且现有知识库无存档 |
|
||||||
|
| 6 | 技术 / API 文档缺失 | 缺少接口规范、配置说明或操作文档 |
|
||||||
|
| 7 | Agent 配置 / 技能文档缺失 | 新 Agent 接入缺少必要说明或配置参考 |
|
||||||
|
|
||||||
|
### 3.3 什么不算知识缺口
|
||||||
|
|
||||||
|
**直接沿用 SOP-017 v1.0 第 3 节“**不属于知识缺口的情况**”,不重复制定替代判据**:
|
||||||
|
|
||||||
|
| # | 场景 | 应对方式 |
|
||||||
|
|---|------|----------|
|
||||||
|
| 1 | 可通过 `web_search` / `tavily_search` 快速获取的公开信息 | 自行检索 |
|
||||||
|
| 2 | 可通过 `memory_search` / `wiki_search` 检索到的现有知识 | 自行读取 |
|
||||||
|
| 3 | 可通过联系对应 Agent 获得的信息 | 直接联系 |
|
||||||
|
| 4 | Agent 自身职责范围内的常识 | 无需上报 |
|
||||||
|
|
||||||
|
补充说明:任务执行中的临时疑问若不影响交付质量或决策准确性,记录在任务评论即可,不构成知识缺口。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 触发条件
|
||||||
|
|
||||||
|
完成执行前检索(SOP-016)后,满足以下任一条件即触发上报:
|
||||||
|
|
||||||
|
1. `memory_search(corpus=all, ...)`、`wiki_search` 与思源全文搜索均无匹配;
|
||||||
|
2. 检索有结果,但缺少支撑当前任务或决策的关键环节;
|
||||||
|
3. 任务执行中发现缺少必要规范、模板、指南、数据或外部存档;
|
||||||
|
4. 现有口径互相冲突,无法判定应采用哪一项。
|
||||||
|
|
||||||
|
> 外部公开信息可快速获取时,按第 3.3 节自行检索,不进入本流程。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 上报流程(Multica 主通道)
|
||||||
|
|
||||||
|
### 5.1 流程总览
|
||||||
|
|
||||||
|
```text
|
||||||
|
发现知识缺口
|
||||||
|
→ Step 1 记录:在当日 memory 日志及当前任务评论中记录
|
||||||
|
→ Step 2 通知:sessions_send 通知 COO
|
||||||
|
→ Step 3 建单:multica issue create,并在日志/评论回写 issue id
|
||||||
|
→ Step 4 审核:COO 审核接受 / 拒绝 / 升级
|
||||||
|
→ Step 5 补全卡:COO 在 Multica 创建补全任务并关联原上报 issue
|
||||||
|
→ Step 6 补全:责任 Agent 完成知识补全并提交证据
|
||||||
|
→ Step 7 闭环:COO 验收、关闭补全任务与原上报 issue,回写结果
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 逐步动作与命令
|
||||||
|
|
||||||
|
#### Step 1:记录
|
||||||
|
|
||||||
|
**动作**:在 `memory/YYYY-MM-DD.md` 追加标准格式记录;若当前已有 Multica issue 或 WorkBoard 卡片,同时在该任务评论中粘贴同一份记录。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 路径示例;按实际日期写入对应文件
|
||||||
|
memory/YYYY-MM-DD.md
|
||||||
|
```
|
||||||
|
|
||||||
|
记录必须包含第 6 节标准格式、已执行的检索方式与 query、当前任务 id / 标题。不得只写“知识缺失”而没有证据。
|
||||||
|
|
||||||
|
#### Step 2:通知 COO
|
||||||
|
|
||||||
|
**动作**:通过 `sessions_send` 通知 COO(陆怀瑾),请求审核;同时保留 Multica 建单动作。
|
||||||
|
|
||||||
|
```text
|
||||||
|
sessionKey: agent:coo:feishu:coo:direct:ou_9f73b4e54af59f038e2b754793ea0908
|
||||||
|
```
|
||||||
|
|
||||||
|
通知正文必须包含:缺口类型、一句话描述、影响、紧急程度、建议处理方式、已记录位置、已执行的检索证据。
|
||||||
|
|
||||||
|
> 通知不是登记的替代品。若 Step 3 建单失败,应立即在通知中说明失败原因,并在恢复后补建。
|
||||||
|
|
||||||
|
#### Step 3:在 Multica 建单并回写 issue id(核心步骤)
|
||||||
|
|
||||||
|
**动作**:使用 `multica issue create` 创建“知识缺口上报”书面项;从 stdout 取得 issue identifier(如 `BIZ-123`)后,立即回写当日日志和当前任务评论。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cat > gap_description.md <<'EOF'
|
||||||
|
## 📚 知识缺口上报
|
||||||
|
|
||||||
|
**缺口类型**:[核心业务数据 | SOP 缺失 | 决策依据 | 外部信息 | 技术文档 | 模板/指南缺失 | Agent 配置]
|
||||||
|
**缺口描述**:[具体缺什么,以及为什么影响当前任务或决策]
|
||||||
|
**影响范围**:[哪些任务、决策或 Agent 受影响]
|
||||||
|
**紧急程度**:[高/中/低]
|
||||||
|
**建议补全方式**:[需要谁提供、查什么、创建什么文档]
|
||||||
|
|
||||||
|
## 已执行的检索
|
||||||
|
- `memory_search(corpus=all, query="[实际 query]")`:无结果 / 结果不足
|
||||||
|
- `wiki_search(query="[实际 query]")`:无结果 / 结果不足
|
||||||
|
- 思源全文搜索 `[实际 query]`:无结果 / 结果不足
|
||||||
|
|
||||||
|
## 当前任务
|
||||||
|
- 任务:[任务标题或 id]
|
||||||
|
|
||||||
|
## 验收标准
|
||||||
|
- [ ] 缺口所需知识已补全并可检索
|
||||||
|
- [ ] 补全内容含来源、版本 / 生效日期与归档路径
|
||||||
|
- [ ] 原上报 Agent 已收到闭环通知
|
||||||
|
EOF
|
||||||
|
|
||||||
|
multica issue create \
|
||||||
|
--title "知识缺口-<领域>-<关键词>" \
|
||||||
|
--description-stdin \
|
||||||
|
--assignee coo \
|
||||||
|
--priority high \
|
||||||
|
--status backlog \
|
||||||
|
< gap_description.md
|
||||||
|
```
|
||||||
|
|
||||||
|
> 优先级口径:高 = 阻塞当前任务;中 = 有 workaround 但影响效率;低 = 可排期改进。若 Multica 当前工作区要求项目归属,应使用 `multica project list` 确认有效 `<pid>` 后追加 `--project <pid>`;不得臆造 pid。
|
||||||
|
|
||||||
|
**回写动作**:拿到 issue id 后,将下列内容追加到 `memory/YYYY-MM-DD.md` 和当前任务评论:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
**Multica 上报 issue id**:[ISS-123]
|
||||||
|
**建单时间**:[YYYY-MM-DD HH:MM]
|
||||||
|
```
|
||||||
|
|
||||||
|
只有包含 issue id 回写的记录,才计入“知识缺口上报通道是否落地”的分子(见第 7 节)。
|
||||||
|
|
||||||
|
#### Step 4:COO 审核
|
||||||
|
|
||||||
|
**动作**:COO 核对检索证据、缺口定义、影响范围、紧急程度与建议补全方式,并给出以下结论之一:
|
||||||
|
|
||||||
|
- **接受**:进入 Step 5;
|
||||||
|
- **拒绝**:在原上报 issue 评论中说明理由并关闭;
|
||||||
|
- **升级**:涉及预算、外部采购、跨部门资源或战略决策时,转刘炜承决策,并在 issue timeline 保留升级记录。
|
||||||
|
|
||||||
|
COO 不得仅在飞书消息中口头处置而不在 Multica 留下评论或状态记录。
|
||||||
|
|
||||||
|
#### Step 5:创建补全卡(Multica 补全任务)
|
||||||
|
|
||||||
|
**动作**:COO 在 Multica 创建补全任务,关联原上报 issue,并设置 stage / 依赖关系。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cat > gap_remediation.md <<'EOF'
|
||||||
|
## 补全目标
|
||||||
|
|
||||||
|
[说明需要补齐的知识及验收口径]
|
||||||
|
|
||||||
|
## 来源与证据
|
||||||
|
|
||||||
|
[原上报 issue id、检索证据、缺口影响]
|
||||||
|
|
||||||
|
## 验收标准
|
||||||
|
- [ ] 补全内容可被 memory_search / wiki_search 检索
|
||||||
|
- [ ] 文档包含版本 / 生效日期、维护者与归档路径
|
||||||
|
- [ ] 原上报 issue 中回写补全链接并通知原上报 Agent
|
||||||
|
EOF
|
||||||
|
|
||||||
|
multica issue create \
|
||||||
|
--title "[知识补全] <缺口主题>" \
|
||||||
|
--description-stdin \
|
||||||
|
--parent <原上报-issue-id> \
|
||||||
|
--stage 1 \
|
||||||
|
--assignee <领域责任人> \
|
||||||
|
--priority <high|medium|low> \
|
||||||
|
< gap_remediation.md
|
||||||
|
```
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
- `<原上报-issue-id>` 与 `<领域责任人>` 必须使用实际值,不得保留尖括号占位符;
|
||||||
|
- 责任矩阵沿用现行《知识缺口上报 SOP v1.0》第三节;无法匹配时由 COO 明确指派;
|
||||||
|
- 若同时使用 WorkBoard 承载执行,仍须以该 Multica 补全任务记录状态与验收结果,不得只维护 WorkBoard。
|
||||||
|
|
||||||
|
#### Step 6:补全
|
||||||
|
|
||||||
|
**动作**:责任 Agent 在补全任务中完成知识产出,并提交可验证证据:
|
||||||
|
|
||||||
|
1. 补全文档 / 数据 / 决策记录及其归档路径;
|
||||||
|
2. 版本号、生效日期、维护者;
|
||||||
|
3. 检索验证结果(`memory_search` / `wiki_search` 能命中);
|
||||||
|
4. 与原上报 issue 的关联说明。
|
||||||
|
|
||||||
|
补全任务的状态更新只能在实际完成后进行;不得以“已创建文档”替代“已完成补全”。
|
||||||
|
|
||||||
|
#### Step 7:闭环
|
||||||
|
|
||||||
|
**动作**:COO 验收补全内容,确认检索可命中、证据齐全后:
|
||||||
|
|
||||||
|
1. 在补全任务中回写验收结论、链接和验收人;
|
||||||
|
2. 在原上报 issue 中回写补全 issue id、验收结论与闭环时间;
|
||||||
|
3. 按 Multica 流程关闭补全任务及原上报 issue;
|
||||||
|
4. 通过 `sessions_send` 通知原上报 Agent:
|
||||||
|
|
||||||
|
```text
|
||||||
|
【知识缺口已补全】
|
||||||
|
原上报 issue:[ISS-123]
|
||||||
|
补全 issue:[ISS-456]
|
||||||
|
补全内容:[文档链接或归档路径]
|
||||||
|
验收结论:[通过 / 需返工]
|
||||||
|
后续可直接检索使用。
|
||||||
|
```
|
||||||
|
|
||||||
|
闭环的完成标志是:原上报 issue 与补全 issue 均有验收和闭环记录,且原上报 Agent 已收到通知。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 标准上报格式
|
||||||
|
|
||||||
|
### 6.1 核心模板(与 BIZ-84、`SOP_知识缺口上报_v1.0.md` 口径一致)
|
||||||
|
|
||||||
|
以下五项为必填核心字段:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## 📚 知识缺口上报
|
||||||
|
|
||||||
|
**缺口类型**:[核心业务数据 | SOP 缺失 | 决策依据 | 外部信息 | 技术文档 | 模板/指南缺失 | Agent 配置]
|
||||||
|
**缺口描述**:[具体缺什么,为什么影响任务]
|
||||||
|
**影响范围**:[哪些任务/决策/Agent 受影响]
|
||||||
|
**紧急程度**:[高/中/低]
|
||||||
|
**建议补全方式**:[希望怎么处理;需要谁提供、查什么、创建什么文档]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.2 登记证据与任务关联(随模板一并记录)
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## 已执行的检索
|
||||||
|
- `memory_search(corpus=all, query="[实际 query]")`:[结果]
|
||||||
|
- `wiki_search(query="[实际 query]")`:[结果]
|
||||||
|
- 思源全文搜索 `[实际 query]`:[结果]
|
||||||
|
|
||||||
|
## 当前任务
|
||||||
|
- 任务:[任务标题或 id]
|
||||||
|
|
||||||
|
## Multica 登记
|
||||||
|
**Multica 上报 issue id**:[ISS-123]
|
||||||
|
**建单时间**:[YYYY-MM-DD HH:MM]
|
||||||
|
```
|
||||||
|
|
||||||
|
> 字段顺序可调整;五个核心字段不得删减。issue id 只有在 `multica issue create` 成功后才能填写。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 量化口径与运营指标(改进)
|
||||||
|
|
||||||
|
### 7.1 直接计数口径
|
||||||
|
|
||||||
|
将知识缺口运营指标从“抽样评论中是否含检索证据”的代理指标,改为直接统计:
|
||||||
|
|
||||||
|
> **知识缺口上报通道落地数 = 在日志或任务评论中回写了 Multica issue id 的知识缺口上报记录数。**
|
||||||
|
|
||||||
|
### 7.2 计算方法
|
||||||
|
|
||||||
|
设:
|
||||||
|
|
||||||
|
- `G` = 周期内确认的知识缺口总数(每条缺口按唯一上报记录计数);
|
||||||
|
- `I` = 周期内含有效 Multica issue id 回写的知识缺口上报记录数。
|
||||||
|
|
||||||
|
则:
|
||||||
|
|
||||||
|
```text
|
||||||
|
通道落地率 = I / G × 100%
|
||||||
|
```
|
||||||
|
|
||||||
|
计数规则:
|
||||||
|
|
||||||
|
1. 一条缺口对应一个 Multica 上报 issue,按 issue id 去重;
|
||||||
|
2. 只有日志或任务评论中明确回写该 issue id,才计入 `I`;
|
||||||
|
3. 仅发飞书通知、仅创建 WorkBoard 卡片、仅提交仓库 commit / PR、仅口头上报,均不计入 `I`;
|
||||||
|
4. `G = 0` 时,本周期通道落地率记为“不适用(无知识缺口)”,不得记为 0%;
|
||||||
|
5. 月度巡检报告须列出:本期 `G`、本期 `I`、通道落地率、去重后的 issue id 清单及未落地原因。
|
||||||
|
|
||||||
|
### 7.3 建议目标
|
||||||
|
|
||||||
|
- **通道落地率目标:≥ 80%**;
|
||||||
|
- **P0 缺口落地率目标:100%**;
|
||||||
|
- 未落地项须在下一次巡检前完成补建或记录明确豁免原因。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 优先级与响应时限
|
||||||
|
|
||||||
|
| 优先级 | 定义 | 建议响应时限 | 建议补全时限 |
|
||||||
|
|--------|------|--------------|--------------|
|
||||||
|
| 高 / P0 | 阻塞当前任务,无法继续执行 | COO 4 小时内响应 | 24 小时内补全 |
|
||||||
|
| 中 / P1 | 有 workaround,但影响效率或质量 | COO 1 个工作日内响应 | 3 个工作日内补全 |
|
||||||
|
| 低 / P2 | 当前可正常运行,属于改进建议 | COO 1 周内响应 | 按排期补全 |
|
||||||
|
|
||||||
|
> 上表的“高 / 中 / 低”用于标准模板和 Multica 优先级;“P0 / P1 / P2”沿用 v1.0 分级语义,两者一一对应,不另行造轮子。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. COO 审核标准
|
||||||
|
|
||||||
|
| 审核项 | 标准 |
|
||||||
|
|--------|------|
|
||||||
|
| 是否真正检索过 | 上报记录包含实际 query 与结果,而非只写“已检索” |
|
||||||
|
| 是否属于知识缺口 | 对照第 3 节;第 3.3 节四类场景应驳回并引导自行处理 |
|
||||||
|
| 是否完成 Multica 登记 | 原上报 issue 已创建,issue id 已回写 |
|
||||||
|
| 优先级是否合理 | 根据影响范围与阻塞程度判断 |
|
||||||
|
| 补全方式是否可行 | 责任人、产出形式、归档位置和验收标准明确 |
|
||||||
|
| 闭环是否可验证 | 补全后能通过 `memory_search` / `wiki_search` 命中 |
|
||||||
|
|
||||||
|
### 审核结果与处理
|
||||||
|
|
||||||
|
| 结果 | 处理方式 |
|
||||||
|
|------|----------|
|
||||||
|
| 接受 | 创建关联的 Multica 补全任务并明确责任人 |
|
||||||
|
| 拒绝 | 在原上报 issue 评论中说明理由,关闭 issue |
|
||||||
|
| 升级 | 转刘炜承决策,在 issue timeline 与评论中留存记录 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. TOOLS.md 注入模板(同步替换旧流程)
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## 📚 知识缺口上报
|
||||||
|
|
||||||
|
> 完整 SOP 见 `specs/SOP-017_知识缺口上报SOP.md`;待 v1.1 批准后更新引用版本。
|
||||||
|
|
||||||
|
### 触发条件
|
||||||
|
执行 SOP-016 检索后,满足以下任一条件需上报:
|
||||||
|
1. 核心业务数据 / SOP / 决策依据缺失;
|
||||||
|
2. 缺失影响任务交付质量或决策准确性;
|
||||||
|
3. 无法通过 memory_search / wiki_search / 思源全文搜索获得必要知识。
|
||||||
|
|
||||||
|
### 不属于知识缺口的情况
|
||||||
|
直接引用 SOP-017 v1.0 第 3 节,不另行维护副本。
|
||||||
|
|
||||||
|
### 上报流程
|
||||||
|
1. **记录**:在 `memory/YYYY-MM-DD.md` 与当前任务评论中使用标准格式记录;
|
||||||
|
2. **通知**:通过 `sessions_send` 通知 COO;
|
||||||
|
3. **建单**:执行 `multica issue create`,并回写 issue id;
|
||||||
|
4. **审核与补全**:COO 审核后创建关联的 Multica 补全任务;
|
||||||
|
5. **闭环**:COO 验收并关闭 issue,通知原上报 Agent。
|
||||||
|
|
||||||
|
### 标准格式
|
||||||
|
见 SOP-017 第 6 节。核心字段:缺口类型、缺口描述、影响范围、紧急程度、建议补全方式。
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. 生效、迁移与维护
|
||||||
|
|
||||||
|
### 11.1 生效条件
|
||||||
|
|
||||||
|
- 本文件为 v1.1 **草案**,经 COO 审核并由刘炜承批准后方可生效;
|
||||||
|
- 生效后应以新文件替换现行引用,但不得直接改写 v1.0 本体,以保留历史基线;
|
||||||
|
- 同步更新各 Agent `TOOLS.md` 中旧工单通道的上报步骤。
|
||||||
|
|
||||||
|
### 11.2 迁移要求
|
||||||
|
|
||||||
|
1. 检查所有 Agent 的 `TOOLS.md` / `AGENTS.md`,将“创建旧仓库 Issue 作为上报通道”替换为 Multica 建单并回写 issue id;
|
||||||
|
2. 存量、尚未建 Multica issue 的知识缺口,按本 SOP 第 5 节补建并回写;
|
||||||
|
3. 已存在的旧仓库 issue 不自动迁移为 Multica issue,但须在巡检报告中列出待补建清单;
|
||||||
|
4. 对 Multica 建单失败项,保留失败日志,修复后补建,不以旧仓库替代。
|
||||||
|
|
||||||
|
### 11.3 维护信息
|
||||||
|
|
||||||
|
- **维护人**:COO(陆怀瑾)
|
||||||
|
- **起草协助**:刘诗妮(secretary)
|
||||||
|
- **review 周期**:每季度一次,或发生流程通道变更时即时修订
|
||||||
|
- **基线保留**:`specs/SOP-017_知识缺口上报SOP.md`(v1.0)保持零改写
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. 版本历史
|
||||||
|
|
||||||
|
| 版本 | 日期 | 变更内容 | 审批 |
|
||||||
|
|------|------|----------|------|
|
||||||
|
| v1.0 | 2026-07-06 | 初始版本;第 5 节 Step 3 使用旧工单通道 | 刘总 |
|
||||||
|
| v1.1 草案 | 2026-10-29 | 上报主通道改为 Multica;增加 issue id 回写;统一标准格式;量化改为直接计数;Gitea 退位为版本控制 | 待审 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*本草案承接 BIZ-158。批准前,SOP-017 v1.0 仍为现行基线。*
|
||||||
@@ -0,0 +1,191 @@
|
|||||||
|
# SOP-018:跨境电商统计口径纪律
|
||||||
|
|
||||||
|
> **版本**: v1.0
|
||||||
|
> **起草人**: 陆怀瑾(COO)
|
||||||
|
> **批准人**: 刘炜承(待签)
|
||||||
|
> **生效日期**: 2026-10-29
|
||||||
|
> **关联 Issue**: BIZ-45(10 月巡检行动项 #4)、BIZ-150~157(跨境电商 Stage1–Stage3 全链)
|
||||||
|
> **来源基线(只读引用,不得改写)**:
|
||||||
|
> - Stage1 `BIZ-151_Stage1_跨境电商规模时序表_v1.2.2.md`(sha256 `a7d3c958…b123631b`)
|
||||||
|
> - Stage2-1 `BIZ-152_S2-1_表D结构扩表_v1.4.md`(`530a9fc5…8f0765f0`)
|
||||||
|
> - Stage2-2 `BIZ-152_S2-2_区域口径核销_v1.2.md`(`27819a14…19e41bfb`)
|
||||||
|
> - Stage2-3 `BIZ-152_S2-3_政策变量建模骨架_v1.3.md`(`122ab64f…2e2a50a2`)
|
||||||
|
> - Stage3 终稿 `BIZ-150_跨境电商趋势简报_终稿_v1.2.md`(`c3415381…f9d6002`)
|
||||||
|
> - 飞书决策版摘要 `BIZ-150_飞书决策版摘要_v1.2.md`(`6d209910…f406`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 目的
|
||||||
|
|
||||||
|
跨境电商规模、结构、政策变量三类数据的公开来源高度混杂,且多组数值**数值相近、口径不同**,最易发生「混用基数 → 算术全错 → 结论句失真」。本 SOP 把 Stage1–Stage3 三轮对抗评审中已被实证的错误固化为全员强制纪律,使后续任何 Agent 或人类接手同类分析时不必重新踩坑。
|
||||||
|
|
||||||
|
**适用一句话判断**:只要分析涉及跨境电商的规模、增速、出口额、目的国/来源国、分省占比、CR5/CR10、政策变量影响,**全部适用**,无例外。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 三条最贵的坑(先看这三条)
|
||||||
|
|
||||||
|
| # | 坑 | 后果 | 纪律 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 1 | **商指针「出口销售额 28388.5 亿元」误当海关出口额** | 与海关出口 22672 亿元差 +25.2%,会把 2025 出口增速由真实 5.4% 虚增至约 31% | 出口基数**只取 22672 亿元**;28388.5 禁入任何计算 |
|
||||||
|
| 2 | **「2.84 万亿元」与「28388.5 亿元」当作同一值混用** | 两者相差 11.5 亿元,相对差 **0.0405%**(= 11.5 ÷ 28388.5) | 二者**禁混用**;引用时必须二选一并写明单位 |
|
||||||
|
| 3 | **海关全业态 / 海关验放零售 B2C / 网经社广义三口径拼接算增速** | 零售旧口径(360.2→1862.1 亿元)与全业态量级差 7–8 倍;网经社对海关倍数 2018≈8.5、2025≈6.6,**倍数随基数变化** | 后两口径分口径单列,**禁跨期取倍数、禁拼序列** |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 唯一主序列与基数纪律
|
||||||
|
|
||||||
|
### 3.1 主序列
|
||||||
|
- **唯一主序列 = 海关全业态跨境电商进出口**。任何趋势、增速、结构分析只以该序列为底。
|
||||||
|
- 出口基数(2025):**22672 亿元**。
|
||||||
|
- 商指针 28388.5 亿元、艾瑞 28.4%、网经社广义、海关验放零售旧口径:**一律禁入主序列与任何计算**。
|
||||||
|
|
||||||
|
### 3.2 两对基数不可混引(Stage1 R9 / Stage2 S2-1 v1.1 实证)
|
||||||
|
|
||||||
|
| 基数对 | 差值 | 相对差 | 适用 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 28400 ↔ 28388.5 | 11.5 亿元 | **0.0405%** | 同一「2025 年出口规模」表述的两种写法 |
|
||||||
|
| 28374 ↔ 28388.5 | 14.5 亿元 | **0.0511%** | Stage1 R9 另一基数对 |
|
||||||
|
|
||||||
|
> **禁止**:把 0.051% 挂到 28400 vs 28388.5 上。该错误已在 Stage2-1 v1.1 出现三处(§4.2、§七 #8、§六 结论句 7),由评审方误引 R9 数值引发,执行方照抄固化 —— **教训:复检方给错的数值,执行方照抄即成系统性错误,双方都有责**。
|
||||||
|
|
||||||
|
### 3.3 初步数 ≠ 最终数
|
||||||
|
海关每年调查两次(上年全年 + 本年上半年),公布值会修订:
|
||||||
|
- 2025:初步 2.75 万亿 → 正式 2.84 万亿
|
||||||
|
- 2024:初步 2.63 万亿 → 修订后约 2.71 万亿
|
||||||
|
- 2021:公布 1.98 万亿后隐含下修至约 1.92 万亿
|
||||||
|
|
||||||
|
**跨版本增速不采**;引用时必须写明「初步 / 正式(修订后)」。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 论断强度纪律(数值能不能进结论句)
|
||||||
|
|
||||||
|
### 4.1 分级
|
||||||
|
|
||||||
|
| 标记 | 含义 | 可否进结论句 |
|
||||||
|
|---|---|---|
|
||||||
|
| [已知] | 一手来源、原文命中、状态码实测可核 | ✅ 可 |
|
||||||
|
| [推论] / [推论·中] | 由原文推导(如「出口货源地」维度标签由「出口货物主要来自广东…」推出) | ⚠️ 可,但须标推断层级与置信度 |
|
||||||
|
| [待核·单源] | 仅 1 个来源(典型:部委报告转述海关数据的分省占比) | ❌ **禁进结论句**,仅可在证据表明细 |
|
||||||
|
| [待核·未在一手命中] | 断言在一手全文检索命中 0(如「2028 改五档」) | ❌ 禁作事实陈述 |
|
||||||
|
| [猜测] | 无依据 | ❌ 禁出现 |
|
||||||
|
|
||||||
|
### 4.2 「单源不进结论句」的完整判据(Stage2-2 教训)
|
||||||
|
**不是只查「分点值」**。判定要看结论句内**每一个数值**的来源条数:
|
||||||
|
- 案例:Stage2-2 v1.1 结论句删掉了 49.4%、16.7% 等分点值,却仍写了「五省合计 81.2%」「进口合计 45.4%」——这两项与分点值同为**单源一手**(商务部《2025 中国数字贸易发展报告》转述海关数据,无独立第二来源),仍属单源值进结论句。
|
||||||
|
- **正确形态**:结论句只陈述「原文已取得、载体、链接实测状态码、数值明细见证据表」,**不复述任何单源数值**。
|
||||||
|
|
||||||
|
### 4.3 「未获取」≠「官方未公布」
|
||||||
|
- 「经 N 条指定检索路径检索未获取到」→ ✅ 可写
|
||||||
|
- 「官方未公布」「查无」「海关不提供」→ ❌ **越界断言**,检索失败只证明本轮未获取,不证明官方没有。
|
||||||
|
|
||||||
|
### 4.4 反面证据必须前置披露
|
||||||
|
执行方已抓到的一手数值若因保守未写入,按事实错误处理;**只引支持性证据、不引反面证据,构成选择性取证**。
|
||||||
|
- 实例:Stage2-3 v1.0 只写 Drewry WCI +2%,漏写 SSE 官方 CCFI −2.41%、波红航线 −5.39% 领跌 → 打回。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 时态与内生关系纪律
|
||||||
|
|
||||||
|
### 5.1 「已生效在执行中」≠「影响已内生于既有基数」
|
||||||
|
判断方法:**政策生效时点 vs 观测区间上界**。生效时点晚于观测上界 → **零内生**,是后续区间的新增扰动。
|
||||||
|
|
||||||
|
| 变量 | 生效时点 | 2026H1 观测上界 | 内生判定 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 欧盟 150 欧关税减免取消(Reg (EU) 2026/382) | 2026-07-01 | 2026-06-30 | **零内生**(唯一) |
|
||||||
|
| 美国 de minimis | 2025-05-02 对华港 / 2025-08-29 全球 / 2026-06/07 CBP 无限期暂停并入规章 | — | 已(部分)内生,归因待剥离 |
|
||||||
|
| GPSR | 2024-12-13(Art 52 适用;Art 51 为祖父条款) | — | 已(部分)内生,归因待剥离 |
|
||||||
|
| 红海绕行 | 持续 | — | 已(部分)内生,归因待剥离 |
|
||||||
|
|
||||||
|
### 5.2 未来时点必须单列
|
||||||
|
OBBBA §70531(b) **2027-07-01** 法定永久终止属**未来时点**,不得省略,否则读者会误以为该变量已封顶。
|
||||||
|
|
||||||
|
### 5.3 法律性质不得偷换
|
||||||
|
- 欧盟 150 欧 = **取消 ≤150 欧元的关税减免**(2026-07-01→2028-07-01 按 EUR 3/件,仅限 IOSS 与邮政路径;Art 3 自 2026-10-01 每月评估)
|
||||||
|
- ❌ 不是「收紧至 150 欧」;❌ 不是「增值税门槛」(从未涉及进口增值税)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 来源与链接可访问性纪律
|
||||||
|
|
||||||
|
### 6.1 四类定性法(同站对照实验:首页 + 栏目页 + 目标路径三连测)
|
||||||
|
|
||||||
|
| 类型 | 特征 | 处置 |
|
||||||
|
|---|---|---|
|
||||||
|
| 站点反爬 | 该站**所有**路径同一拦截码,返回 js 校验页 | **≠ 死链**。属有效一手来源,正文经代理通道取;**不得进失败链接表**(如 customs.gov.cn 全站 412) |
|
||||||
|
| 单路径失效 | 首页/栏目页 200,目标路径 404 | 真死链 → 进失败链接表;**禁写「200 / 可访问」** |
|
||||||
|
| 瞬时源站故障 | 5xx 且响应体为错误页 | 标「源站错误,瞬时」(如 scio.gov.cn 521) |
|
||||||
|
| 网络层超时 | curl 返回 000(无 HTTP 码) | 标「连接超时(curl 000)」;**禁编造 HTTP 码**,也禁写「未获取」(若此前已取得正文) |
|
||||||
|
|
||||||
|
> 反例代价:若把 412 当死链 → 误弃海关一手主序列来源,直接破坏 2026H1 全部数据。
|
||||||
|
|
||||||
|
### 6.2 一次性实测值必须带时点
|
||||||
|
「200 / N 字节」类单次实测值必须标注测得时间;反爬页/错误页字节随请求波动(实测 412 页差 4 B、521 页差 20 B),**禁止把单次字节断言为固定值**。可写「约 X KB」。
|
||||||
|
|
||||||
|
### 6.3 指控前先找原始下载件
|
||||||
|
涉及「臆造/伪造」的指控,必须先在磁盘找执行方的原始下载件/中间产物(如 `/tmp/*.pdf`):
|
||||||
|
- 找不到 → 只能说「无来源、不可复现」
|
||||||
|
- 找到了 → 应定性为「未标时点、复核期不可复现」,**不是编造**
|
||||||
|
- 真实案例:Stage2-3 v1.1 曾把 UNCTAD PDF 写成 200/10,463,733 B,COO 一度定性「臆造」;后经 `/tmp/rmt2024.pdf` 实物(合法 PDF,2026-10-28 13:32 下载)证明字节逐字节吻合 → 撤回定性。
|
||||||
|
|
||||||
|
### 6.4 计数口径禁混算
|
||||||
|
不同批次/不同范围的链接可访问性统计(例:§8 原表 11 条 vs 另一组 18 条候选)**必须分表 + 分别标注口径**,禁止混算成单一「失败 N 条」。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 交付与版本纪律
|
||||||
|
|
||||||
|
1. **交付即冻结**:执行方一落盘,评审方立即复制审计副本并记录 sha256;冻结必须早于任何后续派发。
|
||||||
|
2. **禁止就地改写**:每次修订必须新出版本文件。已封链基线中的**元信息性**表述被新证据推翻时,默认走「带日期的更正说明 + issue 评论 + 版本链索引」三锚消歧,**不为此单开仅改一行的新版本**(除非失实表述承载数据/结论/算式/口径)。
|
||||||
|
3. **阶段间依赖门控**:Stage2-3 放行须 Stage2-1 通过;Stage3 放行须全部上游通过。任何卡未通过评审 → 下游保持 todo,不放行。
|
||||||
|
4. **衍生件必须单独评审**:全链收口 ≠ 收口后新增产物自动免检。要发给老板的对外件必须单独走一轮对抗评审(实例:飞书决策版摘要 v1.0 曾自行新增终稿中没有的法条号与日期,且标「置信度:高」→ 打回)。
|
||||||
|
5. **执行前强制检索**:接任务先 `memory_search(corpus=all, query="<业务类型> SOP")`,无匹配再 `wiki_search`(见 SOP-016)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 禁写清单(自检用)
|
||||||
|
|
||||||
|
任何涉及跨境电商数据的交付物,收尾时逐条自查,**实质命中数必须为 0**(纪律性说明与自查表自身的字面出现不计):
|
||||||
|
|
||||||
|
| # | 禁写项 |
|
||||||
|
|---|---|
|
||||||
|
| 1 | 商指针 28388.5 进入任何计算或主序列 |
|
||||||
|
| 2 | 艾瑞 28.4% / 36.5% 进入结论或与海关直接对比 |
|
||||||
|
| 3 | 网经社 18.68 万亿作为主序列 |
|
||||||
|
| 4 | 零售旧口径 360.2/499.6/902.4/1347/1862.1 用于增速计算 |
|
||||||
|
| 5 | 「2.84 万亿元」与「28388.5 亿元」混用 |
|
||||||
|
| 6 | 28400↔28388.5 的相对差写成 0.051%(应为 0.0405%) |
|
||||||
|
| 7 | 已作废分省值 51.1% / 58.7% 出现于任何段落 |
|
||||||
|
| 8 | 2024 二手结构值与 2026H1 一手值拼成序列 |
|
||||||
|
| 9 | 单源值 / 待核值进入结论句 |
|
||||||
|
| 10 | 「官方未公布」「查无」类越界断言 |
|
||||||
|
| 11 | 「收紧至 150 欧」「增值税门槛」「2026 年新规(指 GPSR)」 |
|
||||||
|
| 12 | 失败链接口径混算 |
|
||||||
|
| 13 | 臆造状态码;412 写入失败链接表;S6/S7 写作「200 / 可访问」 |
|
||||||
|
| 14 | 占位符 / TBD / 待填类标记 |
|
||||||
|
| 15 | 未经指定路径的插值、推测或邻近口径顶替 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 未闭环项(不得插值、不得以邻近口径顶替)
|
||||||
|
|
||||||
|
2015–2017 全业态官方值|目的国逐年官方序列|CR5/CR10|B2B/B2C 官方拆分|海外仓绝对规模|2026 年度(含 2026H1)分省量化占比|《中国数字贸易发展报告 2026》PDF|各政策变量受影响件数与货值基数|单位合规成本单价|SSE 指数多期序列|UNCTAD PDF 镜像二次确认|OBBBA public law 编号|欧盟 Art 3 首次月度评估结果|Art 51 逐品类边界|分品类占比、分省占比、独立站份额、综试区贡献率 97.2%(商指针口径)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. 存储与分发(BIZ-19)
|
||||||
|
|
||||||
|
| 层 | 系统 | 本 SOP 落位 |
|
||||||
|
|---|---|---|
|
||||||
|
| 人类层 | 思源知识库 | `/规范/跨境电商统计口径纪律 v1.0` |
|
||||||
|
| Agent 层 | Gitea(EnterpriseArchitect) | `specs/SOP-018_跨境电商统计口径纪律_v1.0.md` |
|
||||||
|
| 查询层 | OpenClaw Wiki | 由记忆索引同步,供 `memory_search` 命中 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. 变更记录
|
||||||
|
|
||||||
|
| 版本 | 日期 | 变更 | 起草/评审 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| v1.0 | 2026-10-29 | 首版。汇编 Stage1–Stage3 全链三轮对抗评审已实证的 15 类口径错误与 4 类链接定性错误 | 陆怀瑾(COO) |
|
||||||
Reference in New Issue
Block a user