---
name: aia-article-publish
description: 调 aia (月栖 GEO) 的 HTTP API 做 AI 搜索内容营销闭环 —— 让品牌内容被豆包/DeepSeek/通义等 AI 答案引用。覆盖五件事：建/扩监控问题(monitors)、查内容缺口决定写什么(content-gaps)、提交文章带 target_questions（注册为品牌级追踪问题、周期性问 AI）、即时验证 AI 是否引用(instant-check polling)、长期命中分析(hit-analysis，看引用率 citation_rate)，以及拉取 AI 实际搜索词+思考链+引用源当改稿原材料。只要任务涉及"给某品牌做 GEO / AI 搜索优化 / 让品牌被 AI 提及或引用 / 写文章投放后追踪 AI 有没有采用 / 分析为什么内容没被 AI 引用 / 配置 AI 监控问题 / 调月栖或 zizhanai 的 API"，就用这个 skill —— 即使用户没明说"aia"或"月栖"也要触发。不适用：普通 SEO（不追踪 AI 引用）、aia dashboard 自身的前端 UI 开发（那用 aia-dashboard-ui skill）。
metadata:
  type: skill
  scope: 跨项目 / aia API 调用方
  version: 1.0
  api_version: v1
  ralph: Ralph#50
---

# aia GEO 内容闭环 — Claude Code 调用指南

aia（月栖 GEO）是一个 AI 营销监控产品。本 skill 教 Claude Code 怎么**调它的 API 把品牌内容塞进 AI（豆包/DeepSeek/通义等）的答案引用集** —— 不只是把文章发到某个网站，而是追踪并优化"AI 回答相关问题时会不会引用你"。

> 三大能力：**①管理监控**（设定要追踪哪些 AI 问题）→ **②内容闭环**（查缺口→写→提交→验证→命中分析）→ **③分析原材料**（拉 AI 实际搜索词/思考链/引用源反推改稿）。

闭环长这样：

```
1. 调 GET /v1/brands/{id}/content-gaps  → 拿到"该写什么"的 brief
2. 写文章（任何平台：微信 / 知乎 / 自家站）
3. 调 POST /v1/articles 提交 URL + content_markdown + target_questions + ...
   （target_questions 每条会注册为品牌级「追踪问题」，去重后周期性拿去问 AI）
4. Poll GET /v1/articles/{id}/instant-check  → 拿到 3 个 LLM × 3 个 question 的即时验证
5. （等追踪问题跑批数天后）调 GET /v1/articles/{id}/hit-analysis  → 引用率(citation_rate) + 长期命中分析
```

---

## 0. 适用场景

你 (CC) 在为某个客户写 GEO 内容（让品牌进入 AI 答案引用集）时，用这个 skill。**不适用**：
- 普通 SEO 内容写作（不需要追踪 AI 引用）
- aia dashboard 自身的 UI 开发（用 `aia-dashboard-ui` skill）
- 给客户写"普通"博客（不需要闭环数据）

---

## 1. 环境配置

```bash
export AIA_API_BASE="https://zizhanai.com"       # 默认生产域名（apex 的 /v1/ 走后端）；或客户给的实际域名
export AIA_API_KEY="ak_live_<your-api-key>"      # operator 给你的 key（不是 ZHIPU/DEEPSEEK key）
```

API key 走 `Authorization: Bearer <key>` header。

---

## 1.5 管理监控（前置 — monitor 不够时先做这步）

闭环以 **target_questions（品牌级追踪问题）** 为核心驱动：提交文章时声明的每条 target_question 会被注册成追踪问题，去重后周期性问 AI，引用到你 URL 即算命中。`content-gaps` 仍然靠 monitor 的 query 反推「该写什么」 —— 所以 monitor 依旧有用（决定选题方向），但**命中检测不再依赖 `expected_monitor_ids`**。如果 brand 下 monitor 太少（< 3 个）或 query 太少，`content-gaps` 给不出有用的 brief，仍建议先建/扩 monitor。

api-key 直连默认按 operator 放行，且只能操作自己 org —— 所以 CC 可以直接建 monitor。配额是你 org 自己的 `daily_quota`，自限。

### 1.5.1 列出现有 monitor

```bash
curl -H "Authorization: Bearer $AIA_API_KEY" "$AIA_API_BASE/v1/monitors?limit=100"
```

返回 `{data: [{id, name, brand, keywordsCount, questionsCount, ...}]}`。先看缺不缺。

### 1.5.2 （可选）让 LLM 推荐核心词 + 监控问题

不想自己想问题？用这两个 LLM 辅助 endpoint：

