# 在现有个人网站中实现“需求雷达”：可直接执行的扩展开发方案

> 本文是一份面向现有项目的开发规格。实施时不新建另一套网站、账号系统或内容后台，而是在原有 Next.js 网站中增加管理界面，并新增一个隔离运行的 Python 分析服务。

## 1. 开发目标

“需求雷达”要帮助我从公开讨论中提取真实问题，再根据原文证据决定是否进入产品验证。

完整链路如下：

```text
公开数据源
  → Python 服务采集
  → 原文进入现有数据库的 radar_* 表
  → AI 提取需求证据
  → Embedding 召回相似问题
  → 在原网站中人工审核
  → 形成需求簇和机会卡
  → 关联文章、验证页、组件和开发任务
  → 通过现有 MCP 提供给大模型
```

系统的目标不是自动宣布“应该开发什么产品”，而是建立一条可追溯的证据链：

> 原始表达 → 使用场景 → 问题 → 当前方案 → 不满意之处 → 需求簇 → 验证假设

## 2. 与原项目的集成原则

这不是一个新的独立产品，而是原网站的内部运营模块。

### 2.1 直接复用

- 原网站的 Next.js 应用；
- 原有登录和管理员权限；
- 原有数据库及迁移机制；
- 原有 UI 组件和管理页面布局；
- 原有文章、动态组件和模板系统；
- 原有 MCP 服务及身份验证；
- 原有部署、日志和配置方式。

### 2.2 新增但保持隔离

- Python Radar Worker；
- 采集器、文本清洗和模型调用；
- Embedding 与相似需求召回；
- 定时任务；
- 所有以 `radar_` 为边界的数据表；
- Next.js 中的 `radar` 管理路由；
- MCP 中只读优先的 Radar 工具。

### 2.3 明确禁止重复建设

第一版不得：

- 新建另一套用户表；
- 新建另一个登录页面；
- 新建另一套文章系统；
- 为 Radar 单独创建公共前台；
- 让浏览器直接访问 Python 服务；
- 让 Python 服务取得整个主站数据库权限；
- 为了加入一个模块先重构全站 Monorepo；
- 把 Playwright、Embedding 或批量模型任务放进普通 Next.js 请求生命周期。

## 3. 开发前先审查现有项目

文章不能预先假设原项目使用 Prisma、Drizzle、PostgreSQL 或 MySQL。实际开发的第一步必须读取项目并生成《接入审查结果》。

至少确认以下项目：

| 检查项 | 需要得到的结果 |
| --- | --- |
| Next.js 结构 | App Router 或 Pages Router |
| 数据库 | PostgreSQL、MySQL、SQLite 或其他 |
| ORM | Prisma、Drizzle、TypeORM 或自研 |
| 身份认证 | Session、JWT、Auth.js 或自研 |
| 管理员判断 | 现有角色字段或权限函数 |
| API 模式 | Route Handler、Server Action 或独立 API |
| 部署方式 | Node 常驻、Serverless、Docker 或面板进程 |
| MCP 位置 | 同仓库、独立服务或远程服务 |
| 文章关联 | Article 主键和关系建模方式 |
| 组件关联 | 生成组件的主键和版本方式 |
| 测试工具 | Vitest、Playwright、pytest 等 |
| 目录规范 | 当前模块、服务、数据库代码放置规则 |

审查结束后只输出差异化落点，例如：

```text
现有 ORM：Prisma
新增模型位置：prisma/schema.prisma
管理员守卫：src/server/auth/require-admin.ts
内部 API：src/app/api/internal/radar/*
Python 服务：services/demand-radar/
数据库：复用现有 PostgreSQL
```

不能因为本文给出了 SQL 示例，就绕开原有 ORM 和迁移体系直接建表。

## 4. 目标架构

系统采用“同一个产品、同一个仓库、同一个管理入口、两个运行进程”。

