From 73b1a0fddc48bf71644f7cd6ed62c23ebfa02e92 Mon Sep 17 00:00:00 2001 From: lily Date: Sun, 20 Sep 2026 03:47:41 +0800 Subject: [PATCH] =?UTF-8?q?update:=E8=84=9A=E6=9C=AC=E5=88=9D=E5=A7=8B?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + .../check_agentic_project_metric/README.md | 59 +++ .../check_agentic_project_metric/main.go | 417 ++++++++++++++++++ .../skills/L1-geo-html-screen-diff/SKILL.md | 132 ++++++ .../batch.example.json | 47 ++ .../L1-geo-html-screen-diff/examples.md | 28 ++ .../L1-geo-html-screen-diff/reference.md | 59 +++ .../skills/L3-geo-html-screen-diff/SKILL.md | 100 +++++ .../L3-geo-html-screen-diff/examples.md | 13 + 9 files changed, 856 insertions(+) create mode 100644 haoxian_hisense/agentic_project/check_agentic_project_metric/README.md create mode 100644 haoxian_hisense/agentic_project/check_agentic_project_metric/main.go create mode 100644 haoxian_kanaichi/skills/L1-geo-html-screen-diff/SKILL.md create mode 100644 haoxian_kanaichi/skills/L1-geo-html-screen-diff/batch.example.json create mode 100644 haoxian_kanaichi/skills/L1-geo-html-screen-diff/examples.md create mode 100644 haoxian_kanaichi/skills/L1-geo-html-screen-diff/reference.md create mode 100644 haoxian_kanaichi/skills/L3-geo-html-screen-diff/SKILL.md create mode 100644 haoxian_kanaichi/skills/L3-geo-html-screen-diff/examples.md diff --git a/.gitignore b/.gitignore index 7e77241..4463291 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ config/config.dev.yaml # generated DML dumps haoxian_hisense/agentic_project/check_agentic_project_name/dml_*.sql +haoxian_hisense/agentic_project/check_agentic_project_metric/dml_*.sql # Go *.exe diff --git a/haoxian_hisense/agentic_project/check_agentic_project_metric/README.md b/haoxian_hisense/agentic_project/check_agentic_project_metric/README.md new file mode 100644 index 0000000..1233bba --- /dev/null +++ b/haoxian_hisense/agentic_project/check_agentic_project_metric/README.md @@ -0,0 +1,59 @@ +# check_agentic_project_metric + +检查生产环境**项目表露出率**与 **L1 snapshot 露出率**是否一致。 + +## 数据源 + +1. 项目表 + `GET https://geo-api.hisense.com/api/agentic-project/project?page=1&page_size=999&listed=all` + Header: `x-token` +2. 项目指标(L1 snapshot) + `GET https://geo-api.hisense.com/api/geo_label/l1/snapshot?project_id={id}&platform=ALL&batch_date_from={date}&batch_date_to={date}&page=1&page_size=20` + Header: `x-token` + +## 项目过滤 + +与月度表对齐,须同时满足: + +- `listed = 1` +- `status = 'running'` +- `end_month = '2026-09'` +- `target_metric_name = '露出率'` + +## 比对项 + +| 项目表 | snapshot | +|--------|----------| +| `current_metric_value`(指标名=露出率) | `exposure_rate` | + +不一致时输出批量回写 SQL(以 snapshot 为准): + +```sql +UPDATE `agentic`.agentic_project +SET current_metric_value = CASE id + WHEN ... THEN ... +END +WHERE company_id = 13 AND id IN (...); +``` + +默认 `batch_date = 2026-09-12`,可用环境变量覆盖: + +```bash +BATCH_DATE=2026-09-12 go run ./haoxian_hisense/agentic_project/check_agentic_project_metric +``` + +## 运行 + +```bash +go run ./haoxian_hisense/agentic_project/check_agentic_project_metric +``` + +## 配置 + +`config/config.dev.yaml`(已 gitignore): + +```yaml +hisense_geo: + base_url: "https://geo-api.hisense.com" + token: "" # x-token,勿提交仓库 +``` diff --git a/haoxian_hisense/agentic_project/check_agentic_project_metric/main.go b/haoxian_hisense/agentic_project/check_agentic_project_metric/main.go new file mode 100644 index 0000000..ad4d325 --- /dev/null +++ b/haoxian_hisense/agentic_project/check_agentic_project_metric/main.go @@ -0,0 +1,417 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "io" + "log" + "net/http" + "os" + "strconv" + "strings" + "sync" + "sync/atomic" + "time" + + "ai_script/config" +) + +// 与月度表对齐的项目过滤;批次日期可用环境变量 BATCH_DATE 覆盖。 +const ( + requiredListed = 1 + requiredStatus = "running" + requiredEndMonth = "2026-09" + defaultBatchDate = "2026-09-12" + metricNameExposure = "露出率" + snapshotPlatform = "ALL" + maxConcurrency = 10 +) + +func main() { + cfg, err := config.Load() + if err != nil { + log.Fatalf("加载配置失败: %v", err) + } + + batchDate := strings.TrimSpace(os.Getenv("BATCH_DATE")) + if batchDate == "" { + batchDate = defaultBatchDate + } + + ctx := context.Background() + + // --------------------------------------------------------------------------- + // 1、拉取生产环境项目表 + // GET {base_url}/api/agentic-project/project?page=1&page_size=999&listed=all + // --------------------------------------------------------------------------- + projects, err := fetchProdProjects(ctx, cfg) + if err != nil { + log.Fatalf("拉取生产环境项目失败: %v", err) + } + log.Printf("[1] 生产环境项目数: %d", len(projects)) + + eligible := make([]prodProject, 0, len(projects)) + for _, p := range projects { + if !eligibleProject(p) { + continue + } + if p.TargetMetricName != metricNameExposure { + continue + } + eligible = append(eligible, p) + } + log.Printf("[1] 待比对项目: listed=%d AND status=%s AND end_month=%s AND target_metric_name=%s → %d 条", + requiredListed, requiredStatus, requiredEndMonth, metricNameExposure, len(eligible)) + + // --------------------------------------------------------------------------- + // 2、按 project_id 拉取 l1/snapshot(platform=ALL,指定 batch_date) + // GET {base_url}/api/geo_label/l1/snapshot?project_id=...&platform=ALL&batch_date_from=...&batch_date_to=... + // --------------------------------------------------------------------------- + log.Printf("[2] 拉取 snapshot: platform=%s batch_date=%s concurrency=%d", + snapshotPlatform, batchDate, maxConcurrency) + snaps := fetchSnapshots(ctx, cfg, eligible, batchDate) + log.Printf("[2] snapshot 命中: %d / %d", len(snaps), len(eligible)) + + // --------------------------------------------------------------------------- + // 3、比对:项目表 current_metric_value ↔ snapshot.exposure_rate + // --------------------------------------------------------------------------- + compareAndLog(eligible, snaps, batchDate) +} + +type prodProject struct { + ID string + CompanyID string + ProjectName string + Brand string + Category string + Status string + Listed int + EndMonth string + TargetMetricName string + CurrentMetricValue float64 + TargetUnit string +} + +func eligibleProject(p prodProject) bool { + return p.Listed == requiredListed && + strings.EqualFold(p.Status, requiredStatus) && + p.EndMonth == requiredEndMonth +} + +type snapshotMetric struct { + ProjectID string + BatchDate string + ExposureRate float64 + Platform string +} + +func fetchProdProjects(ctx context.Context, cfg *config.Config) ([]prodProject, error) { + if cfg.HisenseGeo.Token == "" { + return nil, fmt.Errorf("配置缺少 hisense_geo.token") + } + base := strings.TrimRight(cfg.HisenseGeo.BaseURL, "/") + url := fmt.Sprintf("%s/api/agentic-project/project?page=1&page_size=999&listed=all", base) + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + if err != nil { + return nil, err + } + setCommonHeaders(req, cfg.HisenseGeo.Token) + + body, err := doJSON(req) + if err != nil { + return nil, err + } + + var raw struct { + Code int `json:"code"` + Message string `json:"message"` + Data struct { + Total int `json:"total"` + List []struct { + ID json.Number `json:"id"` + CompanyID json.Number `json:"company_id"` + ProjectName string `json:"project_name"` + Brand string `json:"brand"` + Category string `json:"category"` + Status string `json:"status"` + Listed int `json:"listed"` + EndMonth string `json:"end_month"` + TargetMetricName string `json:"target_metric_name"` + CurrentMetricValue json.RawMessage `json:"current_metric_value"` + TargetUnit string `json:"target_unit"` + } `json:"list"` + } `json:"data"` + } + if err := json.Unmarshal(body, &raw); err != nil { + return nil, fmt.Errorf("解析 agentic-project 响应失败: %w", err) + } + if raw.Code != 0 { + return nil, fmt.Errorf("agentic-project 接口业务错误 code=%d message=%s", raw.Code, raw.Message) + } + + out := make([]prodProject, 0, len(raw.Data.List)) + for _, w := range raw.Data.List { + companyID := w.CompanyID.String() + if companyID == "" { + companyID = "13" + } + cur, err := parseFloatJSON(w.CurrentMetricValue) + if err != nil { + log.Printf("[1] 警告: id=%s current_metric_value 解析失败: %v raw=%s", + w.ID.String(), err, string(w.CurrentMetricValue)) + } + out = append(out, prodProject{ + ID: w.ID.String(), + CompanyID: companyID, + ProjectName: strings.TrimSpace(w.ProjectName), + Brand: strings.TrimSpace(w.Brand), + Category: strings.TrimSpace(w.Category), + Status: strings.TrimSpace(w.Status), + Listed: w.Listed, + EndMonth: strings.TrimSpace(w.EndMonth), + TargetMetricName: strings.TrimSpace(w.TargetMetricName), + CurrentMetricValue: cur, + TargetUnit: strings.TrimSpace(w.TargetUnit), + }) + } + if raw.Data.Total > len(out) { + log.Printf("[1] 警告: 接口 total=%d,本页仅返回 %d 条,可能未拉全", raw.Data.Total, len(out)) + } + return out, nil +} + +func fetchSnapshots(ctx context.Context, cfg *config.Config, projects []prodProject, batchDate string) map[string]snapshotMetric { + out := make(map[string]snapshotMetric, len(projects)) + var mu sync.Mutex + var wg sync.WaitGroup + sem := make(chan struct{}, maxConcurrency) + var done int64 + total := int64(len(projects)) + + httpClient := &http.Client{Timeout: 60 * time.Second} + base := strings.TrimRight(cfg.HisenseGeo.BaseURL, "/") + + for _, p := range projects { + p := p + wg.Add(1) + go func() { + defer wg.Done() + sem <- struct{}{} + defer func() { <-sem }() + + snap, err := fetchOneSnapshot(ctx, httpClient, base, cfg.HisenseGeo.Token, p.ID, batchDate) + n := atomic.AddInt64(&done, 1) + if n%50 == 0 || n == total { + log.Printf("[2] snapshot 进度 %d / %d", n, total) + } + if err != nil { + log.Printf("[2] snapshot 失败 project_id=%s: %v", p.ID, err) + return + } + if snap == nil { + return + } + mu.Lock() + out[p.ID] = *snap + mu.Unlock() + }() + } + wg.Wait() + return out +} + +func fetchOneSnapshot(ctx context.Context, client *http.Client, base, token, projectID, batchDate string) (*snapshotMetric, error) { + url := fmt.Sprintf( + "%s/api/geo_label/l1/snapshot?project_id=%s&platform=%s&batch_date_from=%s&batch_date_to=%s&page=1&page_size=20", + base, projectID, snapshotPlatform, batchDate, batchDate, + ) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + if err != nil { + return nil, err + } + setCommonHeaders(req, token) + + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("HTTP %d: %s", resp.StatusCode, truncate(string(body), 200)) + } + + var raw struct { + Code int `json:"code"` + Message string `json:"message"` + Data struct { + Total int `json:"total"` + Items []struct { + ProjectID json.Number `json:"project_id"` + BatchDate string `json:"batch_date"` + Platform string `json:"platform"` + ExposureRate json.RawMessage `json:"exposure_rate"` + } `json:"items"` + } `json:"data"` + } + if err := json.Unmarshal(body, &raw); err != nil { + return nil, fmt.Errorf("解析 snapshot 失败: %w", err) + } + if raw.Code != 0 { + return nil, fmt.Errorf("业务错误 code=%d message=%s", raw.Code, raw.Message) + } + if len(raw.Data.Items) == 0 { + return nil, nil + } + + item := raw.Data.Items[0] + rate, err := parseFloatJSON(item.ExposureRate) + if err != nil { + return nil, fmt.Errorf("exposure_rate 解析失败: %w raw=%s", err, string(item.ExposureRate)) + } + return &snapshotMetric{ + ProjectID: projectID, + BatchDate: strings.TrimSpace(item.BatchDate), + ExposureRate: rate, + Platform: strings.TrimSpace(item.Platform), + }, nil +} + +func compareAndLog(projects []prodProject, snaps map[string]snapshotMetric, batchDate string) { + var ( + okList []prodProject + mismatch []prodProject + missingSnap []prodProject + ) + + for _, p := range projects { + snap, ok := snaps[p.ID] + if !ok { + missingSnap = append(missingSnap, p) + continue + } + if sameMetric(p.CurrentMetricValue, snap.ExposureRate) { + okList = append(okList, p) + continue + } + mismatch = append(mismatch, p) + } + + log.Printf("========== 比对概况 ==========") + log.Printf("比对: 项目表 current_metric_value(target_metric_name=露出率) ↔ snapshot.exposure_rate") + log.Printf("batch_date=%s platform=%s", batchDate, snapshotPlatform) + log.Printf("待比对=%d, 一致=%d, 不一致=%d, 无snapshot=%d", + len(projects), len(okList), len(mismatch), len(missingSnap)) + + log.Printf("========== 不一致(共 %d 条)==========", len(mismatch)) + for _, p := range mismatch { + snap := snaps[p.ID] + log.Printf("[不一致] id=%s company_id=%s project_name=%q brand=%q category=%q", + p.ID, p.CompanyID, p.ProjectName, p.Brand, p.Category) + log.Printf(" 项目表 current_metric_value=%v%s", formatNum(p.CurrentMetricValue), p.TargetUnit) + log.Printf(" snapshot exposure_rate=%v (batch_date=%s platform=%s)", + formatNum(snap.ExposureRate), snap.BatchDate, snap.Platform) + } + + log.Printf("---------- 以 snapshot.exposure_rate 回写 current_metric_value SQL(共 %d 条)----------", len(mismatch)) + if len(mismatch) > 0 { + fmt.Println(buildBatchUpdateCurrentMetricSQL(mismatch, snaps)) + } + + log.Printf("========== 无 snapshot(共 %d 条)==========", len(missingSnap)) + for _, p := range missingSnap { + log.Printf("[缺失] id=%s project_name=%q current_metric_value=%v%s", + p.ID, p.ProjectName, formatNum(p.CurrentMetricValue), p.TargetUnit) + } + + log.Printf("比对完成: 一致=%d, 不一致=%d, 无snapshot=%d", + len(okList), len(mismatch), len(missingSnap)) +} + +// buildBatchUpdateCurrentMetricSQL 以 snapshot 露出率为准,批量回写项目表 current_metric_value。 +func buildBatchUpdateCurrentMetricSQL(mismatch []prodProject, snaps map[string]snapshotMetric) string { + var b strings.Builder + b.WriteString("-- 批量回写:snapshot.exposure_rate → current_metric_value(company_id=13)\n") + b.WriteString("UPDATE `agentic`.agentic_project\n") + b.WriteString("SET current_metric_value = CASE id\n") + ids := make([]string, 0, len(mismatch)) + for _, p := range mismatch { + snap := snaps[p.ID] + b.WriteString(fmt.Sprintf(" WHEN %s THEN %s\n", p.ID, formatNum(snap.ExposureRate))) + ids = append(ids, p.ID) + } + b.WriteString("END\n") + b.WriteString("WHERE company_id = 13\n") + b.WriteString(fmt.Sprintf(" AND id IN (%s);", strings.Join(ids, ", "))) + return b.String() +} + +func sameMetric(a, b float64) bool { + const eps = 1e-6 + diff := a - b + if diff < 0 { + diff = -diff + } + return diff < eps +} + +func formatNum(v float64) string { + return strconv.FormatFloat(v, 'f', -1, 64) +} + +func parseFloatJSON(raw json.RawMessage) (float64, error) { + s := strings.TrimSpace(string(raw)) + if s == "" || s == "null" { + return 0, nil + } + var f float64 + if err := json.Unmarshal(raw, &f); err == nil { + return f, nil + } + var str string + if err := json.Unmarshal(raw, &str); err != nil { + return 0, fmt.Errorf("非数字: %s", s) + } + str = strings.TrimSpace(strings.TrimSuffix(str, "%")) + if str == "" { + return 0, nil + } + return strconv.ParseFloat(str, 64) +} + +func setCommonHeaders(req *http.Request, token string) { + req.Header.Set("Accept", "application/json") + req.Header.Set("Content-Type", "application/json; charset=utf-8") + req.Header.Set("Origin", "https://geo.hisense.com") + req.Header.Set("Referer", "https://geo.hisense.com/") + req.Header.Set("x-token", token) +} + +func doJSON(req *http.Request) ([]byte, error) { + client := &http.Client{Timeout: 60 * time.Second} + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("HTTP %d: %s", resp.StatusCode, truncate(string(body), 300)) + } + return body, nil +} + +func truncate(s string, n int) string { + if len(s) <= n { + return s + } + return s[:n] + "..." +} diff --git a/haoxian_kanaichi/skills/L1-geo-html-screen-diff/SKILL.md b/haoxian_kanaichi/skills/L1-geo-html-screen-diff/SKILL.md new file mode 100644 index 0000000..f419bc7 --- /dev/null +++ b/haoxian_kanaichi/skills/L1-geo-html-screen-diff/SKILL.md @@ -0,0 +1,132 @@ +--- +name: l1-geo-html-screen-diff +description: >- + Compare an L1 project GEO HTML report with the haoxian geo-monitor page + (level=project) by on-screen names, percentages, copy, and source-title + order, then write a numbered test report. Supports one URL pair or a JSON + batch of project_id, html_url, and url. Use when the user asks for an L1 + or 项目级 diff of HTML vs 平台, geo-monitor, 露出率, 前三率, 中正面率, 分平台, + 露出排行, AI 认知词云, 信源内容类型, 信源发布时间, 媒体类型, 媒体名称, + 关联信源, 引用信源榜, 画面值, 批量, or a project-level numerical test report. +--- + +# L1 HTML 与平台画面对照 + +对照 L1 项目监测 HTML 与监测页。只比映射板块上写出来的名称、百分比和指定文案。除第 12 项外,不比位置、颜色、条形图还是方块。 + +计算、接口、排序细则见 [reference.md](reference.md)。示例只说明参数怎么传,见 [examples.md](examples.md)。示例 JSON 见 [batch.example.json](batch.example.json)。不要把示例里的地址、日期、目录和 JSON 当成默认值。 + +## 参数 + +两种入口。共享参数缺了就停下来问。不要沿用上次对话、示例或本文件里的地址、日期和目录。用户同时给了单条地址和 JSON 时,以 JSON 为准,按数组逐项做,不要只做消息里那一条。 + +### 单条 + +| 参数 | 用户怎么叫 | 用途 | +|---|---|---| +| `platform_url` | url地址 | 监测页完整 URL,含 `batchDate`、`level=project`、`project` | +| `html_url` | html地址 | 历史 HTML 完整 URL,有版本参数时原样使用 | +| `platform_period` | url日期 | 平台监测周期,如 `9月w38`。只核对页面日期文案 | +| `html_monitor_date` | html日期 | HTML 监测日期,如 `2026-09-15`。只核对页面日期文案 | +| `report_dir` | 测试报告目录 | 绝对路径。报告只写在这个目录下 | + +### 批量 + +用户给 JSON 数组,或给 JSON 文件路径。没给地址、也没给 JSON,就停下来问。数组为空也停下来问。 + +每一项三个字段: + +| 字段 | 用途 | +|---|---| +| `project_id` | 项目 id。必须等于该项 `url` 查询参数 `project` | +| `html_url` | 该项历史 HTML,当作 `html_url` | +| `url` | 该项监测页,当作 `platform_url` | + +`platform_period`、`html_monitor_date`、`report_dir` 仍由用户本次给出,整批共用。不要从示例 JSON 补这三个。 + +`project_id` 和 `url` 里的 `project` 不一致时,停下来问,不要改 URL 去凑。按数组顺序逐项做完 12 项。一项失败不跳过其余项。每项单独编号、单独报告。 + +用户给定的日期对应关系不是差异。不要因为一边是日期、一边是周次就判失败。 + +`batchDate`、公司、批次号、`project` 从本次 `platform_url` 和当次登录会话解析。不要写死批次号、公司号、手机号或密码,也不要把凭据写进报告。 + +## 板块对应 + +用户原文,按这个对照,不要改映射: + +- 露出率:HTML「平均露出率」= 平台「指定露出词露出率」 +- 前三率:HTML「平均前三率」= 平台「前三率」 +- 正面提及率:HTML「平均正面提及率」= 平台「中正面率」 +- HTML「各平台核心指标趋势」露出率 = 平台「指定露出词露出率 · 分平台」 +- HTML「各平台核心指标趋势」前三率 = 平台「前三率 · 分平台」 +- HTML「各平台核心指标趋势」正面提及率 = 平台「中正面率 · 分平台」 +- HTML「行业露出率排行榜」= 平台「露出排行 · 各平台明细」 +- HTML「AI 回答关键词」= 平台「AI 认知词云」 +- HTML「AI 回答正面关键词」= 平台「AI 认知词云」下的正面词 +- HTML「AI 回答负面关键词」= 平台「AI 认知词云」下的负面词 +- HTML「信源内容类型分布」= 平台「TMC 信源分析」下的信源内容类型 +- HTML「信源发布时间分布」= 平台「TMC 信源分析」下的信源发布时间 +- HTML「媒体类型」= 平台「媒体类型」 +- HTML「媒体名称」= 平台「媒体名称」 +- HTML「关联信源」= 平台「该问题的引用信源榜」 +- HTML 标题 = 平台信源标题 +- HTML 信源 = 平台渠道 +- HTML 占比这一列的对照口径 = 平台被引次数。不要把占比百分数和次数当成同一个画面字段 + +HTML「综合」= 平台「全平台」。这是约定,不算第 2 项失败。 + +平台选项:综合、DeepSeek、豆包、通义、元宝、文心、Kimi。接口值:`ALL`、`deepseek`、`doubao`、`qwen`、`yuanbao`、`baidu_wenxin`、`kimi`。 + +## 测试范围 + +12 项都要做。每一项标题写成 `测试通过✅` 或 `测试失败❌`,不要只写相同或不同。 + +1. 确认两个 URL 能否打开,截图进报告。 +2. 七个 AI 平台选项是否相同。HTML 里面综合 = 全平台,通义 = 通义千问,元宝 = 腾讯元宝,文心 = 百度文心 +3. 各选项的露出率、前三率、正面率是否相同。 +4. 各选项上,趋势露出率 / 前三率 / 正面提及率是否等于平台分平台三项。 +5. HTML「行业露出率排行榜」= 平台「露出排行 · 各平台明细」。平台这一张表同时列出各选项,不要切换平台选项。只比各自 Top 10。三步都要做,任一步不同则该项测试失败❌。 + 1. 综合的平均露出率 = 平台全平台列的平均露出率。 + 2. HTML 综合排名品牌 Top 10 = 平台全平台排名品牌 Top 10。名次和品牌名都要相同,名称逐字相同。第 11 名及以后不比。 + 3. 这 10 个品牌在 HTML 的 DeepSeek、豆包、通义、元宝、文心、Kimi 上的平均露出率,等于平台同一张表里对应列。列对应为通义=通义千问,元宝=腾讯元宝,文心=百度文心。未露出时 HTML 为空、平台为「-」,不算不同。差超过 0.05 算不同。 +6. 正面关键词、负面关键词。词云不随 AI 平台切换,七个选项同一套。下面三组文案是约定,写法不同不算失败: + 1. HTML「AI 回答关键词」= 平台「AI 认知词云」 + 2. HTML「AI 回答正面关键词」= 平台「正面词」 + 3. HTML「未监测到负面词」= 平台「中正面率100%,暂无负面词」 + 正面词还要比图片素材:HTML 词云图画出来的词,和平台正面词图画出来的词,词条相同才算相同。不比词的大小、颜色和摆放位置。有负面词时比词条,不比空态文案。 +7. 各选项的信源内容类型是否相同。 +8. 各选项的信源发布时间是否相同。 +9. 各选项的媒体类型:占比和展示名称,只取前 10。 +10. 各选项的媒体名称:占比和展示名称,只取前 10。 +11. 各选项的关联信源:标题、渠道、被引次数,只取前 30。不比序号。 +12. 各选项的关联信源:标题内容,以及序号上展示的标题是否相同。只取前 30。 + +数据不同时给出平台和 HTML 的对比。每个平台都要输出:同名但百分比不同的名称、HTML 画面值、平台画面值。没有这种差异的平台写「画面上同名占比无差异」,不要省略该平台。露出率若同名百分比相同,写条数,不要把相同行展开成差异表。第 5 项只比 Top 10,名次或品牌名不同要展开,不能只写条数。第 11 名及以后不报。 + +差超过 0.05 个点才算不同。0.05 是百分点,不是相对误差。两边按 1 位小数比,最小能看出来的差别是 0.1。13.80% 和 13.8% 差是 0,判相同。 + +## 只在一边展示 + +不要写成两张互不对照的「仅 HTML / 仅平台」名单。改成对照表,写清原因。 + +- 同一条、只是名称写法不同、百分比相同:一行写 HTML 画面名、平台画面名、百分比。例如 HTML「车」、平台「车300」。第 5 项不适用这条例外,品牌名必须逐字相同。 +- 一边因为 Top 10 或 Top 30 没印出来:写「前 10 之外,未印」或「前 30 之外,未印」,不要说成数值算错。第 5 项只比 Top 10,第 11 名及以后不报。 +- HTML 不画「其他」,平台前 10 有「其他」:单独一张表写各平台的平台画面值。若没有把 HTML 的名称挤出前 10,写明没有「仅 HTML」的类型。 +- 选项文案不同(通义 / 通义千问,元宝 / 腾讯元宝,文心 / 百度文心):放进第 2 项对照表。综合 = 全平台不记失败。其余写法不同则第 2 项为测试失败❌。 +- 平台多出来、且没有出现在本次对照画面里的字段,不报。 + +媒体类型、媒体名称只在前 10 条里判缺失。第 11 条及以后不报。 + +## 报告文件 + +目录不存在就创建。 + +项目 id 取本次该项 `platform_url` 的 `project` 参数,记为 `project`。批量时每一项各自取,不要共用上一项的 id。 + +在 `report_dir` 里找已有的 `测试报告-N-{project}.md`,只计本次这个 `project`。下一个编号是这些文件里最大 N 加 1。该项目还没有则从 1 开始。`测试报告.md`、`测试报告-N.md` 和其他项目 id 的文件不算本次编号。批量里各项互不影响编号。 + +写成 `{report_dir}/测试报告-{id}-{project}.md`。截图放在 `{report_dir}/shots-{id}-{project}/`,报告里用相对路径 `shots-{id}-{project}/文件名`。不要覆盖已有报告,不要写到 `report_dir` 以外。 + +文件开头先给结论:一致,或不完全一致。然后是 12 项结论表,每项一个结果:`测试通过✅` 或 `测试失败❌`。 + +写完后在回复里给出报告的绝对路径。批量时按 JSON 顺序逐项给结论和绝对路径。对话里只保留结论和路径,同名差异表以该文件为准。文件里的日期、URL、保存目录用本次参数,不要留示例值。 diff --git a/haoxian_kanaichi/skills/L1-geo-html-screen-diff/batch.example.json b/haoxian_kanaichi/skills/L1-geo-html-screen-diff/batch.example.json new file mode 100644 index 0000000..770b38f --- /dev/null +++ b/haoxian_kanaichi/skills/L1-geo-html-screen-diff/batch.example.json @@ -0,0 +1,47 @@ +[ + { + "project_id": 1169, + "html_url": "https://cemeta-resource-1313090634.cos.ap-beijing.myqcloud.com/workflow/3899e673-cd92-4670-8203-566cee53a121.html", + "url": "https://haoxian.cemeta.cn/geo-monitor?batchDate=2026-09-17&reportType=monitor&level=project&project=1169&line=%E5%8D%A1%E6%B3%B0%E9%A9%B0::%E6%B1%BD%E8%BD%A6%E5%B9%B3%E5%8F%B0" + }, + { + "project_id": 1170, + "html_url": "https://cemeta-resource-1313090634.cos.ap-beijing.myqcloud.com/workflow/b4007bdb-5138-4097-ba18-79b53fdc9c35.html", + "url": "https://haoxian.cemeta.cn/geo-monitor?batchDate=2026-09-17&reportType=monitor&level=project&project=1170&line=%E5%8D%A1%E6%B3%B0%E9%A9%B0::%E6%B1%BD%E8%BD%A6%E5%B9%B3%E5%8F%B0" + }, + { + "project_id": 1171, + "html_url": "https://cemeta-resource-1313090634.cos.ap-beijing.myqcloud.com/workflow/90f82b55-5c3d-480e-991f-c96d6dfc96dc.html", + "url": "https://haoxian.cemeta.cn/geo-monitor?batchDate=2026-09-17&reportType=monitor&level=project&project=1171&line=%E5%8D%A1%E6%B3%B0%E9%A9%B0::%E6%B1%BD%E8%BD%A6%E5%B9%B3%E5%8F%B0" + }, + { + "project_id": 1173, + "html_url": "https://cemeta-resource-1313090634.cos.ap-beijing.myqcloud.com/workflow/8507f96c-d167-45f9-bd78-0958bc99fba0.html", + "url": "https://haoxian.cemeta.cn/geo-monitor?batchDate=2026-09-17&reportType=monitor&level=project&project=1173&line=%E5%8D%A1%E6%B3%B0%E9%A9%B0::%E6%B1%BD%E8%BD%A6%E5%B9%B3%E5%8F%B0" + }, + { + "project_id": 1174, + "html_url": "https://cemeta-resource-1313090634.cos.ap-beijing.myqcloud.com/workflow/03a274fd-74e2-4fc8-bcc5-88b5286016f8.html", + "url": "https://haoxian.cemeta.cn/geo-monitor?batchDate=2026-09-17&reportType=monitor&level=project&project=1174&line=%E5%8D%A1%E6%B3%B0%E9%A9%B0::%E6%B1%BD%E8%BD%A6%E5%B9%B3%E5%8F%B0" + }, + { + "project_id": 1177, + "html_url": "https://cemeta-resource-1313090634.cos.ap-beijing.myqcloud.com/workflow/a5fc2516-2de6-41ab-85a1-67a94bde2181.html", + "url": "https://haoxian.cemeta.cn/geo-monitor?batchDate=2026-09-17&reportType=monitor&level=project&project=1177&line=%E5%8D%A1%E6%B3%B0%E9%A9%B0::%E6%B1%BD%E8%BD%A6%E5%B9%B3%E5%8F%B0" + }, + { + "project_id": 1179, + "html_url": "https://cemeta-resource-1313090634.cos.ap-beijing.myqcloud.com/workflow/74a34826-4088-4b8e-8b7d-4fdf12007e8b.html", + "url": "https://haoxian.cemeta.cn/geo-monitor?batchDate=2026-09-17&reportType=monitor&level=project&project=1179&line=%E5%8D%A1%E6%B3%B0%E9%A9%B0::%E6%B1%BD%E8%BD%A6%E5%B9%B3%E5%8F%B0" + }, + { + "project_id": 1187, + "html_url": "https://cemeta-resource-1313090634.cos.ap-beijing.myqcloud.com/workflow/ef43ba7c-5d22-4df7-99b9-9697d5095f67.html", + "url": "https://haoxian.cemeta.cn/geo-monitor?batchDate=2026-09-17&reportType=monitor&level=project&project=1187&line=%E5%8D%A1%E6%B3%B0%E9%A9%B0::%E6%B1%BD%E8%BD%A6%E5%B9%B3%E5%8F%B0" + }, + { + "project_id": 1199, + "html_url": "https://cemeta-resource-1313090634.cos.ap-beijing.myqcloud.com/workflow/bc21a4ab-c882-4868-adfb-829c649bf6a0.html", + "url": "https://haoxian.cemeta.cn/geo-monitor?batchDate=2026-09-17&reportType=monitor&level=project&project=1199&line=%E5%8D%A1%E6%B3%B0%E9%A9%B0::%E6%B1%BD%E8%BD%A6%E5%B9%B3%E5%8F%B0" + } +] diff --git a/haoxian_kanaichi/skills/L1-geo-html-screen-diff/examples.md b/haoxian_kanaichi/skills/L1-geo-html-screen-diff/examples.md new file mode 100644 index 0000000..362fd62 --- /dev/null +++ b/haoxian_kanaichi/skills/L1-geo-html-screen-diff/examples.md @@ -0,0 +1,28 @@ +# 调用示例 + +示例只说明参数怎么传。执行时用用户本次给的值,不要把下面的地址、日期、目录和 JSON 当成默认值。 + +## 单条 + +```text +platform_url: https://haoxian.cemeta.cn/geo-monitor?batchDate=2026-09-17&reportType=monitor&level=project&project=1174&line=%E5%8D%A1%E6%B3%B0%E9%A9%B0::%E6%B1%BD%E8%BD%A6%E5%B9%B3%E5%8F%B0&tab=exposure&platform=doubao +html_url: https://cemeta-resource-1313090634.cos.ap-beijing.myqcloud.com/workflow/03a274fd-74e2-4fc8-bcc5-88b5286016f8.html +platform_period: 9月w38 +html_monitor_date: 2026-09-15 +report_dir: /Users/lily/Desktop/haoxian_L0L1L2L3/好现灌数/卡泰驰/0917/01-L1-项目级/monitor-0917-test +``` + +`platform_period` 对应用户说的 url日期。`html_monitor_date` 对应用户说的 html日期。`project` 从 `platform_url` 的 `project` 参数取。报告写成该目录下的 `测试报告-1-1174.md`;同一项目已有编号时,编号加 1。 + +## 批量 + +地址对在 [batch.example.json](batch.example.json)。每一项的 `url` 当作 `platform_url`,`html_url` 原样使用。`project_id` 必须等于该项 `url` 里的 `project`。日期和报告目录整批共用,不写进 JSON。 + +```text +batch_json: /Users/lily/Desktop/haoxian_L0L1L2L3/好现灌数/.cursor/skills/l1-geo-html-screen-diff/batch.example.json +platform_period: 9月w38 +html_monitor_date: 2026-09-15 +report_dir: /Users/lily/Desktop/haoxian_L0L1L2L3/好现灌数/卡泰驰/0917/01-L1-项目级/monitor-0917-test +``` + +按数组顺序逐项做完 12 项。每项单独编号,例如 `测试报告-1-1169.md`、`测试报告-1-1170.md`。同一项目已有 `测试报告-N-{project}.md` 时,只给该项编号加 1。消息里如果还写了一条 url地址和 html地址,仍以 JSON 为准,不要只做那一条。 diff --git a/haoxian_kanaichi/skills/L1-geo-html-screen-diff/reference.md b/haoxian_kanaichi/skills/L1-geo-html-screen-diff/reference.md new file mode 100644 index 0000000..31dbdf6 --- /dev/null +++ b/haoxian_kanaichi/skills/L1-geo-html-screen-diff/reference.md @@ -0,0 +1,59 @@ +# L1 画面值与接口 + +本文件只在执行 `l1-geo-html-screen-diff` 时读。不要把这里的企业、批次、项目号写成死值。 + +## 数据从哪读 + +HTML 从 `var DATA` 读。L1 报告用 `brand`(`mode=simple` 时没有品牌/产品切换)。综合用 `brand.summary`、`brand.tmc_aggregate`、`brand.source_top30`、`brand.core_competitors`、`brand.wordcloud`。单个 AI 平台用 `brand.platforms[key]`。 + +平台是 L1,不是 L3。`project` 从 `platform_url` 取。 + +1. 用当次登录会话打 `resource-api.cemeta.cn`。 +2. `GET /api/geo_label/project/batch?companyId={companyId}&reportType=monitor`,用 URL 里的 `batchDate` 找到对应项,再按 `project` 取 `l1CrawlBatchIds[].batchId`。 +3. `GET /api/geo-monitor/metrics/l1?table={table}&batchIds={batchId}&projectId={project}&reportType=monitor`。`table` 用 `snapshot`、`competitor`、`tmc`。信源再加 `&platform={平台值}`,`table=source`。 +4. 词云:`GET /api/geo-monitor/projects/{project}/wordcloud?batchId={batchId}&sentiment=all`。这个接口不按 AI 平台拆。HTML 词云也是写死综合,七个选项同一套词。 + +登录账号从项目认证注册表取,跟本次企业走。不要把手机号、密码、token 写进报告。 + +## 画面百分比 + +保留 1 位小数。差超过 0.05 个点才算不同。 + +- 露出率、前三率、正面率、排行榜露出率:页面直接印的字段。HTML `fmt` 对非 0、非 100 印两位小数,平台印一位。比数值,不比小数位数。 +- 分平台:HTML 趋势序列最后一个监测日的点,对平台该平台 snapshot 的 `exposure_rate`、`top3_rate`、`positive_rate`。综合要覆盖六个平台的这三个点。 +- 信源内容类型、信源发布时间:HTML 环图图例印 `value / 正值之和 × 100`,并去掉名为「无时间」的项。平台环图同样用 `value / 全部正值之和 × 100`,图例最多 8 条,按值从高到低。 +- 媒体类型:HTML 方块去掉「其他」「未知」,再按剩余 `value` 之和重算。平台条形图按值从高到低取前 10;这 10 条 `value` 之和 ≤ 101.5 时直接印 `value`,否则 `value / 该和 × 100`。 +- 媒体名称:HTML 没有占比字段,为 `value / 本组 value 之和 × 100`,去掉「其他」「未知」后取前 10。平台与媒体类型同一条形图规则。 + +## 排行榜 + +第 5 项:HTML「行业露出率排行榜」对平台「露出排行 · 各平台明细」。不要切换 AI 平台选项。只比 Top 10,第 11 名及以后不报。 + +平台全平台列是平均露出率。其余列是 DeepSeek、豆包、通义千问、腾讯元宝、百度文心、Kimi。某品牌在某选项没有露出时,HTML 为空,平台格子是「-」,这不算不同。 + +HTML 综合榜用 `core_competitors`,本品用 `summary.avg_exposure_rate`。分平台用 `brand.platforms[key].industry_ranking`。画面名称截断后对不上(「车」对「车300」)算名称不同。 + +三步都要做,任一步不同则第 5 项测试失败❌: + +1. 综合平均露出率对平台全平台列。差超过 0.05 算不同。 +2. 综合 Top 10 的名次和品牌名,对平台全平台 Top 10。名称逐字相同。 +3. 这 10 个品牌在 DeepSeek、豆包、通义、元宝、文心、Kimi 上的露出率,对平台对应列。通义对通义千问,元宝对腾讯元宝,文心对百度文心。 + +## 词云 + +第 6 项不随 AI 平台切换。HTML 词在 `brand.wordcloud`,平台用词云接口,两边都是综合一套。 + +文案约定,不算失败:HTML「AI 回答关键词」= 平台「AI 认知词云」;HTML「AI 回答正面关键词」= 平台「正面词」;HTML「未监测到负面词」= 平台「中正面率100%,暂无负面词」。 + +正面词图片:HTML 用词云画布,平台用正面词图。比画出来的词条是否相同,不比大小、颜色和位置。负面有词时比词条;两边都空时用上面的空态文案。 + +## 关联信源 + +第 11 项按标题对齐,比渠道和被引次数,不比行号。集合相同就是测试通过✅。通义不足 30 条时,有几条比几条,两边条数不同才失败。 + +第 12 项比画面序号上的标题。 + +- HTML 顺序:`source_top30` 数组顺序,取前 30。 +- 平台顺序:接口返回的行按 `cite_count` 从高到低做稳定排序,次数相同则保持接口原顺序。不要用 `rank` 字段顺序,那个顺序和平台表格不一致。 + +同一序号标题不同、但被引次数相同:记测试失败❌,表里写序号、HTML 标题、平台标题、被引次数,并写明是并列换位,不是缺标题。 diff --git a/haoxian_kanaichi/skills/L3-geo-html-screen-diff/SKILL.md b/haoxian_kanaichi/skills/L3-geo-html-screen-diff/SKILL.md new file mode 100644 index 0000000..e7adbfe --- /dev/null +++ b/haoxian_kanaichi/skills/L3-geo-html-screen-diff/SKILL.md @@ -0,0 +1,100 @@ +--- +name: l3-geo-html-screen-diff +description: >- + Compare an L3 enterprise GEO HTML report with the haoxian geo-monitor page + by on-screen names and percentages, then write a numbered test report. Use + when the user asks for an L3 or 企业级 diff of HTML vs 平台, geo-monitor, + 露出率, 行业露出率排行榜, 信源内容类型, 信源发布时间, 媒体类型, 媒体名称, + 画面值, or an enterprise two-URL numerical test report. Do not use for L1 + 项目级 pages. +--- + +# L3 HTML 与平台画面数值对照 + +这是 L3 企业级技能。对照历史 HTML 与监测页。只比映射板块上写出来的名称和百分比。不比位置、颜色、条形图还是方块。 + +## 必填参数 + +缺任一项就停下来问,不要沿用上次对话、示例或本文件里的地址和日期。 + +| 参数 | 用户传入 | 用途 | +|---|---|---| +| `platform_url` | 监测页完整 URL | 含 `batchDate`、`reportType`。企业级,不是 `level=project` | +| `html_url` | 历史 HTML 完整 URL | 含版本参数时原样使用 | +| `html_monitor_date` | HTML 监测日期,如 `2026-09-15` | 只用于核对页面日期文案 | +| `platform_period` | 平台监测周期,如 `9月W38` | 只用于核对页面日期文案 | +| `report_dir` | 测试报告保存目录的绝对路径 | 报告写在这个目录下,不写到别的位置 | + +用户给定的日期对应关系不是差异。不要因为两边文案一个是日期、一个是周次就判失败。 + +`batchDate`、公司、批次号从本次 `platform_url` 和当次登录会话解析。不要写死批次号、公司号、手机号或密码,也不要把凭据写进报告。 + +## 板块对应 + +- HTML「综合」= 平台「全平台」 +- HTML「品牌综合,竞争格局」= 平台「露出情况概括」 +- HTML「AI 引用信源分析」= 平台「引用信源分析」 +- HTML「信源内容类型分布」= 平台「信源内容类型」 +- HTML「信源发布时间分布」= 平台「信源发布时间」 +- HTML「媒体类型」= 平台「媒体类型」 +- HTML「媒体名称」= 平台「媒体名称」 + +平台选项:综合、DeepSeek、豆包、通义、元宝、文心、Kimi。接口平台值:`ALL`、`deepseek`、`doubao`、`qwen`、`yuanbao`、`baidu_wenxin`、`kimi`。 + +## 测试范围 + +8 项都要做。每一项标题写成 `测试通过✅` 或 `测试失败❌`,不要只写相同或不同。 + +1. 确认两个 URL 页面能否正常打开展示,截图到测试报告 +2. 覆盖综合、DeepSeek、豆包、通义、元宝、文心、Kimi。HTML 里面综合 = 全平台。 +3. 各平台的露出率、前三率、正面率是否相同。 +4. 各平台的行业露出率排行榜是否相同。 +5. 各平台的信源内容类型是否相同。 +6. 各平台的信源发布时间是否相同。 +7. 各平台的媒体类型:两边都按画面占比从高到低只取前 10 条,再比这 10 条的占比是否相同、展示名称是否相同。第 11 条及以后不比。 +8. 各平台的媒体名称:两边都按画面占比从高到低只取前 10 条,再比这 10 条的占比是否相同、展示名称是否相同。第 11 条及以后不比。 + +数据不同时给出平台和 HTML 的对比。每个平台都要输出:同名但百分比不同的名称、HTML 画面值、平台画面值。 + +## 画面值 + +比的是图例、方块、条形图上印出的百分比,不是加总前的字段。字段相同而画面不同,记为不同。 + +平台多出来、且没有出现在本次对照画面里的字段,不报。媒体类型和媒体名称只在前 10 条里判缺失;第 11 条及以后不报,也不写进只在一边展示。 + +HTML 从 `var DATA` 读取。平台从 `resource-api.cemeta.cn` 的 `/api/geo-monitor/metrics/l3` 读取,`table` 取 `snapshot`、`competitor`、`tmc`,`batchIds` 用本次解析出的批次。 + +画面百分比按下面计算,保留 1 位小数。差超过 0.05 才算不同。0.05 是百分点,不是相对误差。1 位小数上能看出来的最小差别是 0.1。13.80% 和 13.8% 差是 0,判相同。 + +- 露出率、前三率、正面率、排行榜露出率:页面直接印字段,两边直接比。 +- 信源内容类型、信源发布时间:HTML 图例印存储的 `pct`。平台环图印 `value / 全部正值之和 × 100`,图例最多 8 条。 +- 媒体类型、媒体名称:先各自算出画面占比,再按占比从高到低只留前 10 条,只比这 10 条的名称和百分比。 + - 媒体类型画面占比:HTML 为 `pct / 全部 pct 之和 × 100`;平台为条形图印出的 `value`(这 10 条 `value` 之和 ≤ 101.5 时直接用 `value`,否则 `value / 该和 × 100`)。 + - 媒体名称画面占比:HTML 没有占比字段,为 `次数 / 本组次数之和 × 100`;平台与媒体类型同一条形图规则。 + +## 只在一边展示 + +不要写成两张互不对照的「仅 HTML / 仅平台」名单。改成对照表,写清原因。 + +- 同一条、只是名称写法不同、百分比相同:一行写 HTML 画面名、平台画面名、百分比。 +- 名称进了前 10、把另一边的名称挤掉:表里两边都写。被挤出的那一侧写名称和画面值,另一侧写「前 10 之外,未印」。 +- HTML 不展示、平台前 10 有的名称:HTML 列写「不展示」,平台列写名称和画面值。并写明有没有把 HTML 的名称挤出前 10。 +- 平台多出来、且没有出现在本次对照画面里的字段,不报。 + +## 报告 + +先给结论:一致,或不完全一致。然后按 8 项写结论表,每项一个结果:`测试通过✅` 或 `测试失败❌`。 + +每个有差异的平台单独列出: + +| 平台 | 名称 | HTML 画面值 | 平台画面值 | +|---|---|---|---| +| 用户本次的平台名 | 同名条目 | 画面百分比 | 画面百分比 | + +没有同名百分比差异的平台写「画面上同名占比无差异」,不要省略该平台。露出率、排行榜若相同,写条数,不要把相同行展开成差异表。 + +在 `report_dir` 里找已有的 `测试报告.md` 和 `测试报告-N.md`。下一个编号是已有最大 N 加 1。都没有则从 1 开始。`测试报告.md` 不算已有编号。 + +写成 `{report_dir}/测试报告-{id}.md`。目录不存在就创建。截图放在 `{report_dir}/shots-{id}/`,报告里用相对路径 `shots-{id}/文件名`。不要覆盖已有报告,不要写到 `report_dir` 以外,也不要改用上次的目录。 + +写完后在回复里给出报告的绝对路径。对话里只保留结论和路径,同名差异表以该文件为准。文件里的日期、URL、保存目录用本次参数,不要留示例值。 diff --git a/haoxian_kanaichi/skills/L3-geo-html-screen-diff/examples.md b/haoxian_kanaichi/skills/L3-geo-html-screen-diff/examples.md new file mode 100644 index 0000000..3d3d3b2 --- /dev/null +++ b/haoxian_kanaichi/skills/L3-geo-html-screen-diff/examples.md @@ -0,0 +1,13 @@ +# 调用示例 + +示例只说明参数怎么传。执行时用用户本次给的值,不要把下面的地址和日期当成默认值。 + +```text +platform_url: https://haoxian.cemeta.cn/geo-monitor?batchDate=2026-09-17&reportType=monitor&tab=citation +html_url: https://cemeta-resource-1313090634.cos.ap-beijing.myqcloud.com/workflow/28757ffe-f284-48bb-8af6-c7739de56fa8.html?hxv=4&hxts=2026-09-16T00%3A00%3A00 +html_monitor_date: 2026-09-15 +platform_period: 9月W38 +report_dir: /Users/lily/Desktop/haoxian_L0L1L2L3/好现灌数/卡泰驰/0917/03-L3-企业级/monitor-0917-test +``` + +这次示例里,HTML 监测日期 `2026-09-15` 对应平台监测周期 `9月W38`。报告写成该目录下的 `测试报告-1.md`。目录里已有 `测试报告-N.md` 时,编号加 1。`测试报告.md` 不算已有编号。换一批数据时,五个参数一起换。