```bash
# 从品牌名推荐 3-5 个核心词
curl -X POST "$AIA_API_BASE/v1/brands/recommend-keywords" \
  -H "Authorization: Bearer $AIA_API_KEY" -H "Content-Type: application/json" \
  -d '{"brand": "曹操充电"}'

# 从品牌 + 核心词推荐 5-10 条监控问题
curl -X POST "$AIA_API_BASE/v1/keywords/recommend-questions" \
  -H "Authorization: Bearer $AIA_API_KEY" -H "Content-Type: application/json" \
  -d '{"brand": "曹操充电", "keyword": "充电桩"}'
```

返回 `{items: [...]}`。**CC 应该 review 一遍再用** —— 推荐的问题可能太泛，挑/改成真实用户会问的。

### 1.5.3 创建 monitor

```bash
curl -X POST "$AIA_API_BASE/v1/monitors" \
  -H "Authorization: Bearer $AIA_API_KEY" -H "Content-Type: application/json" \
  -d '{
    "brand": "曹操充电",
    "brandId": "<brand-uuid，不传则用 org default brand>",
    "competitors": ["特来电", "星星充电"],
    "keywords": [
      {
        "keyword": "充电桩厂家",
        "questions": [
          "充电桩厂家哪家好？",
          "国内充电桩品牌排名怎么样？"
        ]
      },
      {
        "keyword": "新能源充电",
        "questions": ["小区装充电桩找哪家？"]
      }
    ],
    "modelIds": ["doubao-pro", "deepseek-v3", "qwen-max"],
    "config": {"period": "weekly", "questionsPerDay": 10},
    "screenshotMode": "mention",
    "enableDeepThink": true,
    "region": "random"
  }'
```

**字段要点**：

| 字段 | 说明 |
|---|---|
| `brand` | 监控的品牌词（字符串，AI 答案里匹配它算"提及"） |
| `brandId` | 关联的 Brand 实体 uuid；不传 = org 的 default brand |
| `keywords[].keyword` | 核心词分组 |
| `keywords[].questions` | **真实用户会问的完整问句**（这些就是 monitor 跑批时丢给 AI 的 query）。至少一条非空 |
| `modelIds` | 跑哪些 AI。合法值：`doubao-pro` / `hunyuan-pro` / `deepseek-v3` / `ernie-4` / `kimi-k1.5` / `qwen-max` / `weibo-zhisou` / `quark` |
| `competitors` | 竞品词（可空，最多 20） |
| `config.period` | `daily` / `weekly` / `monthly` |
| `config.questionsPerDay` | 每问题每批跑几次（默认 1） |
| `config.fullReadProbe` | `true` 开启「全文精读探针」（**仅豆包生效**），见下方说明 |

返回 `{id, name, brand, status, createdAt}` —— 这个 `id` 可以填进文章的 `expected_monitor_ids`（已弱化、可选，命中检测不再用它），主要还是用来在 `content-gaps` 里定位选题。

**`config.fullReadProbe`（全文精读探针，豆包专属）**：开启后，每个 question 在豆包平台会额外跑一条探针 query，让豆包自报「这次回答里哪些参考文章是读了全文、哪些只看了搜索摘要片段」。抽取结果落在 hit-analysis 的 `read_mode` 字段（见 §5.5），用来判断你的文章是被豆包**当重点全文精读**，还是仅被**片段聚合**。代价：豆包配额 ≈ 翻倍（每问题多一条 query），所以建议只在重点 brand / 重点 monitor 开。其他平台（DeepSeek/通义/元宝）传了也不生效。

### 1.5.4 给现有 monitor 加问题（PATCH）

monitor 已存在、只想加几条 query：

```bash
curl -X PATCH "$AIA_API_BASE/v1/monitors/{monitor_id}" \
  -H "Authorization: Bearer $AIA_API_KEY" -H "Content-Type: application/json" \
  -d '{
    "keywords": [
      {"keyword": "充电桩厂家", "questions": ["充电桩厂家哪家好？", "充电桩多少钱一台？"]}
    ]
  }'
```

注意 PATCH 的 `keywords` 是**整体替换**该 monitor 的核心词表（不是增量 append）—— 先 GET 详情拿现有 keywords，合并后再 PATCH。

### 1.5.5 （可选）试跑验证

```bash
curl -X POST "$AIA_API_BASE/v1/monitors/dry-run" \
  -H "Authorization: Bearer $AIA_API_KEY" -H "Content-Type: application/json" \
  -d '{"brand":"曹操充电","keywords":[{"keyword":"充电桩","questions":["充电桩厂家哪家好？"]}],"modelIds":["deepseek-v3"]}'
```