```text
浏览器
  │
  ▼
现有 Next.js 网站
  ├─ 复用登录与管理员权限
  ├─ /radar/inbox
  ├─ /radar/clusters
  ├─ /radar/opportunities
  └─ /api/internal/radar/*
             │
             │ 内网请求 + 服务密钥
             ▼
Python Radar Worker / API
  ├─ Collectors
  ├─ Normalizer
  ├─ LLM Extractor
  ├─ Embedding
  ├─ Similarity Suggestions
  └─ Scheduler
             │
             ▼
现有数据库中的 radar_* 表
```

职责分工：

| 模块 | 负责 | 不负责 |
| --- | --- | --- |
| Next.js | UI、登录校验、人工审核、数据关联、代理请求 | 长时采集、本地模型、批量任务 |
| Python 服务 | 采集、清洗、AI 提取、Embedding、聚类建议 | 用户登录、公开页面、文章发布 |
| 数据库 | 原文、证据、任务状态、关联关系 | 替代业务判断 |
| MCP | 向模型提供审核后的数据和有限操作 | 执行任意爬虫、任意 SQL |

## 5. 仓库落点

如果原项目不是 Monorepo，不为此进行大规模目录迁移。推荐直接新增：

```text
<existing-project>/
├─ src/
│  ├─ app/
│  │  ├─ radar/
│  │  │  ├─ layout.tsx
│  │  │  ├─ inbox/page.tsx
│  │  │  ├─ clusters/page.tsx
│  │  │  ├─ clusters/[id]/page.tsx
│  │  │  └─ opportunities/[id]/page.tsx
│  │  └─ api/internal/radar/
│  ├─ components/radar/
│  ├─ features/radar/
│  │  ├─ api/
│  │  ├─ schemas/
│  │  ├─ queries/
│  │  └─ permissions/
│  └─ server/radar/
├─ services/
│  └─ demand-radar/
│     ├─ app/
│     │  ├─ api/
│     │  ├─ collectors/
│     │  ├─ extractors/
│     │  ├─ embeddings/
│     │  ├─ repositories/
│     │  ├─ services/
│     │  ├─ jobs/
│     │  ├─ core/
│     │  └─ main.py
│     ├─ tests/
│     ├─ pyproject.toml
│     └─ Dockerfile
├─ scripts/
└─ docker-compose.yml
```

如果现有项目已经是 Monorepo，则遵循原有 workspace 规则，把 Python 服务放入已有的 `apps` 或 `services` 目录。

## 6. 数据库策略

### 6.1 优先复用现有数据库

只要原数据库适合保存业务数据，就在其中增加独立命名空间：

```text
radar_topics
radar_sources
radar_runs
radar_documents
radar_evidence
radar_extractions
radar_embeddings
radar_clusters
radar_cluster_members
radar_reviews
radar_opportunities
radar_links
radar_audit_logs
```

不要默认再引入 SQLite。只有当原项目没有数据库，或 Python 原型必须完全离线运行时，才使用 SQLite。

### 6.2 Python 数据库权限

为 Python 服务创建独立数据库账号，只允许：

- 读写 `radar_*` 表；
- 读取经过明确批准的少量关联表；
- 不允许修改用户、文章、组件和权限表；
- 不允许执行数据库结构变更。

数据库迁移仍由原项目的迁移工具负责。

### 6.3 与原业务的关联

Radar 不直接在核心表上增加大量字段，而是通过通用关联表连接：

```sql
CREATE TABLE radar_links (
  id VARCHAR(36) PRIMARY KEY,
  opportunity_id VARCHAR(36) NOT NULL,
  target_type VARCHAR(32) NOT NULL,
  target_id VARCHAR(128) NOT NULL,
  relation_type VARCHAR(32) NOT NULL,
  created_at TIMESTAMP NOT NULL,
  UNIQUE(opportunity_id, target_type, target_id, relation_type)
);
```

建议枚举：

```text
target_type:
article | validation_page | generated_component | layout_template | development_task

relation_type:
evidence | explains | implements | validates | documents
```

这样可以建立：

```text
机会卡
  ├─ 关联分析文章
  ├─ 关联问题验证页
  ├─ 关联生成组件
  └─ 关联开发任务
```

## 7. 核心数据模型

### 7.1 radar_topics

记录研究方向：

```text
id
name
description
status
created_at
updated_at
```

