feat: Introduce AI Brand Monitoring System V5 technical design document
- Added comprehensive technical design document for AI Brand Monitoring System V5, outlining system architecture, data models, sampling strategies, and monitoring protocols. - Key changes include a shift to a sampling-based trend monitoring approach, updated data collection and storage strategies, and new metrics for performance evaluation. - Implemented migration scripts to support the flattening of brand questions and versioning of question texts, ensuring historical data integrity and version control.
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,960 @@
|
||||
# AI 品牌曝光监测系统技术方案 V5
|
||||
|
||||
## 1. 文档信息
|
||||
|
||||
| 项目 | 内容 |
|
||||
| --- | --- |
|
||||
| 文档名称 | AI 品牌曝光监测系统技术方案 V5(采样型趋势监测 + 异步解耦) |
|
||||
| 文档版本 | V5.1 |
|
||||
| 文档状态 | 待评审 |
|
||||
| 创建日期 | 2026-04-08 |
|
||||
| 基线文档 | `docs/ai-brand-monitoring-tech-design-v4.md` |
|
||||
| 适用范围 | 数据追踪模块全部页面 |
|
||||
| 关联文档 | `docs/geo-platform-prd-v1.md`(PRD 8.4 数据追踪) |
|
||||
| 关联文档 | `docs/question-driven-monitoring-design-v1.md`(问题驱动模型) |
|
||||
| 核心约束 | 单租户仅 1 个监测插件实例,插件所在设备不保证全天在线 |
|
||||
|
||||
## 2. V5 核心结论
|
||||
|
||||
V5 不再把该能力定义为“全量、精确、准实时监控”,而是明确收敛为:
|
||||
|
||||
1. **采样型趋势监测系统**,不是审计系统。
|
||||
2. **单实例插件 + 非全天在线设备**前提下,只承诺趋势参考,不承诺全量覆盖。
|
||||
3. **页面百分比全部按实际采样成功样本计算**,不再暗示“全量真实占比”。
|
||||
4. **页面必须展示采样覆盖信息**,包括计划采样数、实际采样数、覆盖率、最近更新时间。
|
||||
5. **引入 RabbitMQ 和独立 Monitoring PostgreSQL 隔离写链路**,但聚合仍以日批为主,不承诺准实时展示。
|
||||
|
||||
### 2.1 相比 V4 的根本变化
|
||||
|
||||
| 维度 | V4 | V5 |
|
||||
| --- | --- | --- |
|
||||
| 产品定位 | 全量品牌曝光监测 | 采样型趋势参考 |
|
||||
| 采集目标 | 尽量覆盖全部问题 × 全平台 | 每日仅覆盖活跃监测题池 |
|
||||
| 插件节点假设 | 大量用户端节点可互补 | 单租户单主插件实例 |
|
||||
| 数据时效 | 准实时 + T+1 | T+1 为主,允许延迟 |
|
||||
| 数据精度承诺 | 接近真实全量 | 趋势可参考,精度受覆盖率影响 |
|
||||
| 任务处理 | PG + Redis 锁 + RabbitMQ | Monitoring PG 任务表 + PG 租约状态机 + RabbitMQ 结果异步化 |
|
||||
| 存储边界 | 单业务库承载配置与监测结果 | 主业务 PG 存配置,Monitoring PG 存监测数据 |
|
||||
| 页面表达 | 指标卡直接展示百分比 | 指标卡 + 采样覆盖信息 + 置信度提示 |
|
||||
|
||||
### 2.2 非目标
|
||||
|
||||
V5 明确不做以下承诺:
|
||||
|
||||
1. 不承诺所有问题每天都有结果。
|
||||
2. 不承诺所有 AI 平台每天都有样本。
|
||||
3. 不承诺分钟级实时更新。
|
||||
4. 不承诺百分比可用于财务、法务、投放结算或精确 KPI 审计。
|
||||
|
||||
---
|
||||
|
||||
## 3. 产品口径与页面表达
|
||||
|
||||
### 3.1 页面定位
|
||||
|
||||
数据追踪页展示的是:
|
||||
|
||||
- 在“当前监测题池”上的 AI 曝光趋势
|
||||
- 在“实际采样成功样本”上的品牌表现
|
||||
- 在“可归因引用样本”上的文章引用趋势
|
||||
|
||||
不是:
|
||||
|
||||
- 品牌在全网 AI 场景中的绝对真实份额
|
||||
- 所有问题、所有平台、所有时间点的完整监控
|
||||
|
||||
### 3.2 页面必须新增的展示项
|
||||
|
||||
为避免误导,数据追踪页和 Dashboard composite 接口必须返回并展示:
|
||||
|
||||
1. `planned_sample_count`:计划采样数
|
||||
2. `actual_sample_count`:实际采样成功数
|
||||
3. `coverage_rate`:覆盖率 = `actual / planned`
|
||||
4. `last_sampled_at`:最近成功采样时间
|
||||
5. `confidence_level`:`high / medium / low`
|
||||
|
||||
推荐前端呈现方式:
|
||||
|
||||
- 顶部右侧信息条:`采样模式`
|
||||
- 趋势图上方副文案:`已采样 18/30,覆盖率 60%,更新于 04-08 09:20`
|
||||
- 覆盖率过低时展示 `样本不足,仅供参考`
|
||||
|
||||
### 3.3 低覆盖场景的展示规则
|
||||
|
||||
| 覆盖率 | 置信度 | 页面表现 |
|
||||
| --- | --- | --- |
|
||||
| >= 70% | high | 正常展示百分比和趋势 |
|
||||
| 40% - 69% | medium | 展示百分比,并提示“样本偏少” |
|
||||
| < 40% | low | 保留原始计数,百分比弱化展示或标记“仅供参考” |
|
||||
|
||||
当 `actual_sample_count < 5` 时:
|
||||
|
||||
1. 不展示“精确到整数”的百分比变化解释。
|
||||
2. 趋势线可以显示,但需打低置信度标记。
|
||||
3. 引用排行允许为空,不用补零。
|
||||
|
||||
---
|
||||
|
||||
## 4. 采样配额与容量模型
|
||||
|
||||
### 4.1 套餐定义
|
||||
|
||||
V5 中配额控制的不是“题库总量”,而是“每日活跃监测问题数”。
|
||||
|
||||
| 配额项 | Free | Pro | Enterprise |
|
||||
| --- | --- | --- | --- |
|
||||
| 最大品牌数 | 1 | 3 | 自定义 |
|
||||
| 题库总量 | 不限于监测能力,只做配置资产 | 不限于监测能力,只做配置资产 | 自定义 |
|
||||
| **每日活跃监测问题数** | **3-5** | **5-10** | 自定义 |
|
||||
| 采集频率 | 每天 | 每天 | 每天 / 自定义 |
|
||||
| 平台范围 | 当前已接入且已登录的平台 | 当前已接入且已登录的平台 | 同左 |
|
||||
| 数据承诺 | 趋势参考 | 趋势参考 | 趋势参考 / 专属增强 |
|
||||
|
||||
### 4.2 活跃监测题池
|
||||
|
||||
系统将问题分成两层:
|
||||
|
||||
1. **问题题库**:品牌下维护的全部问题,仅做配置和运营资产。
|
||||
2. **活跃监测题池**:每日实际参与采样的问题集合,数量受套餐限制。
|
||||
|
||||
推荐实现:
|
||||
|
||||
- `brand_questions` 保留全部题库
|
||||
- 新增 `monitor_priority`、`monitor_enabled`
|
||||
- 调度器每日只选 Top N 问题进入任务生成
|
||||
|
||||
### 4.3 单实例前提下的目标任务量
|
||||
|
||||
按首批 6 个平台测算:
|
||||
|
||||
| 套餐 | 活跃问题数 | 平台数 | 目标计划任务/天 |
|
||||
| --- | --- | --- | --- |
|
||||
| Free | 3-5 | 6 | 18-30 |
|
||||
| Pro | 5-10 | 6 | 30-60 |
|
||||
|
||||
说明:
|
||||
|
||||
1. 这是 **计划采样量**,不是保证完成量。
|
||||
2. 实际完成量取决于:
|
||||
- 主监测插件是否在线
|
||||
- 插件机器当日在线时长
|
||||
- 对应 AI 平台是否已登录
|
||||
- 平台风控和采集成功率
|
||||
|
||||
### 4.4 核心调度原则
|
||||
|
||||
V5 的调度器不追求“补齐所有漏采任务”,而追求“稳定输出可比较样本”。
|
||||
|
||||
因此:
|
||||
|
||||
1. 每天只生成当天计划任务,不补历史大积压。
|
||||
2. 超过 48 小时未完成的任务自动过期,不回补到新一天的分母中。
|
||||
3. 读侧按 `planned_sample_count` 和 `actual_sample_count` 共同解释结果。
|
||||
|
||||
### 4.5 主插件在线约束
|
||||
|
||||
单租户只指定 1 个 `primary_monitoring_installation` 参与自动监测。
|
||||
|
||||
调度前先判断该插件是否活跃:
|
||||
|
||||
- `last_seen_at <= 24h`:生成当天任务
|
||||
- `last_seen_at > 24h`:不生成新任务,页面沿用最近一次成功数据并提示“近期无新采样”
|
||||
|
||||
这条规则的目的:
|
||||
|
||||
1. 防止设备长期离线时任务无限堆积
|
||||
2. 防止“未执行任务”污染覆盖率统计
|
||||
3. 让系统行为和单实例现实约束一致
|
||||
|
||||
---
|
||||
|
||||
## 5. 数据口径定义
|
||||
|
||||
### 5.1 顶部指标口径
|
||||
|
||||
所有百分比的分母统一为 **实际采样成功回答数**,不是计划任务数,也不是题库总数。
|
||||
|
||||
| 指标 | 定义 |
|
||||
| --- | --- |
|
||||
| 提及率 | `mentioned_count / actual_sample_count` |
|
||||
| 首位提及率 | `top1_mentioned_count / actual_sample_count` |
|
||||
| 首选推荐率 | `first_recommended_count / actual_sample_count` |
|
||||
| 正面提及率 | `positive_mentioned_count / actual_sample_count` |
|
||||
| 品牌可见率 | 与提及率同源,可作为 UI 别名展示 |
|
||||
| 文章引用率 | `cited_answer_count / actual_sample_count` |
|
||||
|
||||
### 5.2 趋势图口径
|
||||
|
||||
趋势图展示的是按天聚合后的样本表现,不是平台真实总体份额。
|
||||
|
||||
每个点必须同时具备:
|
||||
|
||||
- `metric_value`
|
||||
- `actual_sample_count`
|
||||
- `coverage_rate`
|
||||
|
||||
可选增强:
|
||||
|
||||
- 默认展示 7 天滚动均值
|
||||
- 用户切换后可看原始日值
|
||||
|
||||
### 5.3 高频问题口径
|
||||
|
||||
高频问题区不再解释为“全站高频触发问题”,而是:
|
||||
|
||||
> 在当前时间窗口内,被实际采样成功次数最多的问题
|
||||
|
||||
推荐展示字段:
|
||||
|
||||
- 问题文案
|
||||
- 成功采样次数
|
||||
- 提及率
|
||||
- 最近采样时间
|
||||
|
||||
### 5.4 引用排行口径
|
||||
|
||||
引用排行按模型平台展示:
|
||||
|
||||
- 有过可归因引用的回答数
|
||||
- 引用文章数
|
||||
- 引用率
|
||||
|
||||
若平台当日无样本或平台未登录:
|
||||
|
||||
- 展示为 `未采样`
|
||||
- 不强行按 `0` 并入分母
|
||||
|
||||
---
|
||||
|
||||
## 6. 数据模型修订
|
||||
|
||||
### 6.1 双 PostgreSQL 存储边界
|
||||
|
||||
V5.1 将数据按“业务配置”和“监测结果”拆到两个 PostgreSQL:
|
||||
|
||||
**主业务 PostgreSQL**
|
||||
|
||||
- `tenants`
|
||||
- `brands`
|
||||
- `brand_keywords`
|
||||
- `brand_questions`
|
||||
- `articles`
|
||||
- `publish_records`
|
||||
- `plugin_installations`
|
||||
- `tenant_monitoring_quotas`
|
||||
|
||||
**Monitoring PostgreSQL**
|
||||
|
||||
- `monitoring_collect_tasks`
|
||||
- `question_monitor_runs`
|
||||
- `question_monitor_parse_results`
|
||||
- `monitoring_citation_facts`
|
||||
- `monitoring_article_url_aliases`
|
||||
- `monitoring_*_daily`
|
||||
|
||||
设计原则:
|
||||
|
||||
1. 不做跨库物理外键,只保留逻辑 ID 引用。
|
||||
2. 监测链路的高频写入、重试、归档不影响主业务 PG。
|
||||
3. 读侧监测查询优先从 Monitoring PG 获取。
|
||||
|
||||
### 6.2 `question_version_id` 移除后的替代方案
|
||||
|
||||
V5 接受原系统不再维护 `question_version_id`,但仍然保留“输入快照不可变”原则。
|
||||
|
||||
替代方案:
|
||||
|
||||
1. 在任务表和运行表写入 `question_text_snapshot`
|
||||
2. 同时写入 `question_hash`
|
||||
3. 历史汇总按 `question_hash` 区分口径
|
||||
|
||||
这样即使 `question_id` 不变、文案发生修改,历史数据也不会被新文案污染。
|
||||
|
||||
### 6.3 `tenant_monitoring_quotas`
|
||||
|
||||
```sql
|
||||
CREATE TABLE tenant_monitoring_quotas (
|
||||
tenant_id BIGINT PRIMARY KEY REFERENCES tenants(id),
|
||||
max_brands INT NOT NULL DEFAULT 1,
|
||||
daily_active_question_limit INT NOT NULL DEFAULT 5,
|
||||
collect_frequency VARCHAR(20) NOT NULL DEFAULT 'daily',
|
||||
enabled_platforms JSONB NOT NULL DEFAULT '["deepseek","qwen","doubao"]',
|
||||
primary_installation_id BIGINT REFERENCES plugin_installations(id),
|
||||
task_daily_hard_cap INT NOT NULL DEFAULT 36,
|
||||
plan_tier VARCHAR(20) NOT NULL DEFAULT 'free',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
```
|
||||
|
||||
建议默认值:
|
||||
|
||||
- Free:`daily_active_question_limit = 5`,`task_daily_hard_cap = 36`
|
||||
- Pro:`daily_active_question_limit = 10`,`task_daily_hard_cap = 72`
|
||||
|
||||
说明:
|
||||
|
||||
1. `tenant_monitoring_quotas` 放在主业务 PostgreSQL。
|
||||
2. `primary_installation_id` 继续引用主业务库中的 `plugin_installations`。
|
||||
|
||||
### 6.4 `monitoring_collect_tasks`
|
||||
|
||||
V5.1 使用 Monitoring PG 单表状态机进行任务租约,并用 RabbitMQ 解耦结果写入。
|
||||
|
||||
```sql
|
||||
CREATE TABLE monitoring_collect_tasks (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
tenant_id BIGINT NOT NULL,
|
||||
brand_id BIGINT NOT NULL,
|
||||
question_id BIGINT NOT NULL,
|
||||
question_text_snapshot TEXT NOT NULL,
|
||||
question_hash VARCHAR(64) NOT NULL,
|
||||
ai_platform_id VARCHAR(30) NOT NULL,
|
||||
run_mode VARCHAR(30) NOT NULL DEFAULT 'plugin_standard',
|
||||
business_date DATE NOT NULL,
|
||||
planned_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'pending',
|
||||
lease_token_hash VARCHAR(128),
|
||||
leased_to_installation BIGINT,
|
||||
leased_at TIMESTAMPTZ,
|
||||
lease_expires_at TIMESTAMPTZ,
|
||||
callback_received_at TIMESTAMPTZ,
|
||||
completed_at TIMESTAMPTZ,
|
||||
skip_reason VARCHAR(50),
|
||||
error_message TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
|
||||
CONSTRAINT uk_collect_task_idempotent
|
||||
UNIQUE (tenant_id, brand_id, question_id, question_hash, ai_platform_id, run_mode, business_date)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_collect_tasks_lease
|
||||
ON monitoring_collect_tasks(tenant_id, status, business_date, lease_expires_at);
|
||||
```
|
||||
|
||||
状态流转:
|
||||
|
||||
- `pending`
|
||||
- `leased`
|
||||
- `received`
|
||||
- `completed`
|
||||
- `skipped`
|
||||
- `expired`
|
||||
- `failed`
|
||||
|
||||
说明:
|
||||
|
||||
1. 任务租约状态在 Monitoring PG 中维护。
|
||||
2. `received` 表示 API 已接收插件结果并成功投递 MQ,但 worker 尚未完成最终入库。
|
||||
|
||||
### 6.5 `question_monitor_runs`
|
||||
|
||||
在 V5 中至少补充:
|
||||
|
||||
```sql
|
||||
ALTER TABLE question_monitor_runs
|
||||
ADD COLUMN IF NOT EXISTS tenant_id BIGINT,
|
||||
ADD COLUMN IF NOT EXISTS business_date DATE,
|
||||
ADD COLUMN IF NOT EXISTS run_mode VARCHAR(30),
|
||||
ADD COLUMN IF NOT EXISTS question_text_snapshot TEXT,
|
||||
ADD COLUMN IF NOT EXISTS question_hash VARCHAR(64),
|
||||
ADD COLUMN IF NOT EXISTS provider_model VARCHAR(100),
|
||||
ADD COLUMN IF NOT EXISTS request_id VARCHAR(100),
|
||||
ADD COLUMN IF NOT EXISTS raw_answer_storage_key TEXT;
|
||||
```
|
||||
|
||||
建议幂等键:
|
||||
|
||||
```sql
|
||||
CREATE UNIQUE INDEX uk_monitor_run_idempotent
|
||||
ON question_monitor_runs(tenant_id, brand_id, question_id, question_hash, ai_platform_id, run_mode, business_date);
|
||||
```
|
||||
|
||||
建议:
|
||||
|
||||
1. `question_monitor_runs` 落在 Monitoring PostgreSQL。
|
||||
2. 若原始回答文本不超过阈值,可直接存库;超大原文再转对象存储。
|
||||
|
||||
### 6.6 文章 URL 归因表
|
||||
|
||||
为支撑“总引用数 / 文章引用率 / 引用文章数”,新增 URL 归因别名表:
|
||||
|
||||
```sql
|
||||
CREATE TABLE monitoring_article_url_aliases (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
tenant_id BIGINT NOT NULL,
|
||||
article_id BIGINT NOT NULL,
|
||||
publish_record_id BIGINT,
|
||||
platform_id VARCHAR(64),
|
||||
external_article_id VARCHAR(128),
|
||||
original_url TEXT NOT NULL,
|
||||
normalized_url TEXT NOT NULL,
|
||||
last_path_segment VARCHAR(255),
|
||||
confidence VARCHAR(20) NOT NULL DEFAULT 'high',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX uk_article_url_alias
|
||||
ON monitoring_article_url_aliases(tenant_id, normalized_url);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 解析标准化与 SoV 统计模型
|
||||
|
||||
### 7.1 解析分层
|
||||
|
||||
V5.1 的解析分为 3 层:
|
||||
|
||||
1. **回答层**:品牌提及、首位提及、首选推荐、情感倾向
|
||||
2. **引用层**:从回答、citation JSON、search_results 中提取 URL 事实
|
||||
3. **来源层**:基于域名清洗和站点聚合生成 SoV 统计结果
|
||||
|
||||
### 7.2 基于 `tldextract` 的顶级域名清洗
|
||||
|
||||
为避免 `www`、二级域名、国家后缀等问题导致同一站点重复计数,引用归因与来源排行统一采用 **Public Suffix List + `tldextract`** 口径。
|
||||
|
||||
处理目标:
|
||||
|
||||
1. 从原始 URL 中提取 `subdomain / domain / suffix`
|
||||
2. 生成 `registrable_domain`
|
||||
3. 生成统一站点主键 `site_key`
|
||||
4. 为页面级和站点级聚合提供稳定维度
|
||||
|
||||
示例:
|
||||
|
||||
| 原始 URL | `registrable_domain` | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `https://www.zhihu.com/question/123` | `zhihu.com` | `www` 折叠 |
|
||||
| `https://zhuanlan.zhihu.com/p/123` | `zhihu.com` | 子域归并到站点 |
|
||||
| `https://mp.weixin.qq.com/s/abc` | `weixin.qq.com` | 基于 PSL 提取可注册域 |
|
||||
| `https://finance.sina.com.cn/roll/123.html` | `sina.com.cn` | 正确处理 `com.cn` |
|
||||
| `https://sub.example.co.uk/a` | `example.co.uk` | 正确处理 `co.uk` |
|
||||
|
||||
推荐清洗流程:
|
||||
|
||||
1. URL 小写化主机名
|
||||
2. 去掉默认端口
|
||||
3. 去掉 fragment
|
||||
4. path 做基础规范化
|
||||
5. host 通过 `tldextract` 计算 `registrable_domain`
|
||||
6. 保存:
|
||||
- `normalized_url`
|
||||
- `host`
|
||||
- `registrable_domain`
|
||||
- `subdomain`
|
||||
- `suffix`
|
||||
|
||||
### 7.3 `monitoring_citation_facts` 推荐字段
|
||||
|
||||
为支撑域名清洗和来源排行,建议引用事实表至少包含:
|
||||
|
||||
```sql
|
||||
CREATE TABLE monitoring_citation_facts (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
tenant_id BIGINT NOT NULL,
|
||||
run_id BIGINT NOT NULL,
|
||||
brand_id BIGINT NOT NULL,
|
||||
ai_platform_id VARCHAR(30) NOT NULL,
|
||||
business_date DATE NOT NULL,
|
||||
cited_url TEXT NOT NULL,
|
||||
normalized_url TEXT NOT NULL,
|
||||
host VARCHAR(255) NOT NULL,
|
||||
registrable_domain VARCHAR(255) NOT NULL,
|
||||
subdomain VARCHAR(255),
|
||||
suffix VARCHAR(50),
|
||||
site_key VARCHAR(255) NOT NULL,
|
||||
article_id BIGINT,
|
||||
publish_record_id BIGINT,
|
||||
resolution_status VARCHAR(20) NOT NULL DEFAULT 'resolved',
|
||||
resolution_confidence VARCHAR(20) NOT NULL DEFAULT 'high',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_citation_facts_domain
|
||||
ON monitoring_citation_facts(tenant_id, business_date, registrable_domain);
|
||||
```
|
||||
|
||||
约定:
|
||||
|
||||
1. `normalized_url` 用于页面级去重和文章归因
|
||||
2. `registrable_domain` 用于站点级来源排行
|
||||
3. `site_key` 默认等于 `registrable_domain`
|
||||
4. 如业务需要区分站群,可对特定域名做覆写映射
|
||||
|
||||
### 7.4 `site_key` 映射规则
|
||||
|
||||
默认规则:
|
||||
|
||||
- `site_key = registrable_domain`
|
||||
|
||||
可选增强规则:
|
||||
|
||||
1. 将 `mp.weixin.qq.com`、`weixin.qq.com` 统一到 `weixin.qq.com`
|
||||
2. 将业务上需要区分的站群写入 `site_domain_mapping`
|
||||
3. 对同集团但不同产品是否合并,必须通过配置显式决定,不靠临时规则猜测
|
||||
|
||||
推荐新增配置表:
|
||||
|
||||
```sql
|
||||
CREATE TABLE site_domain_mappings (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
registrable_domain VARCHAR(255) NOT NULL,
|
||||
site_key VARCHAR(255) NOT NULL,
|
||||
site_name VARCHAR(255) NOT NULL,
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
```
|
||||
|
||||
### 7.5 SoV 统计模型
|
||||
|
||||
V5.1 中的 SoV 统一解释为:
|
||||
|
||||
> 在当前采样窗口内,某品牌、某站点或某文章在全部有效样本中的相对占比
|
||||
|
||||
必须明确分母,禁止不同口径混用。
|
||||
|
||||
### 7.6 品牌 SoV
|
||||
|
||||
品牌 SoV 适用于品牌曝光类页面。
|
||||
|
||||
定义:
|
||||
|
||||
```text
|
||||
brand_sov = brand_mentioned_answers / actual_sample_count
|
||||
```
|
||||
|
||||
其中:
|
||||
|
||||
- `brand_mentioned_answers`:成功采样回答中,提及目标品牌的回答数
|
||||
- `actual_sample_count`:成功采样回答总数
|
||||
|
||||
这和页面上的“提及率”同源,可视为品牌在样本回答中的 `Share of Voice`。
|
||||
|
||||
### 7.7 引用来源 SoV
|
||||
|
||||
引用来源 SoV 适用于“引用来源排行”“来源平台占比”。
|
||||
|
||||
定义:
|
||||
|
||||
```text
|
||||
source_sov = source_citation_count / total_resolved_citation_count
|
||||
```
|
||||
|
||||
其中:
|
||||
|
||||
- `source_citation_count`:某 `site_key` 在窗口内被引用的次数
|
||||
- `total_resolved_citation_count`:全部已成功解析并完成站点归类的引用次数
|
||||
|
||||
补充口径:
|
||||
|
||||
- unresolved 引用不进入分子
|
||||
- unresolved 是否进入分母,要固定为“不进入”
|
||||
- unresolved 单独展示占比,作为质量指标
|
||||
|
||||
### 7.8 文章 SoV
|
||||
|
||||
文章 SoV 用于内部文章在可归因引用中的占比。
|
||||
|
||||
定义:
|
||||
|
||||
```text
|
||||
article_sov = article_citation_count / total_article_resolved_citation_count
|
||||
```
|
||||
|
||||
其中:
|
||||
|
||||
- `article_citation_count`:某篇内部文章被 AI 引用的次数
|
||||
- `total_article_resolved_citation_count`:全部成功归因到内部文章的引用次数
|
||||
|
||||
注意:
|
||||
|
||||
1. 文章 SoV 不是品牌 SoV
|
||||
2. 文章 SoV 的分母不能拿全部回答数
|
||||
3. 若用户看的是“文章引用率”,仍然应使用 `cited_answer_count / actual_sample_count`
|
||||
|
||||
### 7.9 覆盖率与 SoV 的关系
|
||||
|
||||
覆盖率和 SoV 必须分开解释:
|
||||
|
||||
1. `coverage_rate` 反映“今天采样够不够”
|
||||
2. `SoV` 反映“在已采样样本里占比多少”
|
||||
|
||||
因此页面解释顺序应为:
|
||||
|
||||
1. 先看覆盖率
|
||||
2. 再看 SoV / 提及率 / 引用率
|
||||
|
||||
当 `coverage_rate < 40%` 时:
|
||||
|
||||
- 允许展示 SoV
|
||||
- 但必须附带低置信度提示
|
||||
|
||||
### 7.10 聚合输出建议
|
||||
|
||||
建议在 `monitoring_citation_source_daily` 和 `monitoring_citation_page_daily` 中增加:
|
||||
|
||||
1. `citation_count`
|
||||
2. `citation_sov`
|
||||
3. `resolved_count`
|
||||
4. `unresolved_count`
|
||||
5. `confidence_level`
|
||||
|
||||
这样前端可以同时展示:
|
||||
|
||||
- 引用次数
|
||||
- SoV
|
||||
- 数据质量
|
||||
|
||||
---
|
||||
|
||||
## 8. 任务协议与插件侧流程
|
||||
|
||||
### 8.1 统一为租约式协议
|
||||
|
||||
V5 删除 `GET tasks -> POST claim` 两步式协议,统一改为:
|
||||
|
||||
1. 插件请求租约
|
||||
2. 服务端原子分配任务并返回 `lease_token`
|
||||
3. 插件执行采集
|
||||
4. 插件带 `lease_token` 回传结果
|
||||
5. 服务端校验归属、先写任务状态为 `received` 并投递 RabbitMQ
|
||||
6. Monitor Worker 异步写入运行结果、解析结果并完成任务
|
||||
|
||||
### 8.2 插件 API
|
||||
|
||||
```text
|
||||
POST /api/plugin/monitoring/tasks/lease
|
||||
POST /api/plugin/monitoring/tasks/{id}/result
|
||||
POST /api/plugin/monitoring/tasks/{id}/skip
|
||||
```
|
||||
|
||||
请求头:
|
||||
|
||||
- `X-Geo-Installation-Token`
|
||||
- `X-Geo-Installation-Id`
|
||||
|
||||
### 8.3 租约接口行为
|
||||
|
||||
租约接口建议按 PG 原子领取:
|
||||
|
||||
```sql
|
||||
SELECT id
|
||||
FROM monitoring_collect_tasks
|
||||
WHERE tenant_id = $1
|
||||
AND status = 'pending'
|
||||
AND business_date = $2
|
||||
ORDER BY planned_at ASC, id ASC
|
||||
FOR UPDATE SKIP LOCKED
|
||||
LIMIT $3;
|
||||
```
|
||||
|
||||
服务端完成以下动作:
|
||||
|
||||
1. 选出任务
|
||||
2. 生成短期 `lease_token`
|
||||
3. 更新 `status = leased`
|
||||
4. 写入 `lease_expires_at = now() + interval '15 minutes'`
|
||||
|
||||
### 8.4 插件执行策略
|
||||
|
||||
V5 继续复用现有插件基础设施:
|
||||
|
||||
- [background.ts](/Users/liangxu/Documents/test/geo-rankly/apps/browser-extension/entrypoints/background.ts)
|
||||
- [runtime.ts](/Users/liangxu/Documents/test/geo-rankly/apps/browser-extension/src/runtime.ts)
|
||||
- [storage.ts](/Users/liangxu/Documents/test/geo-rankly/apps/browser-extension/src/storage.ts)
|
||||
|
||||
但采样策略改为“小批量、固定预算”:
|
||||
|
||||
| 项目 | 建议值 |
|
||||
| --- | --- |
|
||||
| 心跳频率 | 15 分钟 |
|
||||
| 单次租约任务数 | 2-3 |
|
||||
| 单任务超时 | 60 秒 |
|
||||
| 租约时长 | 15 分钟 |
|
||||
| 每日硬上限 | 按套餐 36 / 72 |
|
||||
|
||||
### 8.5 任务过期与回收
|
||||
|
||||
定时任务每 30 分钟处理一次:
|
||||
|
||||
1. `lease_expires_at < now()` 且状态仍为 `leased` 的任务改为 `expired`
|
||||
2. `received` 状态超过阈值未被 worker 完成时,进入异常告警
|
||||
3. 同一 `business_date` 的 `expired` 任务最多重试 1 次
|
||||
4. 超过 `business_date + 2 days` 的未完成任务不再重试
|
||||
|
||||
---
|
||||
|
||||
## 9. 采集、解析、聚合架构
|
||||
|
||||
### 9.1 首期简化版链路
|
||||
|
||||
```text
|
||||
每日 00:10 调度器生成当天任务
|
||||
↓
|
||||
插件后台 15 分钟心跳租约任务
|
||||
↓
|
||||
插件执行提问并回传结果
|
||||
↓
|
||||
tenant-api:校验租约 -> 标记 received -> 投递 RabbitMQ
|
||||
↓
|
||||
Monitor Worker:消费结果 -> 写 Monitoring PG -> 解析结果 -> 完成任务
|
||||
↓
|
||||
每日 06:30 聚合 Job 汇总到 monitoring_*_daily
|
||||
↓
|
||||
读侧 API + Redis 缓存
|
||||
```
|
||||
|
||||
### 9.2 RabbitMQ 的职责边界
|
||||
|
||||
V5.1 中 RabbitMQ 只承担“结果异步化”,不承担“任务分发”。
|
||||
|
||||
使用 RabbitMQ 的原因:
|
||||
|
||||
1. 插件回调接口可以快速返回,降低超时和重试风险
|
||||
2. 解析、引用归因、写库失败不会直接影响插件侧体验
|
||||
3. 方便把高频写入隔离到独立 worker
|
||||
4. 后续若企业版放大量级,不需要重做写链路
|
||||
|
||||
但 RabbitMQ 不参与以下事项:
|
||||
|
||||
1. 不做任务租约
|
||||
2. 不做读侧查询
|
||||
3. 不强制引入准实时前端刷新
|
||||
|
||||
推荐队列:
|
||||
|
||||
- `monitor.result.ingest`
|
||||
- `monitor.result.dlq`
|
||||
- `monitor.aggregate.trigger`
|
||||
|
||||
### 9.3 Monitoring PostgreSQL 的职责
|
||||
|
||||
独立 Monitoring PG 的目的不是“多一套库”,而是隔离监测链路的写放大。
|
||||
|
||||
承载内容:
|
||||
|
||||
1. 任务表
|
||||
2. 原始回答
|
||||
3. 解析结果
|
||||
4. 引用事实
|
||||
5. 日聚合结果
|
||||
|
||||
收益:
|
||||
|
||||
1. 主业务 PG 不承受高频插入和大文本更新
|
||||
2. Monitoring PG 可独立做分区、归档、VACUUM 策略
|
||||
3. 监测表膨胀不会影响文章、品牌、发布主链路
|
||||
|
||||
### 9.4 原始回答存储
|
||||
|
||||
V5.1 中抓取结果优先进入 Monitoring PG:
|
||||
|
||||
1. 原始回答文本
|
||||
2. 平台响应元数据
|
||||
3. 引用与搜索结果原始 JSON
|
||||
4. 解析后的结构化字段
|
||||
|
||||
可选归档策略:
|
||||
|
||||
1. 小于 64 KB 的原文直接存 Monitoring PG
|
||||
2. 超大原文或完整调试负载转存对象存储
|
||||
3. Monitoring PG 仅保留摘要、关键字段和存储路径
|
||||
|
||||
---
|
||||
|
||||
## 10. 文章引用归因策略
|
||||
|
||||
### 10.1 为什么不能只看 URL 最后一段
|
||||
|
||||
仅按最后一段匹配存在以下问题:
|
||||
|
||||
1. 不同平台可能出现相同 path segment
|
||||
2. 同一平台 PC / H5 / 分享页 URL 可能不同
|
||||
3. AI 引用结果可能带参数、重定向或 canonical URL
|
||||
4. 同一篇文章可能存在多个可访问别名
|
||||
|
||||
因此 V5 的归因策略是 **分层匹配 + 置信度标记**。
|
||||
|
||||
### 10.2 归因顺序
|
||||
|
||||
1. `normalized_url` 精确匹配
|
||||
2. `(platform_id, external_article_id)` 精确匹配
|
||||
3. `(domain, last_path_segment)` 唯一匹配
|
||||
4. 无法唯一命中时,记为 `unresolved`
|
||||
|
||||
### 10.3 低置信度处理
|
||||
|
||||
当仅通过 `last_path_segment` 命中时:
|
||||
|
||||
1. 给归因结果打 `low_confidence`
|
||||
2. 可以进入趋势参考视图
|
||||
3. 不建议进入需要精确计费或精确归属的报表
|
||||
|
||||
这与 V5 的“趋势参考”定位是一致的。
|
||||
|
||||
---
|
||||
|
||||
## 11. 平台接入范围
|
||||
|
||||
### 11.1 V5 交付范围
|
||||
|
||||
数据模型支持 6 平台扩展,但首期交付只承诺首批 3 平台:
|
||||
|
||||
1. DeepSeek
|
||||
2. 通义千问
|
||||
3. 豆包
|
||||
|
||||
### 11.2 页面与套餐表达
|
||||
|
||||
页面和套餐都必须按“当前已接入且已登录的平台”解释:
|
||||
|
||||
- 已接入且已登录:进入采样计划
|
||||
- 已接入未登录:展示 `未登录`
|
||||
- 未接入:展示 `未接入`
|
||||
|
||||
不得再用“全 6 平台稳定可用”作为首期对外承诺。
|
||||
|
||||
---
|
||||
|
||||
## 12. 读侧与汇总表
|
||||
|
||||
V5 读侧继续沿用 V3 的聚合表和缓存思路,但口径增加采样字段:
|
||||
|
||||
1. `planned_sample_count`
|
||||
2. `actual_sample_count`
|
||||
3. `coverage_rate`
|
||||
4. `confidence_level`
|
||||
|
||||
推荐所有 `monitoring_*_daily` 汇总表统一增加以上字段。
|
||||
|
||||
### 12.1 Dashboard composite
|
||||
|
||||
`/api/tenant/monitoring/dashboard/composite` 继续保留,但返回结构需扩展:
|
||||
|
||||
```json
|
||||
{
|
||||
"overview": {
|
||||
"mention_rate": 0.42,
|
||||
"citation_rate": 0.18,
|
||||
"planned_sample_count": 30,
|
||||
"actual_sample_count": 21,
|
||||
"coverage_rate": 0.70,
|
||||
"confidence_level": "high",
|
||||
"last_sampled_at": "2026-04-08T09:20:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 12.2 缓存策略
|
||||
|
||||
读侧缓存策略可继续沿用 V3:
|
||||
|
||||
- composite 聚合缓存
|
||||
- singleflight
|
||||
- TTL 抖动
|
||||
- stale-while-revalidate
|
||||
|
||||
但缓存失效触发点改为:
|
||||
|
||||
1. 每日聚合完成
|
||||
2. 用户切换品牌 / 时间窗口
|
||||
|
||||
不需要为单条采样结果做准实时逐条失效。
|
||||
|
||||
---
|
||||
|
||||
## 13. 降级、告警与风险
|
||||
|
||||
### 13.1 降级策略
|
||||
|
||||
| 场景 | V5 行为 |
|
||||
| --- | --- |
|
||||
| 插件 24h 未活跃 | 暂停生成新任务,页面展示上次数据 |
|
||||
| 平台未登录 | 该平台不进入当天计划样本 |
|
||||
| 当日覆盖率低 | 页面提示“样本不足,仅供参考” |
|
||||
| 某平台 adapter 失效 | 该平台展示“暂不可用”,其余平台继续 |
|
||||
| URL 无法归因 | 计入 unresolved,不强行挂到文章 |
|
||||
|
||||
### 13.2 关键告警
|
||||
|
||||
| 指标 | 阈值 |
|
||||
| --- | --- |
|
||||
| 主插件离线时长 | > 24h |
|
||||
| 日覆盖率 | < 40% |
|
||||
| 单平台连续失败 | > 20 次 / 天 |
|
||||
| unresolved 引用占比 | > 30% |
|
||||
| 每日聚合延迟 | > 08:00 未完成 |
|
||||
|
||||
### 13.3 风险接受说明
|
||||
|
||||
V5 明确接受以下风险:
|
||||
|
||||
1. 单日样本偏少导致波动大
|
||||
2. 插件离线导致当天无新数据
|
||||
3. 部分 URL 只能低置信度归因
|
||||
|
||||
但不接受以下风险:
|
||||
|
||||
1. 任务重复执行导致统计翻倍
|
||||
2. 输入口径变更污染历史数据
|
||||
3. 未授权回调写入虚假监测结果
|
||||
|
||||
---
|
||||
|
||||
## 14. 实施计划
|
||||
|
||||
### 14.1 Phase 分期
|
||||
|
||||
| Phase | 内容 | 估时 |
|
||||
| --- | --- | --- |
|
||||
| A | 双库方案落地:主业务 PG / Monitoring PG schema 与连接配置 | 4 天 |
|
||||
| B | RabbitMQ 基础设施 + Monitor Worker 骨架 + DLQ | 4 天 |
|
||||
| C | 插件:AI adapter 框架 + 3 平台 + 租约式任务执行 | 6 天 |
|
||||
| D | 后端:租约 API、结果回调、MQ 消费、解析入库、日聚合 | 7 天 |
|
||||
| E | 前端:数据追踪页补覆盖率、更新时间、置信度提示 | 4 天 |
|
||||
| F | 联调、坏样本验证、口径验收 | 4 天 |
|
||||
| **合计** | | **29 天** |
|
||||
|
||||
### 14.2 验收标准
|
||||
|
||||
1. 页面可展示 `planned_sample_count / actual_sample_count / coverage_rate`
|
||||
2. 页面可正常展示趋势图、高频问题、引用排行
|
||||
3. 当覆盖率不足时,页面有明确提示
|
||||
4. 同一任务不会被重复计入统计
|
||||
5. 问题文案修改后,历史趋势不被污染
|
||||
6. 引用文章数可通过 URL alias 规则稳定归因,无法归因时进入 unresolved
|
||||
|
||||
---
|
||||
|
||||
## 15. V4 → V5 变更摘要
|
||||
|
||||
| 编号 | V4 | V5 |
|
||||
| --- | --- | --- |
|
||||
| C1 | 全量监测叙事 | 采样趋势叙事 |
|
||||
| C2 | Free 1 品牌 / 40 问题 | Free 3-5 个每日活跃问题 |
|
||||
| C3 | Pro 3 品牌 / 40 问题 | Pro 5-10 个每日活跃问题 |
|
||||
| C4 | GET + claim 两步任务协议 | 一步租约式任务协议 |
|
||||
| C5 | `question_version_id` | `question_hash + question_text_snapshot` |
|
||||
| C6 | RabbitMQ + 增量聚合 | RabbitMQ 仅做结果异步化,不参与任务分发或前端准实时 |
|
||||
| C7 | 准实时可见 | T+1 / 延迟可接受 |
|
||||
| C8 | 仅展示百分比 | 百分比 + 采样覆盖信息 |
|
||||
| C9 | 首期 6 平台叙事 | 首期只承诺 3 平台 |
|
||||
| C10 | 单一业务库承载全部数据 | 主业务 PG + Monitoring PG 双库隔离 |
|
||||
|
||||
## 16. 最终建议
|
||||
|
||||
V5 适合当前阶段的原因不是“它最强”,而是“它和当前约束一致”:
|
||||
|
||||
1. 和单实例插件现实相符
|
||||
2. 和 PRD 的 V1 边界相符
|
||||
3. 和用户对“趋势参考”的接受度相符
|
||||
|
||||
如果未来要升级到 V6,再考虑:
|
||||
|
||||
1. 专用在线采集节点
|
||||
2. 企业版更高采样量
|
||||
3. 更细粒度的增量聚合和近实时刷新
|
||||
4. 更强的 URL 归因与多次重复采样校准
|
||||
Reference in New Issue
Block a user