不入库，立即返回示例答案 —— 用来确认 query 设计合理（AI 答得上来、能匹配到品牌）再正式建 monitor。

---

## 2. Workflow Recipe — CC 写新文章前

### 2.1 拿 content-gaps（决定写什么）

```bash
curl -H "Authorization: Bearer $AIA_API_KEY" \
  "$AIA_API_BASE/v1/brands/{brand_id}/content-gaps?limit=10&lookback_days=7"
```

返回 top N 个"值得写"的 query + 竞争源：

```json
{
  "brand_id": "...",
  "lookback_days": 7,
  "gaps": [
    {
      "query_id": "uuid",
      "query_text": "国内 GEO 入门",
      "monitor_id": "uuid",
      "monitor_name": "GEO 主监控",
      "current_coverage": 0,              // 月栖现有 article 在此 query 上 hit 数
      "ai_run_count": 12,                  // 最近 7 天此 query 被 AI 跑过几次
      "top_competing_domains": [
        {"domain": "zhihu.com", "cite_count": 8},
        {"domain": "huxiu.com", "cite_count": 5}
      ],
      "gap_score": 0.85                    // 越高越值得写（0-1）
    }
    // ...
  ]
}
```

**CC 应该**：
- 优先写 `gap_score` 高的 query
- 看 `top_competing_domains` 决定要"打败谁" — 如果竞争源是 zhihu，你也写得像 zhihu 长文；如果是企业 PR 稿，你写得更技术化
- 同时覆盖 3-5 个 query 的文章 > 单 query 文章

### 2.2 用 content-gaps 的 query 当 target_questions

提交文章时真正的驱动是 **target_questions** —— 每条会注册成品牌级追踪问题、周期性问 AI。content-gaps 返回里的 `query_text` 就是最好的素材（它们本就是真实用户问句、且有缺口）。直接把高 `gap_score` 的 `query_text` 拿去当 target_questions 即可。

> `expected_monitor_ids` 已弱化为可选，命中检测不再用它（见 §3 字段表）。仍想看品牌整体覆盖情况，可拉 coverage-map：

```bash
# 列出 brand 下所有 monitor × query × covering article 矩阵
curl -H "Authorization: Bearer $AIA_API_KEY" \
  "$AIA_API_BASE/v1/brands/{brand_id}/coverage-map"
```

看哪些 query 一个 article 都没覆盖到 —— 这些就是优先要写、并写进 target_questions 的问题。

---

## 3. Workflow Recipe — 提交文章

写完文章发到目标平台（微信/知乎等）拿到 URL 后：

```bash
curl -X POST "$AIA_API_BASE/v1/articles" \
  -H "Authorization: Bearer $AIA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://mp.weixin.qq.com/s/abc123",
    "brand_id": "uuid",
    "title": "GEO 入门：让 AI 引用你品牌的 5 个步骤",
    "content_markdown": "# 标题\n\n## 第一节\n\n...",
    "target_questions": [
      "国内 GEO 入门",
      "GEO 跟 SEO 有什么区别",
      "AI 搜索怎么投放"
    ],
    "source_platform": "wechat",
    "expected_monitor_ids": ["uuid1", "uuid2"],
    "promoted_keywords": ["GEO", "AI 营销", "AI 引用"],
    "publish_date": "2026-05-20",
    "status": "published"
  }'
```

### 字段含义

| 字段 | 必填？ | 说明 |
|---|---|---|
| `url` | ✅ | 文章已发布的实际 URL |
| `brand_id` | ✅ | 关联的客户品牌 |
| `title` | 推荐 | 不传系统自动 fetch og:title（可能取不准） |
| `content_markdown` | **强烈推荐** | 文章正文 Markdown。**关键**：后续分析"AI 用了你哪段"靠它，不传以后没法重抓 |
| `target_questions` | **必填**（闭环靠它） | 这篇文章瞄准的**自然语言完整问题**列表（非短关键词）。每条会注册成品牌级**追踪问题**（按归一化文本在品牌内去重），去重后**周期性原样拿去问 AI**（豆包/DeepSeek 等）；多篇文章瞄准同一问题共享一次跑批。**措辞即问句**——AI 会逐字拿它去检索，措辞要像真实用户问法。每条 8-30 字最佳 |
| `source_platform` | 推荐 | 后端是自由字符串（无枚举约束），常用值：`wechat` / `zhihu` / `xiaohongshu` / `weibo` / `toutiao`（头条号）/ `sohu`（搜狐号）/ `baijiahao`（百家号）/ `own_blog` / `media_pr` / `other`。AI 对不同平台权威性差异巨大，按实际发布平台填即可，没对应值就用最接近的或新造一个稳定 slug（保持同平台一致） |
| `expected_monitor_ids` | 可选（已弱化，命中不再依赖它） | 期望覆盖的 monitor id 列表。**已废弃用途**：旧模型靠它 + embedding 语义相似度猜文章"应该"命中哪些 query，这套机制已移除。字段仍被接受但**命中检测忽略它**，可不传 |
| `promoted_keywords` | 可选 | SEO 投放词（短词），跟 target_questions 是不同维度 — 后者是 GEO 语义匹配 |
| `publish_date` | 可选 | 实际发布日期 |
| `monitor_id` | 可选 | 主关联 monitor（向后兼容字段，与命中检测无关） |