第一期主题：

> 个人开发者如何利用 AI 降低网站内容管理和运营成本。

### 7.2 radar_sources

记录数据源与增量游标：

```text
id
topic_id
source_type
name
config_json
cursor_json
enabled
last_collected_at
created_at
updated_at
```

密钥不能进入 `config_json`，只保存环境变量引用名称。

### 7.3 radar_runs

所有长任务统一记录：

```text
id
job_type
source_id
status
started_at
finished_at
scanned_count
inserted_count
skipped_count
failed_count
cursor_before_json
cursor_after_json
error_code
error_message
metadata_json
```

任务状态：

```text
pending → running → succeeded
                  ↘ partially_failed
                  ↘ failed
                  ↘ cancelled
```

### 7.4 radar_documents

保留未经模型改写的原文：

```text
id
source_id
external_id
canonical_url
title
author_external_id
author_name
published_at
raw_text
normalized_text
content_hash
language
metadata_json
processing_status
collected_at
updated_at
```

唯一约束：

```text
(source_id, external_id)
(canonical_url, content_hash)
```

### 7.5 radar_evidence

记录当前可审核的需求证据：

```text
id
document_id
extraction_id
ordinal
user_type
scenario
problem
expected_result
current_solution
dissatisfaction_json
demand_signal
payment_signal
evidence_quote
confidence
review_status
created_at
updated_at
```

枚举：

```text
demand_signal:
complaint | request | workaround | migration | purchase | discussion

payment_signal:
none | weak | explicit

review_status:
pending | accepted | corrected | rejected
```

### 7.6 radar_extractions

单独保存模型运行记录，避免升级模型时覆盖历史：

```text
id
document_id
model_name
extractor_version
prompt_version
input_hash
raw_response
token_usage_json
duration_ms
status
error_message
created_at
```

人工审核过的证据默认不得被新提取结果覆盖。

### 7.7 radar_embeddings

```text
evidence_id
model_name
model_revision
dimensions
vector_data
input_hash
created_at
```

如果原数据库支持向量类型，可使用原生向量列；否则第一版保存 `float32` 二进制，由 Python 批量读取计算余弦相似度。几百条数据不需要额外部署向量数据库。

### 7.8 radar_clusters 与 radar_cluster_members

```text
radar_clusters:
id
topic_id
name
summary
target_user
core_problem
status
score_json
opportunity_score
created_at
updated_at

radar_cluster_members:
cluster_id
evidence_id
membership_source
similarity
created_at
```

`membership_source`：

```text
suggested | manual | confirmed
```

### 7.9 radar_opportunities

```text
id
cluster_id
hypothesis
proposed_solution
validation_method
success_criteria
rejection_criteria
status
created_at
updated_at
```

状态：

```text
draft | ready_to_validate | validating | validated | rejected | archived
```

## 8. 统一采集器接口

每个来源实现统一协议：

```python
from datetime import datetime
from typing import Any, Protocol
from pydantic import BaseModel, HttpUrl

class CollectedDocument(BaseModel):
    external_id: str | None
    canonical_url: HttpUrl
    title: str | None
    author_external_id: str | None
    author_name: str | None
    published_at: datetime | None
    raw_text: str
    language: str | None
    metadata: dict[str, Any]

class CollectResult(BaseModel):
    documents: list[CollectedDocument]
    next_cursor: dict[str, Any] | None

class Collector(Protocol):
    async def collect(
        self,
        config: dict[str, Any],
        cursor: dict[str, Any] | None,
    ) -> CollectResult: ...
```

后续新增来源只增加新的 Collector，不修改下游流程。

