# GEO 平台问题驱动的数据监控设计 V1 ## 1. 文档信息 | 项目 | 内容 | | --- | --- | | 文档名称 | GEO 平台问题驱动的数据监控设计 V1 | | 文档版本 | V1.0 | | 文档状态 | 待评审 | | 创建日期 | 2026-03-25 | | 适用范围 | 品牌词库、问题集、数据追踪页 | | 关联文档 | `docs/geo-platform-prd-v1.md` | | 关联文档 | `docs/geo-platform-tech-architecture-v1-50k.md` | ## 2. 设计背景 当前数据监控页不是通用 BI,而是明确来源于: - 品牌 - 关键词 - 关键词下的问题集 - 模型 - 最近 30 天时间窗口 因此,数据监控的真实逻辑源头不是“文章”本身,也不是“关键词”本身,而是: - `品牌关键词下配置的问题` - `系统按周期执行这些问题后的模型回答结果` 这意味着整套监控系统必须按“问题驱动”来设计,否则后续会出现: - 指标口径不清 - 历史数据与问题版本混淆 - 任务量失控 - 查询逻辑和数据模型对不上 ## 3. 核心结论 V1 建议采用以下原则: 1. `问题` 是监控数据的最小采集单元。 2. `关键词` 只是问题集合的容器和聚合维度。 3. `品牌监控页` 展示的是问题运行结果的聚合视图。 4. `历史数据必须绑定问题版本`,不能因为改了问题文案就覆盖历史口径。 5. `页面查询只查汇总表`,不扫描原始问答记录。 ## 4. 页面逻辑拆解 结合当前页面,用户选择条件是: - 品牌 - 关键词 - 模型 - 时间范围,最多近 30 天 页面展示内容包括: - 提及率 - 首位提及率 - 首选推荐率 - 正面提及率 - 日趋势折线 - 高频问题 - 引用排行榜 这些数据不应该直接从原始问答明细实时计算,而应该来自预聚合结果。 ## 5. 数据采集逻辑 ## 5.1 问题来源 问题来源于品牌词库中的关键词问题集。 例如: - 品牌:12 - 关键词:数字时尚 - 问题: - 数字时尚品牌有哪些值得关注的新趋势 - 如何选择适合自己的数字时尚单品 - 数字时尚和传统时尚的主要区别是什么 这些问题是监控任务的输入,不是单纯展示内容。 ## 5.2 采集单元 建议将一次监控采集定义为: - 一个问题 - 一个模型 - 一次执行时间 即: `1 question x 1 model x 1 run_time = 1 条原始监控记录` ## 5.3 指标来源 每次模型回答返回后,系统进行解析,得到结构化结果: - 是否提及品牌 - 是否首位提及品牌 - 是否首选推荐品牌 - 情感倾向是否正向 - 是否引用系统文章 - 引用了哪篇文章 然后再做日级汇总。 ## 6. 推荐数据模型 ## 6.1 主数据表 ### `brand_keywords` - `id` - `brand_id` - `name` - `status` - `created_at` ### `brand_questions` - `id` - `brand_id` - `keyword_id` - `current_version_id` - `status` - `created_at` 说明: - 一条问题属于一个品牌和一个关键词 - 数据监控页中的“高频问题”和“问题集”都可以从这里关联出来 ### `brand_question_versions` - `id` - `question_id` - `question_text` - `question_hash` - `version_no` - `is_active` - `created_at` 说明: - 问题文案一旦修改,新增版本,不覆盖历史版本 - 历史监控记录绑定到具体 `question_version_id` ## 6.2 原始采集表 ### `question_monitor_runs` - `id` - `brand_id` - `keyword_id` - `question_id` - `question_version_id` - `model_id` - `run_date` - `run_at` - `task_batch_id` - `status` - `raw_answer_storage_key` - `created_at` 说明: - 一次执行一条记录 - 模型原始回答建议放对象存储,数据库只存引用路径和摘要字段 ### `question_monitor_parse_results` - `id` - `run_id` - `mentioned_brand` - `top1_mentioned` - `first_recommended` - `positive_mentioned` - `citation_article_count` - `citation_platform_count` - `parser_version` - `created_at` 说明: - 解析结果是后续聚合的直接来源 ## 6.3 汇总表 ### `question_model_daily_metrics` 粒度: - `brand_id + keyword_id + question_id + question_version_id + model_id + date` 字段: - `ask_count` - `mention_count` - `top1_mention_count` - `first_recommend_count` - `positive_mention_count` - `citation_count` - `updated_at` 用途: - 回看单个问题在每天的表现 - 支撑高频问题排行和问题详情 ### `keyword_model_daily_metrics` 粒度: - `brand_id + keyword_id + model_id + date` 字段: - `question_count` - `ask_count` - `mention_count` - `top1_mention_count` - `first_recommend_count` - `positive_mention_count` - `citation_count` - `mention_rate` - `top1_mention_rate` - `first_recommend_rate` - `positive_mention_rate` - `updated_at` 用途: - 直接支撑你截图里的 30 天趋势和顶部指标卡 ### `keyword_model_top_questions_daily` 粒度: - `brand_id + keyword_id + model_id + date + question_id` 字段: - `question_text_snapshot` - `ask_count` - `mention_count` - `score` 用途: - 支撑右侧“高频问题” ### `keyword_model_citation_rank_daily` 粒度: - `brand_id + keyword_id + model_id + date + cited_article_id` 字段: - `citation_count` - `citation_rate` 用途: - 支撑引用排行榜 ## 7. 查询口径定义 ## 7.1 顶部指标卡 以所选: - 品牌 - 关键词 - 模型 - 时间范围 为过滤条件,从 `keyword_model_daily_metrics` 汇总得出: - 提及率 = `sum(mention_count) / sum(ask_count)` - 首位提及率 = `sum(top1_mention_count) / sum(ask_count)` - 首选推荐率 = `sum(first_recommend_count) / sum(ask_count)` - 正面提及率 = `sum(positive_mention_count) / sum(ask_count)` ## 7.2 趋势图 直接查询 `keyword_model_daily_metrics` 最近 30 天数据。 每个点只是一行日汇总,查询量很小。 ## 7.3 高频问题 从 `keyword_model_top_questions_daily` 取最近 30 天累计 TopN。 ## 7.4 引用排行榜 从 `keyword_model_citation_rank_daily` 取最近 30 天累计 TopN。 ## 8. 任务调度设计 ## 8.1 为什么要控制任务量 监控任务量不是由页面访问量决定,而是由: - 品牌数 - 每个品牌下关键词数 - 每个关键词下问题数 - 模型数 - 每天执行次数 共同决定。 ## 8.2 数据量公式 每日原始采集量近似为: `品牌数 x 每品牌关键词数 x 每关键词问题数 x 模型数 x 每日执行次数` 例如: ### 轻量场景 - 50 个品牌 - 每品牌 10 个关键词 - 每关键词 10 个问题 - 5 个模型 - 每天 1 次 则: `50 x 10 x 10 x 5 x 1 = 25,000 条/天` ### 中等场景 - 100 个品牌 - 每品牌 20 个关键词 - 每关键词 20 个问题 - 5 个模型 - 每天 2 次 则: `100 x 20 x 20 x 5 x 2 = 400,000 条/天` 如果每条原始回答按 5KB 估算: - 每天约 2GB 原始文本 - 30 天约 60GB 原始文本 因此: - 原始回答不建议长期放 PostgreSQL - 建议放对象存储 - PostgreSQL 只保存解析结果和汇总结果 ## 8.3 V1 推荐执行频率 为了控制数据量,V1 建议: - 每个问题每天默认执行 `1 次` - 重要品牌或付费用户可提升为 `2 次` - 不建议 V1 做高频小时级轮询 ## 8.4 去重策略 如果同一品牌下多个关键词出现完全相同的问题文案,建议: - 通过 `question_hash` 检测重复问题 - 同一品牌、同一模型、同一天内同 hash 的问题尽量复用一次执行结果 但聚合归属仍可按映射关系分别记账。 这样可以显著减少任务量。 ## 9. 版本与历史口径 ## 9.1 为什么必须版本化 如果用户把问题: - “数字时尚品牌有哪些值得关注的新趋势” 改成: - “2026 年数字时尚品牌有哪些值得关注的新趋势” 那前后两者其实已经不是同一个监控口径。 如果直接覆盖旧问题,历史趋势就会失真。 ## 9.2 推荐做法 - 问题修改时新建 `question_version` - 新运行记录绑定到新版本 - 历史数据仍归属旧版本 - 页面默认展示当前版本问题的聚合数据 如果要做历史总览,可按 `question_id` 聚合,但需要在文档里说明“跨版本聚合”。 ## 10. 页面查询策略 ## 10.1 页面不查原始表 数据页不能直接查: - `question_monitor_runs` - `question_monitor_parse_results` 这些表只用于: - 问题详情 - badcase 回放 - 算法验证 ## 10.2 页面只查汇总表 数据页查询路径应为: 1. 先查 Redis 2. Redis miss 后查 PostgreSQL 汇总表 3. 后台异步更新缓存 ## 10.3 Redis Key 建议 可按以下维度缓存: - `brand:{brand_id}:keyword:{keyword_id}:model:{model_id}:range:30d:summary` - `brand:{brand_id}:keyword:{keyword_id}:model:{model_id}:range:30d:trend` - `brand:{brand_id}:keyword:{keyword_id}:model:{model_id}:range:30d:top_questions` - `brand:{brand_id}:keyword:{keyword_id}:model:{model_id}:range:30d:citation_rank` ## 11. V1 性能判断 针对当前页面,如果采用“按问题采集、按天汇总、按 30 天查询”的模型,PostgreSQL 是够用的。 原因是: - 页面查询只查 30 天汇总数据 - 每次查询最多几十到几百行 - 高频问题和排行榜都可提前汇总 - Redis 可以承接高频重复查询 真正可能变大的不是页面查询,而是: - 原始问题执行任务量 - 原始模型回答存储量 所以要重点控制的是任务调度和原始结果留存,而不是页面 SQL 本身。 ## 12. V1 推荐保留策略 建议: - 原始运行记录保留 `30 ~ 90 天` - 原始回答正文放对象存储 - PostgreSQL 解析结果保留 `90 天` - 日级汇总表保留 `180 天` 或更久 如果页面只看 30 天,也建议汇总表多保留一段时间,方便后续扩展。 ## 13. 升级边界 当出现以下情况时,再升级架构: - 问题量暴涨 - 模型数明显增加 - 每日执行次数明显增加 - 页面支持自由筛选、明细钻取 - 监控维度从 30 天扩展到半年、一年 此时可以逐步升级为: - 独立分析库 - 更大规模任务调度 - 更复杂的去重复用逻辑 - 专用分析引擎 ## 14. V1 最终建议 结合你当前页面和品牌词库结构,V1 最合理的设计是: 1. 以 `问题` 为最小监控采集单元。 2. 以 `关键词` 作为问题的聚合容器。 3. 以 `问题版本` 保证历史口径稳定。 4. 以 `日级汇总表` 支撑页面查询。 5. 以 `对象存储` 保存原始回答,避免 PostgreSQL 膨胀。 6. 以 `问题 hash 去重` 控制执行量。 7. 默认每题每天执行 1 次,避免 V1 数据量失控。 这样做,逻辑、数据量、页面性能三者才能对齐。