### 返回值（重要 — instant_check_id）

```json
{
  "id": "article-uuid",
  "url": "...",
  "instant_check_id": "check-uuid",   // ← 接下来 poll 这个
  "target_questions": ["..."],
  "source_platform": "wechat",
  "expected_monitor_ids": ["uuid1", "uuid2"],
  // ... 其他字段 ...
}
```

`instant_check_id` 非空说明 aia 已经把 3 LLM × 3 question = 最多 9 个任务入队了。

---

## 4. Workflow Recipe — 拿即时验证 (instant_check polling)

提交后 **不要立刻** poll — 任务排队需要 1-3 分钟，全部跑完需要 5-10 分钟（每个 Playwright 调用 30s-2min）。

```bash
# 等 1 分钟后开始 poll，每 5 秒一次
sleep 60
while true; do
  resp=$(curl -s -H "Authorization: Bearer $AIA_API_KEY" \
    "$AIA_API_BASE/v1/articles/{article_id}/instant-check")
  status=$(echo "$resp" | jq -r '.status')
  echo "status=$status"
  [ "$status" = "ready" ] && break
  [ "$status" = "failed" ] && break
  sleep 5
done
echo "$resp" | jq
```

### 返回结构

```json
{
  "check_id": "uuid",
  "status": "ready",              // pending / running / ready / failed
  "submitted_at": "2026-05-20T08:00:00Z",
  "completed_at": "2026-05-20T08:07:33Z",
  "results": [
    {
      "question": "国内 GEO 入门",
      "llm": "doubao",
      "your_article_cited": false,
      "actual_citation_domains": ["zhihu.com", "huxiu.com"],
      "suggestion": null,
      "completed_at": "..."
    },
    {
      "question": "国内 GEO 入门",
      "llm": "deepseek",
      "your_article_cited": true,
      "actual_citation_domains": ["mp.weixin.qq.com"],
      "suggestion": null,
      "completed_at": "..."
    }
    // ... 共最多 9 条 (3 LLM × 3 question)
  ]
}
```

**CC 怎么用这些数据**：

```python
# 解析 results 决定下一步
all_cited = all(r["your_article_cited"] for r in results)
none_cited = not any(r["your_article_cited"] for r in results)

if all_cited:
    print("✓ 文章已经在所有 LLM 上命中")
elif none_cited:
    print("⚠️  零命中 — 看 actual_citation_domains，决定是否改稿")
    # 收集竞争 domain：
    competing = set()
    for r in results:
        competing.update(r["actual_citation_domains"])
    print(f"AI 当前用的源: {competing}")
    # 决策：要么改文章质量赶超它们，要么换平台发（zhihu 比公众号有效就改投 zhihu）
else:
    print("部分命中 — 接受现状或针对未命中的 LLM 微调")
```

### 失败处理

- `status='failed'`：检查 `error` 字段。罕见，一般是 worker 全部账号挂了。等 1 小时重试。
- 长时间 `status='running'`（> 30 min）：可能 worker 有平台风控。可以提交新 article 重新触发，老的 check 自动作废。

---

## 5. Workflow Recipe — 长期命中分析 (hit-analysis)

instant-check 是"提交瞬间"的 AI 现状。真正的命中追踪要看你 target_questions 注册的追踪问题周期性跑批后累积的数据。**至少等 1 周** 才有意义。

```bash
curl -H "Authorization: Bearer $AIA_API_KEY" \
  "$AIA_API_BASE/v1/articles/{article_id}/hit-analysis"
```

返回：