Crawlee 提供 HTTP、无头浏览器、请求路由、持久队列、并发和重试等能力，可以用于后续网页来源扩展。[Crawlee for Python 官方文档](https://crawlee.dev/python/docs/introduction)

## 9. 第一数据源：GitHub Issues

第一版先接一个真实数据源，不同时开发多个采集器。

推荐顺序：

1. GitHub REST API；
2. 指定仓库的 Issues；
3. 可选读取有限数量的评论；
4. 后续再加入 Discussions 或公开论坛。

配置示例：

```json
{
  "repositories": ["owner/repository"],
  "state": "all",
  "labels": [],
  "include_comments": true,
  "max_comments": 20,
  "updated_since_days": 30,
  "max_items_per_run": 100
}
```

GitHub 的 Issues API 结果可能包含 Pull Request。存在 `pull_request` 字段时，第一版跳过，避免把代码审查讨论混入需求样本。[GitHub Issues REST API](https://docs.github.com/en/rest/issues/issues)

一条 Issue 形成一条 `radar_document`：

```text
Issue 标题
Issue 正文

[评论 | 作者 | 时间]
评论正文
```

过滤：

- Bot 评论；
- Issue 模板中未填写的占位符；
- 大段日志和堆栈；
- 重复引用；
- 与问题无关的自动通知。

## 10. 增量采集和幂等

执行规则：

1. 新建 `radar_run` 并保存旧游标；
2. 拉取一页；
3. 标准化每条记录；
4. 根据外部 ID 和内容哈希幂等写入；
5. 当前页全部持久化后才推进游标；
6. 请求失败时保持旧游标；
7. 429 按响应头等待；
8. 5xx 使用指数退避；
9. 单条解析失败只增加失败计数；
10. 服务重启时将遗留的 `running` 任务标记为失败或中断。

文本标准化只做：

- Unicode NFKC；
- 换行统一；
- 连续空白折叠；
- 明确模板噪声删除；
- 保留原句、标点和段落。

```python
def content_hash(normalized_text: str) -> str:
    return sha256(normalized_text.encode("utf-8")).hexdigest()
```

`raw_text` 永久保存，后续清洗规则变化时重新生成 `normalized_text`，不得反向覆盖原文。

## 11. AI 结构化提取

Pydantic 用于校验模型输出，并可以生成 JSON Schema 作为结构化输出契约。[Pydantic Models 官方文档](https://docs.pydantic.dev/latest/concepts/models/)

```python
from enum import StrEnum
from pydantic import BaseModel, ConfigDict, Field

class DemandSignal(StrEnum):
    COMPLAINT = "complaint"
    REQUEST = "request"
    WORKAROUND = "workaround"
    MIGRATION = "migration"
    PURCHASE = "purchase"
    DISCUSSION = "discussion"

class PaymentSignal(StrEnum):
    NONE = "none"
    WEAK = "weak"
    EXPLICIT = "explicit"

class ExtractedEvidence(BaseModel):
    model_config = ConfigDict(extra="forbid")

    user_type: str | None
    scenario: str | None
    problem: str = Field(min_length=4, max_length=300)
    expected_result: str | None
    current_solution: str | None
    dissatisfaction: list[str] = Field(default_factory=list)
    demand_signal: DemandSignal
    payment_signal: PaymentSignal
    evidence_quote: str = Field(min_length=2, max_length=500)
    confidence: float = Field(ge=0, le=1)

class ExtractionResult(BaseModel):
    model_config = ConfigDict(extra="forbid")

    is_demand_evidence: bool
    rejection_reason: str | None = None
    items: list[ExtractedEvidence] = Field(default_factory=list)
```

Prompt 固定规则：

- 只提取原文明确表达的问题；
- 不把文章主题当成作者需求；
- 不推断职业和支付能力；
- 没有信息则返回 `null` 或 `none`；
- 一篇文档允许零条或多条证据；
- 广告、新闻和泛泛观点标记为无证据；
- `evidence_quote` 必须来自输入原文。

程序二次验证引用：

```python
for item in result.items:
    if item.evidence_quote not in document.raw_text:
        raise UngroundedEvidenceError(item.evidence_quote)
```

落库时同时记录模型、Prompt、输入哈希、原始响应、耗时和 Token。模型升级产生新 `radar_extraction`，不覆盖旧结果。

## 12. Embedding 与相似需求召回

Sentence Transformers 可以生成句向量并计算语义相似度，适合第一版进行本地 Top-K 召回。[Semantic Textual Similarity 文档](https://www.sbert.net/docs/sentence_transformer/usage/semantic_textual_similarity.html)

Embedding 输入只使用结构化字段：

```python
def build_embedding_text(evidence) -> str:
    return "\n".join([
        f"用户类型：{evidence.user_type or '未知'}",
        f"使用场景：{evidence.scenario or '未知'}",
        f"核心问题：{evidence.problem}",
        f"期望结果：{evidence.expected_result or '未知'}",
        f"当前方案：{evidence.current_solution or '未知'}",
    ])
```

第一版不自动决定归类：

1. 仅处理 `accepted` 和 `corrected` 证据；
2. 计算新证据与已有簇中心向量的余弦相似度；
3. 返回 Top 5；
4. 大于 0.82：强候选；
5. 0.68～0.82：弱候选；
6. 小于 0.68：建议创建新簇；
7. 人工确认后才写入正式成员关系。

阈值必须使用环境变量配置，并通过人工标注样本校准。

## 13. Next.js 内部代理

浏览器不直接请求 Python 服务。

请求链路：

```text
浏览器
  → Next.js /api/internal/radar/*
  → 检查现有 Session
  → 检查管理员权限
  → 添加 RADAR_SERVICE_TOKEN
  → 转发给 Python 服务
```

代理必须：

- 复用原项目的管理员守卫；
- 设置超时；
- 限制请求体大小；
- 过滤不应转发的 Header；
- 统一错误格式；
- 不把内部服务地址和密钥发给浏览器；
- 对修改操作记录当前管理员 ID。

Python 服务只监听内网，或只接受带服务密钥的请求。

## 14. API 边界

Python 服务提供内部接口：

```text
POST /internal/radar/sources/{id}/crawl
GET  /internal/radar/runs
GET  /internal/radar/runs/{id}

GET   /internal/radar/documents
GET   /internal/radar/documents/{id}

GET   /internal/radar/evidence
PATCH /internal/radar/evidence/{id}
POST  /internal/radar/evidence/{id}/accept
POST  /internal/radar/evidence/{id}/reject
POST  /internal/radar/evidence/{id}/reprocess

GET    /internal/radar/clusters
POST   /internal/radar/clusters
GET    /internal/radar/clusters/{id}
POST   /internal/radar/clusters/merge
POST   /internal/radar/clusters/{id}/split
POST   /internal/radar/clusters/{id}/members
DELETE /internal/radar/clusters/{id}/members/{evidence_id}

POST  /internal/radar/clusters/{id}/opportunity
GET   /internal/radar/opportunities/{id}
PATCH /internal/radar/opportunities/{id}

GET  /internal/radar/links
POST /internal/radar/links
DELETE /internal/radar/links/{id}
```

长任务返回 `202 Accepted + run_id`，Next.js 轮询 `radar_runs`。

FastAPI 的 BackgroundTasks 适合小型响应后任务；重计算或多进程任务需要更完整的任务队列。第一版可以进程内执行，但必须持久化任务状态并能识别服务重启造成的中断。[FastAPI Background Tasks](https://fastapi.tiangolo.com/tutorial/background-tasks/)

## 15. 原网站中的管理页面

页面必须放入现有管理区域，并沿用当前站点视觉和权限。

### 15.1 `/radar/inbox`

目标：审核单条需求证据。

- 左侧：原始标题、来源、作者、时间和上下文；
- 中间：AI 字段编辑器；
- 右侧：相似需求簇和模型信息；
- 操作：接受、修改后接受、拒绝、重新提取；
- 筛选：主题、来源、状态、信号、日期；
- 支持键盘快捷键。

### 15.2 `/radar/clusters`

每个需求簇显示：

- 名称；
- 目标用户；
- 核心问题；
- 成员数量；
- 独立作者和来源数量；
- 7/30 天出现次数；
- 付费与迁移信号；
- 代表性原文；
- 当前评分。

支持合并、拆分、移除成员和新增人工证据。

### 15.3 `/radar/opportunities/[id]`

机会卡回答：

1. 谁遇到了问题？
2. 问题在哪个流程发生？
3. 当前怎样处理？
4. 为什么不满意？
5. 有多少独立证据？
6. 是否出现付费、购买或迁移信号？
7. 是否能够触达这些用户？
8. 准备验证什么假设？
9. 最小验证物是什么？
10. 什么结果意味着继续或放弃？

同时显示关联的文章、验证页、组件和开发任务。

## 16. 与文章、组件和 MCP 的连接

### 16.1 文章

机会卡可以关联：

- 需求观察文章；
- 技术实现记录；
- 验证结果；
- 最终复盘。

Radar 只能创建“生成文章草稿”的任务，不能绕过现有文章发布流程自动公开。

### 16.2 动态组件与模板

当某个需求进入验证阶段，可以关联：

- 问题验证组件；
- 交互原型；
- 数据展示组件；
- 页面模板。

组件仍由原有的预览、审核和版本机制保存，Radar 只保存引用。

### 16.3 MCP

第一版新增只读工具：

```text
radar_list_clusters
radar_get_cluster
radar_list_opportunities
radar_get_opportunity
radar_get_weekly_summary
```

第二阶段再增加有限写操作：

```text
radar_review_evidence
radar_update_opportunity
radar_create_validation_task
radar_link_article
```

不提供：

```text
radar_execute_sql
radar_run_arbitrary_crawler
radar_auto_publish_article
radar_auto_contact_users
```

这样以后可以在手机端询问：

> 最近一周有哪些重复出现、有行动信号，并且适合个人开发的问题？

MCP 返回审核后的需求簇及证据摘要，而不是未经处理的原始帖子。

## 17. 机会评分

自动统计：

- 独立作者数；
- 独立来源数；
- 最近 7/30 天次数；
- 明确购买、迁移和 workaround 数量；
- 最早和最近出现时间。

人工填写：

| 维度 | 权重 |
| --- | ---: |
| 痛苦程度 | 20% |
| 重复程度 | 15% |
| 付费迹象 | 20% |
| 现有方案缺口 | 15% |
| 用户可触达性 | 15% |
| 个人匹配度 | 15% |

每项 0～5 分，换算成 0～100。

以下任一情况可以否决：

- 数据取得不合法或不稳定；
- 必须先建立大规模社群；
- 需要双边市场；
- 属于高风险专业领域；
- 一个人无法长期维护；
- 目标用户无法再次触达；
- 只有抱怨，没有任何行动迹象。

## 18. 部署方式

在现有服务器上运行两个进程：

```text
next-web
radar-worker
```

如果当前使用 Docker Compose，新增 `radar-worker` 服务；如果使用进程管理器，则增加独立进程。

要求：

- Python 服务不绑定公网端口；
- Next.js 通过内网地址访问；
- 两个服务分别配置密钥；
- Python 服务设置 CPU 和内存限制；
- Playwright 单独限制并发；
- 数据库连接池设置较小上限；
- 日志包含统一 `run_id`。

如果原数据库是 SQLite：

- 开启 WAL；
- 缩短写事务；
- Worker 限制并发写；
- 避免 Next.js 和 Worker 长事务竞争；
- 定期备份数据库文件。

如果原数据库是 PostgreSQL 或 MySQL，直接使用原连接，但采用 Radar 专用账号。

## 19. 测试要求

### 19.1 不破坏原项目

必须验证：

- 原登录和文章功能不受影响；
- 非管理员无法访问 Radar 页面；
- 浏览器无法获得 Python 服务密钥；
- Python 账号无法修改非 `radar_*` 表；
- Radar 失败不会导致主站不可用；
- 原有构建、Lint 和测试继续通过。

### 19.2 后端

覆盖：

- GitHub Issue 转换；
- 文本标准化和哈希；
- 重复采集幂等；
- 增量游标提交；
- Pydantic 输出校验；
- 引用必须存在于原文；
- 人工审核结果不会被覆盖；
- 相似度建议；
- 合并与拆分事务；
- 任务状态恢复。

### 19.3 前端

覆盖：

- 管理员守卫；
- 审核和修改；
- 筛选、分页和轮询；
- 合并、拆分和撤销；
- 长原文展示；
- 关联文章和组件；
- Worker 不可用时的错误提示。

集成测试使用固定 Fixture，不调用真实 GitHub 或真实模型。重复运行完整流程两次，数据库记录不能翻倍。

## 20. 分阶段实施

### 阶段 0：接入审查

交付：

- 当前目录、数据库、ORM、认证、部署和 MCP 的审查结果；
- Radar 的实际文件落点；
- 数据迁移方案；
- 原项目风险清单。

验收：没有创建代码前，已经明确哪些能力复用、哪些模块新增。

### 阶段 1：采集纵向打通

交付：

- `radar_topics`、`radar_sources`、`radar_runs`、`radar_documents`；
- Python 服务骨架；
- `Collector` 协议；
- GitHub Issue Collector；
- 文本标准化、哈希和幂等写入；
- 手动运行命令；
- 单元与集成测试。

验收：同一批 Issue 执行两次不产生重复记录，主站功能不受影响。

### 阶段 2：AI 需求提取

交付：

- `radar_extractions`、`radar_evidence`；
- Pydantic Schema；
- Prompt 与版本机制；
- 原文引用校验；
- 批处理和失败重试。

验收：错误结构不能入库；每条证据可以跳回原文。

### 阶段 3：原站审核页面

交付：

- Next.js 内部代理；
- 管理员守卫；
- `/radar/inbox`；
- 修改、接受、拒绝和重新提取；
- 审计日志。

验收：能够连续审核 50 条证据，所有修改可以追踪到管理员。

### 阶段 4：相似需求与需求簇

交付：

- Embedding；
- Top-K 相似召回；
- 需求簇页面；
- 合并、拆分和撤销。

验收：使用人工标注的相似/不相似样本校准阈值，不出现未经确认的自动正式归类。

### 阶段 5：机会卡与原项目关联

交付：

- 评分；
- 机会卡；
- `radar_links`；
- 关联文章、验证页、组件和开发任务。

验收：生成 3 张有原文证据的机会卡，并能打开关联对象。

### 阶段 6：MCP 接入

交付：

- 只读工具；
- 权限检查；
- 有界结果与分页；
- 原文链接和证据摘要。

验收：通过手机端模型查询需求簇时，不需要传输全部原文，也不能绕过审核执行高风险操作。

## 21. 第一版完成定义

- 复用原网站登录、数据库和管理入口；
- Python Worker 与 Next.js 独立运行；
- 至少接通一个真实数据源；
- 保存 300 条以上可追溯原文；
- 提取 50 条以上需求证据；
- 至少人工审核 30 条；
- 形成 5 个以上需求簇；
- 形成 3 张机会卡；
- 选出 1 个验证问题；
- 至少关联一篇文章或一个验证页面；
- MCP 可以只读查询审核后的结果；
- 重复任务不会重复写入；
- 模型升级不会覆盖历史审核；
- Worker 故障不会影响主站阅读；
- 任意结论都能回到原始链接。

## 22. Codex 的第一条实施指令

后续不要直接把整篇文章交给 Codex 并要求“一次性完成”。第一条指令应当是：

> 阅读《在现有个人网站中实现“需求雷达”》技术方案。先审查当前仓库的 Next.js 结构、数据库、ORM、身份认证、管理页面、部署方式和 MCP 位置，输出 Radar 的实际接入方案与需要修改的文件清单。当前阶段不要新增依赖、不要创建数据表、不要写功能代码。

确认接入方案后，第二条指令才是：

> 根据已确认的接入方案实施阶段 1，只完成 Radar 基础数据模型、Python 服务骨架和 GitHub Issue 幂等采集。复用现有数据库迁移方式，不实施 AI 提取、Embedding、审核页面或 MCP 工具。完成后运行相关测试并报告仍未实现的阶段。

这样可以先让 Codex 理解原项目，再按阶段开发，避免它根据通用文章另外搭建一套网站、账号和数据库。

需求雷达最终应该成为原网站的一项内部能力：公开文章负责沉淀思考，验证页面负责接触真实用户，组件负责快速试验，MCP 负责让成熟大模型读取审核后的上下文，而 Python Worker 负责处理不适合放进主站请求生命周期的采集和分析任务。