```json
{
  "citation_rate": 0.667,         // ★ 引用率 = cited_runs / tracked_runs（主指标）
  "tracked_runs": 6,              // ★ 这篇订阅的追踪问题被跑批（完成 query）的总次数
  "cited_runs": 4,                // ★ 其中引用到本文（命中事件）的次数
  "expected_count": 6,            // 向后兼容别名 = tracked_runs
  "hit_count": 4,                 // 向后兼容别名 = cited_runs
  "hit_rate": 0.667,              // 向后兼容别名 = citation_rate
  "hits": [
    {
      "query_id": "...",
      "query_text": "国内 GEO 入门",
      "monitor_id": "...",
      "snippet_quoted": "...",     // AI 答案里引用你文章的那段
      "first_seen_at": "2026-05-23T..."   // 首次命中时间
    }
    // ...
  ],
  "expected_misses": [
    {
      "query_id": "...",
      "query_text": "GEO vs SEO 区别",
      "monitor_id": "...",
      "competing_sources": [        // 客户视角 (X-Aia-Role: client) 时为空
        {"domain": "zhihu.com", "url": "...", "cite_count": 12},
        {"domain": "8848.com", "url": "...", "cite_count": 8}
      ]
    }
  ]
}
```

**命中判定（确定性，无阈值/无 embedding）**：文章通过 target_questions **订阅**了某些追踪问题 → 这些问题被周期性跑批 → 某次跑批的引用源里出现你的文章 URL = `hit`，否则该次 = `expected_miss`。不再有语义相似度匹配、不再拿 monitor 的 query_text 做模糊比对。

**怎么读 `citation_rate`（引用率）**：
- `citation_rate` 高 = 文章在它瞄准的问题上**经常被 AI 引用**，GEO 效果好。
- `citation_rate` 低 = 内容深度/权威性不够，被检索到但没被采信 —— 需提升内容深度、补全维度、增强结构化。
- 结合 `read_mode.full_count`（全文精读，见 §5.5）一起看：引用率高 **且** 有 full_count = 最强 GEO 信号（被 AI 当重点深读）。

**CC 怎么用**：每月底跑一次，识别 `citation_rate` 低、`expected_misses` 多的文章，决定要不要写续篇 / 改稿 / 切换发布平台。

---

## 5.5 拉取分析原材料 — AI 的"处理全过程"

命中分析告诉你"中没中"，但要知道**为什么没中、文章该怎么改**，得看 AI 实际怎么处理这个问题。每条 monitor query 都存了 AI 的完整处理过程，CC 用 api-key 直接拉（org-scoped）：

```bash
# 1. 列出某 monitor 的 query（拿 query_id）
curl -H "Authorization: Bearer $AIA_API_KEY" \
  "$AIA_API_BASE/v1/queries?monitor_id={monitor_id}&limit=50"

# 2. 拉单条 query 的完整处理过程
curl -H "Authorization: Bearer $AIA_API_KEY" \
  "$AIA_API_BASE/v1/queries/{query_id}"
```

返回里有三块**分析金矿**：

```json
{
  "query": "充电桩厂家哪家好？",
  "answer": "...",                    // AI 给用户的最终答案
  "thinking": "...",                  // 思考链（仅 monitor 配了 enableDeepThink 时有；quick 模式为空）
  "search_queries": [                 // ★ AI 实际用的搜索 rewrite 词 ★
    "2025国内充电桩品牌排名 厂家推荐",
    "充电桩一线品牌 质量口碑 商用家用",
    "充电桩十大品牌 2025最新"
  ],
  "is_web_search": true,
  "citations": [                      // AI 引用的源（你的竞争对手）
    {"domain": "chejiahao.autohome.com.cn", "title": "...", "url": "...", "snippet": "..."},
    {"domain": "m.toutiao.com", "title": "...", ...}
  ]
}
```

**`search_queries` 是 GEO 最上游的信号** —— 它告诉你用户问「充电桩厂家哪家好？」时，AI 真正拿去检索的是「2025国内充电桩品牌排名」「厂家推荐」「十大品牌」「商用家用」这些词。

### CC 写/改文章的决策链

```
拉 query 详情：
├─ search_queries → 文章标题/正文/H2 必须命中这些词（"2025"、"品牌排名"、"厂家推荐"、"商用家用"）
│  这是"怎么被检索到"的答案 —— 比 promoted_keywords 瞎猜精准
├─ citations → 看 AI 引用了谁（autohome / toutiao）→ 你文章的深度/结构要赶超它们
├─ thinking（如果有）→ 看 AI 怎么理解这个问题的"意图"（要 X 维度信息）→ 文章覆盖这些维度
└─ answer → 看 AI 现在怎么回答 → 你文章要提供 answer 里缺的、更具体的信息
```

**典型 workflow**：
1. hit-analysis 发现某 query 是 `expected_miss`
2. 拉那条 query 的 `/v1/queries/{id}` 详情
3. 看 `search_queries` → 发现 AI 搜的是"2025最新品牌排名"，而你文章是"充电桩选购指南"（词对不上）
4. 改文章：标题/小标题加入"2025"、"品牌排名"、"厂家推荐" → 重新发布 → 下次跑批看是否命中

> 注意：`thinking` 只在 monitor 配了 `enableDeepThink: true` 时才有值；普通 quick 模式查询为空。`search_queries` 目前豆包/DeepSeek 有，通义/元宝可能为空。

### read-mode：你的文章被豆包「全文精读」还是「片段聚合」？

如果 monitor 开了 `config.fullReadProbe`（§1.5.3，豆包专属），hit-analysis 会多出一个 `read_mode` 字段：

```json
// GET /v1/articles/{id}/hit-analysis
{
  "citation_rate": 0.667, "tracked_runs": 6, "cited_runs": 4,
  "expected_count": 6, "hit_count": 4, "hit_rate": 0.667,   // 向后兼容别名
  "hits": [...], "expected_misses": [...],
  "read_mode": { "full_count": 2, "snippet_count": 1 }   // ★ 仅 fullReadProbe 开启时非零
}
```

- **`full_count > 0`** = 豆包在回答里把你的文章**当作重点、读了全文**。这是最强的 GEO 信号：不只是被检索命中，而是被深度采信。
- **全是 `snippet_count`（full_count=0）** = 你的文章只被当**搜索摘要片段**聚合，没进入深读。说明权威性/结构深度还不够，需要：提升内容深度、补全维度、增强结构化（清单/表格/分点），争取从「片段」升级为「全文精读」。
- **两者都为 0** = 该文章还没有探针数据（monitor 没开 `fullReadProbe`，或还没跑过批）。

> 解读优先级：`hit`（命中）> `full`（全文精读）> `snippet`（片段）。命中但只是 snippet 的文章，是下一轮优化的重点 —— 它已经被检索到了，差的是「被深读」这一步。

---

## 6. 常见陷阱

### ❌ target_questions 写得太短或太关键词化

```json
"target_questions": ["AI 营销", "GEO"]    // ❌ 这是 SEO 关键词，不是问题
```

正确：

```json
"target_questions": [
  "国内品牌如何在 AI 搜索中被引用？",
  "GEO 跟 SEO 有什么区别？",
  "AI 营销监控应该追踪哪些指标？"
]
```

短词应放 `promoted_keywords` 字段。target_questions 必须是**自然语言完整问句** —— 因为它会被注册成追踪问题、**原样拿去问 AI**。措辞不对（关键词化、不像真实问法）会触发 AI 错误的搜索改写，检索到的根本不是你这篇能回答的语境，自然不会引用你。

### ❌ 引用率(citation_rate)低，却以为是配置问题

引用率低**不是**因为 target_questions 或 monitor 配少了 —— 命中是确定性的：你订阅的问题被跑批、引用源里有你的 URL 才算命中。引用率低 = **内容权威性/深度不够**（被检索到但没被采信，或压根没进引用集）。对策：提升内容深度、补全 AI 思考链关注的维度、增强结构化（清单/表格/分点），对照 §5.5 的 `search_queries` 和竞争 `citations` 改稿，而不是去加 monitor 或 expected_monitor_ids。

### ❌ content_markdown 不传

后续分析"AI 用了你哪段"靠这个字段。不传以后再想分析就只能 re-fetch URL，平台可能反爬 / 改版 / 下架。**强烈建议提交时就贴上**。

### ❌ 提交后立即 poll instant-check

刚提交时 task 还在 Redis queue 排队，至少**等 1 分钟**再开始 poll。第一次 poll 大概率拿到 `status='pending'` 或前 1-2 个 result。

### ❌ source_platform 留默认 "other"

填错了 AI 检索权重模型就用不上。即便不确定 "公众号还是知乎"，至少给个具体平台。

---

## 7. 完整 Python 调用范例

```python
import os
import time
import httpx

AIA_API_BASE = os.environ["AIA_API_BASE"]
AIA_API_KEY = os.environ["AIA_API_KEY"]
HEADERS = {"Authorization": f"Bearer {AIA_API_KEY}"}


async def get_content_gaps(brand_id: str, limit: int = 10) -> list[dict]:
    async with httpx.AsyncClient(timeout=15) as cli:
        r = await cli.get(
            f"{AIA_API_BASE}/v1/brands/{brand_id}/content-gaps",
            headers=HEADERS,
            params={"limit": limit, "lookback_days": 7},
        )
        r.raise_for_status()
        return r.json()["gaps"]


async def submit_article(
    *, brand_id: str, url: str, title: str, content_markdown: str,
    target_questions: list[str], source_platform: str,
    expected_monitor_ids: list[str] = None,  # 已弱化、可选，命中检测不用它
    promoted_keywords: list[str] = None,
) -> dict:
    async with httpx.AsyncClient(timeout=30) as cli:
        r = await cli.post(
            f"{AIA_API_BASE}/v1/articles",
            headers={**HEADERS, "Content-Type": "application/json"},
            json={
                "url": url, "brand_id": brand_id, "title": title,
                "content_markdown": content_markdown,
                "target_questions": target_questions,  # ← 闭环驱动：注册为追踪问题
                "source_platform": source_platform,
                "expected_monitor_ids": expected_monitor_ids or [],  # 可省略
                "promoted_keywords": promoted_keywords or [],
                "status": "published",
            },
        )
        r.raise_for_status()
        return r.json()


async def wait_instant_check(article_id: str, max_wait_sec: int = 600) -> dict:
    """Poll instant_check 直到 ready/failed 或超时."""
    async with httpx.AsyncClient(timeout=10) as cli:
        start = time.time()
        while time.time() - start < max_wait_sec:
            r = await cli.get(
                f"{AIA_API_BASE}/v1/articles/{article_id}/instant-check",
                headers=HEADERS,
            )
            if r.status_code == 404:
                # 文章没 target_questions → 没 instant_check
                return {"status": "no_check"}
            r.raise_for_status()
            data = r.json()
            if data["status"] in ("ready", "failed"):
                return data
            await asyncio.sleep(5)
        return {"status": "timeout"}


# 完整闭环示例
async def main():
    brand_id = "xxx"

    # 1. 拿 brief
    gaps = await get_content_gaps(brand_id, limit=5)
    print(f"Top gap: {gaps[0]['query_text']} (score={gaps[0]['gap_score']})")
    target_questions = [g["query_text"] for g in gaps[:3]]  # ← 闭环驱动
    # expected_monitor_ids 已弱化、可选（命中检测不用它），下面顺手收集仅作元信息
    expected_monitor_ids = list({g["monitor_id"] for g in gaps[:5]})

    # 2. 写文章（手动 / 自己用 LLM）→ 发到平台 → 拿到 URL
    article_url = "https://mp.weixin.qq.com/s/abc"
    content_md = "# Title\n\n..."

    # 3. 提交
    art = await submit_article(
        brand_id=brand_id, url=article_url,
        title="GEO 入门指南",
        content_markdown=content_md,
        target_questions=target_questions,
        source_platform="wechat",
        expected_monitor_ids=expected_monitor_ids,
        promoted_keywords=["GEO", "AI 营销"],
    )
    print(f"Article id: {art['id']}, waiting for instant_check...")

    # 4. Poll
    check = await wait_instant_check(art["id"])
    if check["status"] == "ready":
        cited_count = sum(1 for r in check["results"] if r["your_article_cited"])
        print(f"✓ Instant check: {cited_count}/{len(check['results'])} cited")
        for r in check["results"]:
            if not r["your_article_cited"]:
                print(f"  ✗ {r['llm']} on '{r['question']}' used: {r['actual_citation_domains']}")
```

---

## 8. TypeScript 调用范例

```typescript
const AIA_API_BASE = process.env.AIA_API_BASE!;
const AIA_API_KEY = process.env.AIA_API_KEY!;
const HEADERS = { Authorization: `Bearer ${AIA_API_KEY}` };

interface Gap {
  query_id: string;
  query_text: string;
  monitor_id: string;
  current_coverage: number;
  top_competing_domains: { domain: string; cite_count: number }[];
  gap_score: number;
}

async function getContentGaps(brandId: string, limit = 10): Promise<Gap[]> {
  const r = await fetch(
    `${AIA_API_BASE}/v1/brands/${brandId}/content-gaps?limit=${limit}`,
    { headers: HEADERS },
  );
  if (!r.ok) throw new Error(`gaps fetch failed: ${r.status}`);
  const data = await r.json();
  return data.gaps;
}

async function submitArticle(payload: {
  brand_id: string;
  url: string;
  title: string;
  content_markdown: string;
  target_questions: string[];            // 闭环驱动：注册为品牌级追踪问题
  source_platform: string;
  expected_monitor_ids?: string[];        // 已弱化、可选，命中检测不用它
}): Promise<{ id: string; instant_check_id: string | null }> {
  const r = await fetch(`${AIA_API_BASE}/v1/articles`, {
    method: "POST",
    headers: { ...HEADERS, "Content-Type": "application/json" },
    body: JSON.stringify(payload),
  });
  if (!r.ok) throw new Error(`submit failed: ${r.status} ${await r.text()}`);
  return r.json();
}

async function pollInstantCheck(articleId: string, maxWaitSec = 600) {
  const start = Date.now();
  while (Date.now() - start < maxWaitSec * 1000) {
    const r = await fetch(
      `${AIA_API_BASE}/v1/articles/${articleId}/instant-check`,
      { headers: HEADERS },
    );
    if (r.status === 404) return { status: "no_check" };
    if (!r.ok) throw new Error(`poll failed: ${r.status}`);
    const data = await r.json();
    if (data.status === "ready" || data.status === "failed") return data;
    await new Promise((res) => setTimeout(res, 5000));
  }
  return { status: "timeout" };
}
```

---

## 9. 服务端配置 checklist（aia 部署方）

如果你（aia 运维）部署 aia 时遇到 instant_check 不工作，检查：

- [ ] `ZHIPU_API_KEY` 环境变量已设（embedding 用）— 在 `deploy/.env.secrets`
- [ ] doubao / deepseek / qwen 三个 platform_accounts 在 admin console 都有可用账号
- [ ] aia-worker systemd service 在跑（`systemctl is-active aia-worker`）
- [ ] Redis 连得通（`redis-cli ping`）
- [ ] 数据库已应用 migration `aaa255f373dc` 和 `2ec7317dd4fe`
- [ ] FRONTEND_BASE_URL 已设（邀请链接、引用 URL 生成用）

---

## 10. 给 CC 的最终 Decision Tree

```
开始写一篇 GEO 文章前：
├─ 调 content-gaps 拿 brief
│  ├─ gap_score > 0.7 的有几个？
│  │  ├─ 1 个 → 写专题深度文（专攻一个 query）
│  │  └─ 多个 → 写综合文覆盖 3-5 个 query
│  └─ top_competing_domains 主要是？
│     ├─ 知乎/虎嗅长文 → 写技术深度长文（3000+ 字）
│     ├─ 企业 PR 稿 → 写"对照分析"角度
│     └─ 个人博客 → 写权威 case study
│
写完发到平台 → 拿到 URL：
├─ 调 POST /v1/articles
│  ├─ content_markdown 必传
│  ├─ target_questions 至少 3 条，每条 ≥ 10 字自然语言问句（会原样问 AI，措辞要像真实问法）
│  ├─ source_platform 选具体值，别用 'other'
│  └─ expected_monitor_ids 可不填（已弱化，命中检测不用它）
│
提交后：
├─ 等 1 分钟 → poll instant-check
│  ├─ status='ready' & 大多命中 → 收工
│  ├─ status='ready' & 大多没中 → 看 competing_domains 决定重写/换平台
│  └─ status='failed' → 1 小时后重试
│
1 周后：
└─ 调 hit-analysis 看长期数据 → 月度复盘
```

---

**版本**：v1.0（2026-05-20）
**适用 aia 版本**：Ralph#50+（含 article closed loop 闭环）
**反馈**：在 aia 仓库提 issue

---

## 附录：如何安装这个 skill

> aia 是私有仓库，本 skill 通过**公开 Gist** 分发（不暴露 aia 源码）。

### 选项 A：用户级（这台机器所有 CC 项目都能用）

```bash
mkdir -p ~/.claude/skills/aia-article-publish
curl -o ~/.claude/skills/aia-article-publish/SKILL.md \
  "https://gist.githubusercontent.com/zizhanovo/2b5b79ed6d9282b3c3d401df96390419/raw/aia-article-publish.SKILL.md"
```

### 选项 B：项目级（仅当前项目）

```bash
mkdir -p .claude/skills/aia-article-publish
curl -o .claude/skills/aia-article-publish/SKILL.md \
  "https://gist.githubusercontent.com/zizhanovo/2b5b79ed6d9282b3c3d401df96390419/raw/aia-article-publish.SKILL.md"
```

安装后重启 Claude Code，CC 会自动加载此 skill。需要使用时直接说 "用 aia-article-publish skill 发一篇关于 X 的文章"。

Gist 地址（可在浏览器查看 / 编辑）：https://gist.github.com/zizhanovo/2b5b79ed6d9282b3c3d401df96390419
