# Rent Assist

> 国内租房全能助手，一切围绕用户的租房问题：小区综合了解（口碑/推荐/价格/房源，"天通苑怎么样""XX小区租房推荐吗"）、房东中介背景查询、住哪合适/选址建议（"在XX上班住哪、哪个性价比高"）、找房子/找个人转租/直租/合租房源（一居室/单间/次卧）、租房咨询（押金/合同/流程/中介费）、租房避坑踩坑。仅限租客侧长租问题：不含买房卖房投资、装修、搬家、酒店民宿短住。

- Skill: `daxueren666/rent-assist` (Agent Skill, multi-file: 5 files)
- Install (CLI): `npx skillmds@latest add daxueren666/rent-assist`
- Raw SKILL.md: https://api.skillmd.com/api/skills/daxueren666/rent-assist/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: daxueren666 (https://skillmd.com/u/daxueren666)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/daxueren666/rent-assist

---


# rent-assist 租房全能助手

**测试纪律**：联调/测试一律小量验证：批次编排 run_collect.py 用 `--target 5`，直跑单脚本用 `--limit 3-5`；正式查询才用正式量级。

**正式量级（用户 2026-08-15 定）**：总量目标由用户在采集前从菜单选择（见工作流 A 第 3 步：轻量 60 / 标准 150 / 深度 300，可自定义），由批次编排器自动累计：每批每平台 limit 20、自动多轮换词、`--min-per-platform 20` 保底；评论 `--top-comments 20` 且**按点赞数（最热）排序取**。单批 20 只是批次上限，绝不能当作正式查询的总量。

用户以自然语言提问，skill 先做**意图路由**，再走五条工作流之一：
A 小区综合了解 / B 推荐评价 / C 选址建议 / D 找转租房源 / E 租房咨询。
五条工作流最终都产出可视化报告：A/B/C/D 走采集→分析→报告；E 先在回复里直答，再出 FAQ 可视化报告（不采集，秒出）。

数据流：run_collect.py 批次编排采集脚本（opencli 小红书 / 豆瓣 HTTP / Exa+Jina / 抖音 MediaCrawler；12315 不进编排器单独跑）→ `<data>/raw/*.jsonl`
→ `clean.py` → `<data>/cleaned/*.json` → **Claude 只读 cleaned 做语义分析** → `<data>/analysis/<标的>.json`（按标的命名，防多标的互相覆盖）
→ `render.py` → `<data>/reports/*.html`。

诚实边界：只聚合公开口碑帖与转租帖；不爬贝壳/链家挂牌数据（反爬+判例风险），不做全网房源比价。报告仅为公开舆情聚合，不构成决策依据，无数据不等于安全。

## 第 0 步：依赖探测（采集类工作流第一步）

**采集类工作流（意图 A/B/C/D）先运行**；意图 E（纯咨询，不采集）直接作答，不跑（避免无谓的真实网络探测等待）：

```bash
python <skill>/scripts/check_deps.py
```

`<skill>` 指本 skill 目录。**路径占位符（全文通用）**：`<data>`=数据目录、`<tools>`=第三方工具目录（MediaCrawler/asr-venv/模型/playwright 内核/douban-profile 所在）、`<keys>`=密钥文件；统一解析规则：环境变量（RENT_ASSIST_DATA / RENT_ASSIST_TOOLS / RENT_ASSIST_KEYS）优先 → 开发机 `E:\租房\` 下对应路径（存在时）→ `~/.rent-assist/` 下对应路径；check_deps.py 首屏会打印三者实际解析结果（环境变量需在脚本进程启动前设置）。数据目录下 raw/cleaned/analysis/geo/reports/media、缓存 cache.db、批次进度账本 progress/（断点续采与 summary 都在这里）均在 `<data>` 下，不落 skill 目录。按输出处理：

- **未装 agent-reach**：向用户转述安装指引并**停止本次采集类工作流**（意图 E 可继续）：
  1. 从 GitHub 仓库 `github.com/Panniantong/Agent-Reach` 安装：`agent-reach install --system`。
  2. 警告：PyPI 上的同名包 "agent-reach" 是**冒名包**，禁止 `pip install agent-reach`。
- **小红书源不可用**：小红书需要 OpenCLI + Chrome 及 OpenCLI 扩展，并执行 `opencli xiaohongshu login` 在浏览器里登录一次。未就绪则跳过小红书源，在报告 `coverage.note` 中说明，不阻塞其他源。
- **Exa/Jina 等可选源缺失**：跳过对应源并说明。

## 输入预处理（意图路由之前）

用户提问常啰嗦、信息密集，路由前先从原话提取**五元组**：

| 字段 | 取值 |
|---|---|
| 标的 | 小区/公寓/中介/房东/片区名；没有则留空（意图 C/E 常见） |
| 城市 | 缺失则按工作流规则一次性问齐 |
| 标的类型 | 小区 / 中介 / 片区（超大片区照常采集，分析层按子区归类，见「超大片区」节） |
| 关注点 | 从原话提取：推荐 / 价格 / 二房东 / 转租 / 噪音 / 押金 / 靠谱 等 |
| 约束 | 预算 / 通勤 / 合租整租 / 时间窗——**仅取用户原话明说的，缺失不补不猜** |

提问啰嗦或信息密集时，提取后用一句话向用户复述确认（例："你是想了解北京天通苑的租房口碑，预算 3000、要合租，对吗？"），确认后再路由；问题本身简短清晰则直接路由，缺项按各工作流的问询规则补齐。后续查询词 = 标的 + 关注点场景词现场组合（正本见 references/queries.md）；约束不进搜索词，留给过滤层。

**零假设铁律（分析/报告/推荐全程适用）**：约束类信息（合租/单间/次卧/整租/户型/预算/通勤/电梯房等）**只使用用户明说的**。未提及的一律按"未指定"处理——分析和报告里不得出现"默认按合租单间考虑""适合合租人群"这类凭空推断（用户没说预算 2500 就该合租）。缺关键约束影响结论时问一句，绝不猜。

## 第 1 步：意图路由

对用户问题分类，判断规则按优先级从上到下：

| 意图 | 判断规则 | 示例问法 |
|---|---|---|
| A 小区综合了解 | 有明确标的（**小区/公寓/片区/板块/镇等任意居住地名**/房东/中介名），泛了解"它怎么样"或直接"XX 租房"：口碑、价格、房源、出租方，用户问什么就查什么 | "天通苑怎么样""天通苑租金多少钱""海淀永丰租房""永丰租房贵不贵""这家中介靠谱吗" |
| B 推荐评价 | 有明确标的 + 明确求推荐/值不值/对比的决策判断 | "天通苑租房推荐吗""XX公寓值得租吗""天通苑和回龙观选哪个" |
| C 选址建议 | **无明确意向居住地名**，只有工作地/预算/通勤要求，问住哪 | "在国贸上班预算3000住哪合适""望京上班哪个性价比高"；反例："海淀永丰租房"=用户点名了想住的地，走 A |
| D 找转租房源 | 求具体房源/转租/合租信息，常带位置或预算 | "XX附近有没有个人转租""2000以内合租有吗" |
| E 租房咨询 | 合同/押金/流程等知识性问题，无标的 | "押金一般押几付几""签合同注意什么""中介费谁出" |

- 判断不了就用一句话向用户澄清再路由。
- **A/C 分界铁律**：用户点名了想住的地名（哪怕是大片区/板块，如"海淀永丰租房"）→ 一律走 A，直接搜该地名，**不得生成候选片区**；只有"无意向地名 + 在哪上班/问住哪"才是 C。字面判不准（如"永丰上班预算2500"既可理解为查永丰本地、也可理解为找通勤范围）→ 一句话问用户，禁止猜。
- **零假设/零添加总纲**：用户说什么就拆什么、搜什么——约束（合租/单间/整租/预算/通勤/户型）只取用户原话明说的；搜索词只加 2-3 个通用评价词，不加用户没说的限定词（地铁站/线路/户型等）。**查询效果不佳时先换场景词补采，仍不佳才在最后向用户建议加词——建议是兜底，不是起手。**
- 复合问题（"住哪好+有没有转租"）拆成多次路由依次执行。
- 意图 B 为多标的对比（"A 和 B 哪个好"）时：逐个标的按工作流 A 完整采集分析，再合并对比给结论（见工作流 B）。
- 意图 A/B 输入为超大片区时照常走 A/B：搜索词=「片区名+场景词」（用户原话的片区名），**绝不枚举子小区拼搜索词**；子区差异由分析层从帖内信息归类呈现（见下方「超大片区」节）。
- 触发词参考：租房避坑/踩坑/口碑/推荐租吗/住哪合适/选址/转租/直租/合租/押金/合同/房东/中介。

## 超大片区（如天通苑/回龙观）

**铁律：搜索词只含用户原话里的名字**——用户打了"天通苑"就搜"天通苑 避坑""天通苑 怎么样"，绝不自己枚举"本一区/本三区/东区…"去逐个拼词采集（历史教训：LLM 拍的子区清单+逐区采集=自作聪明，已废除）。超大片区与普通小区走完全相同的管线，区别只在分析层：

- 问整体（"天通苑怎么样"）→ 按维度汇总，帖内出现的子区差异作为 findings 呈现。
- 问子区粒度（"哪个区合适""本区和东区比"）→ 分析层把帖内提及的各子区当对比项（B 模式逐条 pros/cons+证据；或 locate 模式 candidates），**数据里提到几个子区就写几个**，不预设清单。
- 子区粒度信息不足 → 明示"公开讨论未覆盖到子区层面，建议实地核验"，禁止编造。

## 工作流 A：小区综合了解（意图 B/C 复用）

1. **输入解析**：确定 `标的名 + 城市 + 标的类型`，并记下**用户的问题本身**（问的是推荐、价格、直租还是二房东，第 2 步和第 8 步都要用它）。
   - 标的类型默认 `小区`；名称含"地产/置业/房产/管家/公寓运营"或用户明说"这个中介/房东"则记为 `中介`。
   - 用户给的是**人名房东**：先反问可标识信息（地址/电话/挂房平台），并明示人名舆情检索的局限（同名多，证据仅作参考）。
   - **只有中介/房东类型才运行 collect_12315.py**，小区类型跳过 12315。
   - 城市缺失：能由唯一知名片区/地标推知就直接推知并在复述中确认；推不出或可能歧义（同城同名小区）才问用户，同一轮把缺失信息一次问齐。
   - 同一轮顺带问一句"上班地点/通勤目的地是哪？"（可选，用户可拒绝；仅当标的为小区/片区时问，中介/房东人名标的不问）：有答案留给地理层画通勤线（见地理层第 2 条），拒绝或没有就跳过 route，不追问、不阻塞采集。
2. **搜索词策略（动态生成，不预设；词库正本与平台传法见 references/queries.md §1）**：搜索词 = `小区名 + 场景词`，从用户的问题现场组合，生成的 3-5 组词拼进 run_collect 的 `--queries`（逗号分隔），分批与换词轮转由编排器自动执行。铁律：
   - 场景词**按意图选视角**：口碑类（意图 A/B/C）只用评价视角词，找房类（意图 D）只用供给侧词（两栏词库与组合示例见 queries.md §1）。
   - **"租房"泛词禁止单独作场景词**（求租帖混入的头号源头），要带评价后缀如"租房体验"；用户没提的角度只补 2-3 个通用评价词，禁止铺开维度词矩阵、不从避坑出发。
   - 中介/房东标的词（靠谱吗/套路/退费）、平台传参（豆瓣 --intent word|listing）、标的词形规范均见 queries.md；搜索层挡不住的求租帖由 clean.py 兜底剔除（双层防御）。
   - 豆瓣平台传裸标的词（平台词形差异见 queries.md §1.5）。
   - **零添加铁律：搜索词 = 用户原话拆出的标的 + 用户提到的场景词（+少量通用评价词）**。禁止擅自加入用户没说的限定词——地铁站名/地铁线路（如"16号线"）/小区开发商/户型/合租单间/预算数字一律不加。效果不佳（不足下限或不相关）先换通用场景词补采；仍不佳，**最后**才向用户建议（例："要不要加地铁站名缩小范围？"）——建议是兜底不是起手。
3. **量级确认（采集前必做）**：给用户三档菜单让其选择（可自定义数字）：
   - 轻量 **60 条 ≈ 20 分钟**（--target 60）
   - 标准 **150 条 ≈ 1 小时**（--target 150）
   - 深度 **300 条 ≈ 2 小时+**（--target 300）
   耗时依据实测约 2.5 条/分钟/平台（串行）；加 `--parallel` 四平台并行约减半。用户不选就默认**轻量档**（60 条约 20 分钟——采集成本承诺最小，数据不够按下限表规则补采，不起步就重采）。**前端衔接（主动拉起 + 队列直达闭环）**：量级确认前先探测门面页——`curl -s http://127.0.0.1:8770/ping` 返回 `"ok":true` 即服务已在跑（`alive` 只反映心跳，false 时仅补挂 Monitor，**不要重复启动**）；没起则后台以直达模式启动 `RENT_ASSIST_INBOX=1 python <skill>/scripts/serve_home.py`（run_in_background，电脑浏览器自动弹出），**并挂 Monitor 接收队列**（touch `<data>/inbox/heartbeat` 起 5 秒心跳循环 + `tail -n 0 -f <data>/inbox/queue.jsonl`，队列每行即一条页面提交的命令，按本 skill 工作流处理）。把启动横幅里「手机访问(需同一WiFi): http://<局域网IP>:8770/」转述给用户。闭环保证：页面提交 → 本会话接收处理 → 报告落 `<data>/reports/` → serve_home 盯到新报告**自动在原页面弹出**（电脑/手机同享）。页面提交自带【量级：X】标注（X 含档名与条数），Claude 不再重复追问量级；**勿重复启动**（8770 双实例会抢占端口）。
4. **批次采集（一条 run_collect 命令，编排器自动多轮累计；脚本内部请求间隔随机、批间休眠 10-30s；评论一律按点赞最热排序取 top；时间窗与排序按下方硬规则传参）**：

   ```bash
   python <skill>/scripts/run_collect.py --target 300 --parallel --queries "天通苑住过,天通苑避坑,天通苑二房东" --platforms "xhs,douban,web,douyin" --days 180 --sort "discussion,hot" --min-per-platform 20 --douban-intent word
   ```

   - **语义**：外层遍历查询词、内层遍历平台，每个（词 × 平台）组合一批（每批每平台 limit 20），自动累计到 `--target` 总量（`--per-platform N` 可直接定每平台上限）；**剩余配额不足一批时自动以剩余量作本批 --limit（配额即钳位，不整页超采）**；断点续采：中断后重跑自动跳过已完成组合（进度账本 <data>/progress/<slug>.json，done 超 7 天自动失效）。
   - **7 天复用闸门（代码强制）**：某（平台×查询词）组合近 7 天已采 ≥10 条 → 自动跳过并在 `[复用]` 行打印明细；`--refresh` 强制全部重采（--parallel 下会透传给各平台 worker）。`--parallel` 下各 worker 的 auth_state 写隔离目录、不回写主 state（只影响展示层，不影响采集）。
   - **停止规则（按平台）**：达到 target 配额即停；连续 2 批新增率 <10% 或连续 3 批失败则停该平台；查询词耗尽仍未达 `--min-per-platform` 时依次启用 `--extra-queries "补位词1,补位词2"` 续采。
   - **退出码**：0=正常结束（允许部分批次失败）；3=子脚本需登录，整体中止，先跑 `python <skill>/scripts/ensure_auth.py` 再重跑；130=用户中断。子脚本退出 4=该源数据质量差，记 degraded 继续并按降级处理；2 及其他=失败继续，重跑会重试。
   - 12315 不进编排器：中介/房东标的另跑 `python <skill>/scripts/collect_12315.py --query "XX地产" --limit 20 --days 180`。
   - 视频笔记与图文同样采集（collect_xhs 已在 extra.note_type 标记 video/image/unknown）：视频帖的标题+简介就是文字信号，直接可用；抖音口播转写见第 7 步。
   - 轻量场景（意图 C 等）可直跑单脚本（collect_xhs/douban/web/douyin），传参同矩阵。
   - 同标的 **7 天内已采集** → run_collect 启动时自动识别复用（`[复用]` 明细，`--refresh` 强制重采）；cleaned/analysis/report 产物同样直接沿用，除非用户要求重跑。

   **采集后健康检查（必做）**：读 run_collect stdout 末尾 `[汇总]` 行（总量、当日去重新增、summary 与账本路径），必要时读 progress/<slug>.summary.json 里各平台条数与失败/降级组合；并核对 cleaned 数据中 `note_fetch_failed` / `comments_fetch_failed` 标记占比：任一占比 ≥50% → 视为该源本轮失败，按降级规则处理（跳过该源、在 `coverage.note` 说明），不基于残缺数据下结论。
5. **清洗**：`python <skill>/scripts/clean.py --query <标的>` → 产出 `<data>/cleaned/<标的>.json`（去重、广告过滤、求租帖剔除、8 类风险粗分类、房源帖识别、评论取 top）。
6. **媒体处理（用户 2026-08-15 定：小红书不读图）**：xhs 以正文+评论为主，**不默认下载图片识别**；fetch_media.py 保留为可选工具，仅在用户点名"看看帖子里的图"时使用（`--note-ids "id1,id2"` 或完整 URL）。
7. **抖音视频口播转写（默认执行，抖音源的核心价值在口播内容）**：主批次（含 `--parallel`）跑完后，**默认追加一条独立小量命令**单独跑（`--get-video` 与 `--parallel` 互斥，必须后置单跑）：

   ```bash
   python <skill>/scripts/collect_douyin.py --query "<主查询词>" --limit 5 --get-video 3 --top-comments 10 --days 180 --sort discussion,hot
   ```

   （N≤3，对讨论 top 视频帖：MediaCrawler 下载→ffmpeg 抽音频→sherpa-onnx+SenseVoice 本地转写→文本以"【口播转写】"并入该帖 content，extra.asr=true）。**带宽铁律：limit 必须小**——MediaCrawler 开视频后会下载搜索页全部结果，`--get-video` 务必配 `--limit 3` 级别控带宽。用户明说"不要转写/不用视频"才跳过。频控口径不变（≤3 帖）。运行时装于 <tools>/asr-venv + models（缺依赖时脚本 exit 3 给指引）；手动转写单文件用 `python <skill>/scripts/asr.py --video <文件>`。
8. **语义分析（Claude 亲自做，只读 <data>/cleaned/*.json）**：首要任务是**回答用户提出的那个问题**：用户问推荐就给推荐结论，问价格就汇总价格锚点，问二房东就查二房东证据。references/risk-signals.md 的风险八维只作为组织发现的分类词汇表（复核 clean.py 粗分类、剔除否定语境误命中如"从来没漏过水"），不是分析的目的。按下方 schema 写出 `<data>/analysis/<标的>.json`（按标的命名，防多标的互相覆盖；schema 不变）。
9. **渲染**：`python <skill>/scripts/render.py --analysis <data>/analysis/<标的>.json --cleaned <data>/cleaned/<标的>.json`（启用地理层时再加 `--geo <data>/geo/<标的>.json`）→ `<data>/reports/<标的>_<YYYYMMDD>.html`。最后告知用户报告绝对路径，并给一句话结论。

**数据稀疏降级（报告照出）**：cleaned 有效帖总量 <3 条 → 仍写 analysis.json 走 render 出轻量报告（coverage 如实记低量；findings/candidates 只放有证据的，没有就不写；结论按"数据不足"表述），报告数据说明卡明示"无数据≠安全，公开舆情未覆盖不代表无风险，建议实地核验"并附 checklist 对应阶段要点。**交付层不变：报告必出**——冒烟实测旧版降级不出报告时，前端页面无任何回执，提交者分不清"跑完但没数据"还是"卡死"（问题记录_v3 #4）。

## 工作流 B：推荐评价（意图 B）

1. 完整执行工作流 A 第 1-9 步，但 `mode = "recommend"`。多标的对比输入（"A 和 B 哪个好"）：逐个标的走完 A 的采集与清洗（各自 query），再写一份对比 analysis（findings/candidates 分标的），verdict 给对比结论。
2. 客观维度按用户问的来：用户提了交通/通勤才评估交通配套（依据帖子中的地铁/商圈信息，标注"未实地验证"），没提就不自加维度（零假设铁律同样适用于评估维度）。
3. `verdict` 字段必须是一句话结论，**只在用户明说了约束时附适用人群**（例：用户说了"通勤优先"才写"通勤优先者可考虑"），没说约束就只给结论不给人群画像，不给绝对化承诺。
4. positive 数组放真实好评证据引用，suggestions 放"租前需当面核验的事项"。

## 工作流 C：选址建议（意图 C）

**设计原则：候选片区由数据归纳，不是 LLM 生成。** 平台上"XX 上班住哪合适"本身就是大量真实讨论帖——直接搜用户的问法，数据里被推荐的片区就是候选，每条带原帖证据；用户通勤范围大，数据自然覆盖更多片区，不预设数量也不硬凑。

1. **信息确认**：工作地（地标/地铁站）、预算、可接受通勤时长、其他要求（合租/整租、电梯房等），缺则一次问齐；用户拒绝答或说"随便"就按未指定继续，不阻塞。
2. **搜索词 = 用户问法 + 分平台词形（不生成候选、不预设范围）**。原则（问题记录_v3 #1 定调）：**格式转换 ≠ 自作主张**——把用户的人话拆成关键词是翻译，红线是改意思（加没说的限定词、换标的、生成片区）。2-3 组词里问法词与关键词组混搭，各平台各取所需：
   - 问法词组（web 语义搜索整句直接懂；xhs/douyin 搜索半语义、问法帖是真实体裁）：`{工作地}上班住哪`、`{工作地} 通勤 租房`
   - 关键词组（关键词平台的正路， douban 裸词见 references/queries.md §1.5）：`{城市前缀}{工作地} 租房推荐`、`{工作地} 怎么样`
   - 一条编排直接采：`python <skill>/scripts/run_collect.py --target 60 --queries "{工作地}上班住哪,{城市}{工作地}租房推荐" --platforms "xhs,web,douyin,douban" --days 180 --sort "discussion,hot"`（量级按第 3 步菜单定，示例为轻量档；不跑 12315）。豆瓣拿到问法词 0 命中、xhs/douyin 问法词 0 命中时，脚本自愈会自动剥问法/场景词降级成工作地裸词重搜一次（lexicon.SCENARIO_WORDS，格式转换层）。**命中仍不足**（关键词平台 0-1 条）→ 换另一词形补采一轮（问法词↔关键词组互换）→ 仍不足按数据稀疏出说明报告，建议兜底再向用户提议加词。用户输入已含意向地名的照 A/C 分界走 A。
3. **候选片区从清洗后数据归纳**：Claude 统计帖子里实际被推荐/讨论的居住片区，candidates 每条必带 evidence（引文+原帖链接），被推荐频次高、口碑一致者优先；数据里有几个写几个（如"中关村 2 小时通勤"数据可能覆盖全城，就照数据写）。
4. **通勤分析**：对数据归纳出的候选做通勤测算（起点=候选片区、终点=用户工作地，用户给了工作地才跑）；M3 之前文本估算（标注"文本估算"），之后 `geocode.py` route 实测。通勤信息只进报告的通勤区块，**绝不进平台搜索词**。
5. 汇总写 `mode = "locate"` 的 analysis.json（candidates：name/pros/cons/commute/evidence + 口碑风险摘要）→ render → 输出数据归纳的片区排名 + 提醒实地看房。

## 工作流 D：找转租房源（意图 D）

1. **信息确认**：位置（小区/地铁/片区）、预算、整租或合租、入住时间，缺则问。
2. **采集**：搜索词按动态规则组合 = 位置词 + `转租` / `直租` / `合租` / `个人转租` / `无中介`（用户问直租就优先"房东直租"），2-3 组拼进一条编排命令（豆瓣 cat=1013 租房小组为主源 + 小红书，web 源可选加进 --platforms）：

   ```bash
   python <skill>/scripts/run_collect.py --target 60 --queries "天通苑转租,天通苑个人转租,天通苑房东直租" --platforms "douban,xhs" --days 7 --sort "time" --min-per-platform 10
   ```

   评论深度沿用 run_collect 透传的 `--top-comments` 默认值（20）即可；时间窗与排序只有意图 D 用 time + 近 7 天。
3. **过滤**：`clean.py` 的 `is_listing` 结果只保留房源帖；再按预算/位置/发布时间筛选，**按发布时间最新在前排序**，超过 7 天的转租帖基本失效，只作参考且必须标注发布时间。
4. 写 `mode = "listings"` 的 analysis.json（listings 数组：标题/链接/价格提示/户型/平台/发布时间）→ render → 输出列表 + 防骗提示（引用 references/checklist.md 看房前阶段与虚假房源风险类）。
5. 提醒：只做聚合展示，不核验房源真实性，联系前先按 checklist 核身。

## 工作流 E：租房咨询（意图 E）

不采集任何数据、不跑清洗，两步走（先直答、再出报告——报告是固定交付物，必出）：

1. **直接答**：读 `references/checklist.md`，定位与问题对应的阶段小节，结合问题具体作答（可引用清单条目），答案直接写在回复里，不让用户等报告。
2. **FAQ 可视化报告**：把答案整理成 `mode = "faq"` 的 analysis.json，跑 render.py 出报告落 `<data>/reports/`：
   - `question` 用用户问题原话；`sections` 2-4 个分节、每节 3-6 要点；`risk_notes` 风险提示；`action_checklist` 可勾选行动清单。
   - 不传 `--cleaned`/`--geo`，秒出。
   - **诚实边界**：faq 报告基于通用知识与会话材料（checklist 等），无平台数据支撑，报告中如实标注。
   - 若用户顺带提到具体标的，提示"可对该标的跑意图 A 尽调"。

## 地理层（M3）

意图 A/B/C（尽调/推荐/选址）且**标的可定位**（小区/片区名能 geocode 出坐标）时，在写完 analysis.json 之后、render 之前追加地理层；意图 D/E 不跑。标的为中介/房东（无固定坐标）或 geocode 无结果时跳过，报告自动显示"地图数据未采集"。

1. **采集**（geocode.py 全量走 cache.db 缓存，同参数复跑 0 新请求；测试纪律同样适用）：

   ```bash
   python <skill>/scripts/geocode.py geocode "天通苑" --city 北京            # 定位
   python <skill>/scripts/geocode.py around "116.42,40.07" --city 北京     # 周边 8 组配套各取最近 3
   python <skill>/scripts/geocode.py around "116.42,40.07" --city 北京 --custom "健身房"   # 按需自定义关键词，取最近 5
   python <skill>/scripts/geocode.py route "天通苑地铁站" "国贸地铁站" --city 北京      # 通勤测算（--mode transit|walking|riding），仅当用户提供了通勤目的地
   ```

   around 的 target 直接用 geocode 返回的 location；route 的起点=标的、终点=用户提供的通勤目的地（用户没给就不跑 route）。**起终点地名后缀『地铁站』更稳**：高德对裸小片区名常解析失败（报 30001，如"马连洼"会被误解析成"西苑"），加"地铁站"后缀即通；geocode 定位同理。**该后缀只用于 geocode/route 命令参数，禁止进入任何平台搜索词**（搜索词只用用户原话的地名）。noise 命令保留但默认不跑：仅用户主动问噪音时执行 `python <skill>/scripts/geocode.py noise <坐标> --city <城市>`。
2. **主动问通勤（交互）**：意图 A/B 在输入解析时顺带问一句"上班地点/通勤目的地是哪？"（用户可拒绝；仅当标的为小区/片区时问，中介/房东人名标的不问）。用户给了 → 跑 route（起点=标的、终点=该地点），geo.json 写 route 段，模板自动画通勤虚线+摘要；拒绝或没有 → 跳过 route，不追问。报告出来后用户补充工作地也一样补跑。
3. **按需周边查询（交互）**：用户问"附近有没有大超市/医院/健身房"这类配套 → 直接跑 `python <skill>/scripts/geocode.py around <坐标> --city <城市> --custom "超市"`（--custom 单独给定时只查该关键词不查预置 8 组，省配额；逗号可分隔多个）→ **先用文字直接回答**（名称+距离，取最近 5），同时把返回的 group（group 名=关键词）追加进 geo.json 的 around.groups（见第 5 条），重跑 render 后地图上以灰点可视化。
4. **组装** `<data>/geo/<标的>.json`：四段输出拼成 `{"geocode":{location,formatted_address,city},"around":{"radius_m":1500,"groups":[{group,pois:[{name,distance_m,address,location}]}]},"noise":{"total":0,"hits":[{type,name,distance_m,address,location}]},"route":{mode,summary,distance_m,duration_min,transfers,origin_location,dest_location}}`，可参照 `<data>/geo/` 下已有 geo 文件（如有）。
5. **groups 运行时追加**：同一标的的多次 around 结果（预置组/自定义组）**合并写回同一个 geo.json**——新 group 直接 append 进 around.groups，同名 group 用新结果覆盖；追加后只重跑 render，不重采 geocode/noise/route 段。
6. **渲染**：`python <skill>/scripts/render.py --analysis <data>/analysis/<标的>.json --cleaned <data>/cleaned/<标的>.json --geo <data>/geo/<标的>.json`。模板渲染地图块（小区强调色 marker、配套灰点；仅当 geo.json 带 noise 段才画噪音红点，仅当 coverage.note 标了「通勤目的地：」才画通勤虚线）+ 断网可读的文字层（配套分组网格；通勤摘要仅当用户提供了目的地）。自定义组与预置组同构（group=关键词、pois 同字段），模板按组名直接显示，无需改动。
7. **密钥**：`<keys>` 的 `AMAP_JSAPI_KEY`（进模板加载地图，客户端可见属正常，靠高德控制台域名白名单保护）与 `AMAP_JSAPI_SECRET`（securityJsCode）。`AMAP_JSAPI_SECRET` 待填时地图自动降级为文字版，填好后无需改模板。`AMAP_WEB_KEY` 只供 geocode.py 本地调用，**绝不进报告**。

## analysis.json schema（Claude 按此产出，render.py 消费）

```json
{"mode":"diligence|recommend|locate|listings|faq",
 "faq 模式专有":{"question":"用户问题原话","sections":[{"title":"","points":[""]}],"risk_notes":[""],"action_checklist":[""]},
 "target":{"name":"","city":"","type":"小区|中介|片区"},
 "coverage":{"xhs":0,"douban":0,"complaint12315":0,"web":0,"note":""},
 "overall_score":0,"risk_level":"低|中|高|数据不足",
 "verdict":"recommend/locate 必填：一句话结论（约束仅取用户原话，未明说不附人群画像）",
 "dimensions":[{"name":"房屋质量","score":0,"reason":"","evidence_idx":[]}],
 "findings":[{"risk":"","severity":"高|中|低","confidence":"高|中|低","summary":"","evidence":[{"title":"","url":"","platform":"","quote":""}]}],
 "listings":[{"title":"","url":"","price_hint":"","room_hint":"","platform":"","published_at":""}],
 "candidates":[{"name":"","pros":"", "cons":"","commute":"","evidence":""}],
 "positive":[""],"suggestions":[""],
 "disclaimer":"无数据≠安全，本报告仅为公开舆情聚合"}
```

字段约定：

- `dimensions` 固定 8 项（与 references/risk-signals.md 分类法一一对应，口径统一便于跨报告对比）：
  房屋质量 / 房东中介 / 押金费用 / 虚假房源 / 噪音 / 物业 / 治安 / 通勤配套。
  **数组顺序即展示顺序：用户问题点名的关注维排最前，其余按默认序，无数据维度（score 0 且 evidence_idx 空）沉底**——报告模板按数组顺序渲染，问"二房东多吗"时房东中介维置顶，不让空维度占卡抢视线。
  score 为 0-100；每维必带 `reason`（一句话中文归因，如"通勤配套 22：距地铁 1.5km+ 公交线少"）；无数据的维度 score 置 0 且 evidence_idx 留空。
- `overall_score` 为 8 维加权（押金费用/房东中介/虚假房源权重更高）；`risk_level` 依据 findings 最高 severity 与数据量综合。
- `findings.evidence.quote` 摘原文不超过 100 字；`evidence_idx` 指向 cleaned JSON 中帖子的下标。
- `coverage` 记各平台清洗后有效帖数；降级/跳源的写进 `note`。
- `verdict` 仅 recommend/locate 模式必填，且**不得包含用户未明说的约束假设**（用户没提合租就不写"适合合租单间人群"，按用户实际提及或"未指定"表述）；`listings`/`candidates` 分别为意图 D/C 必填；`candidates` 每条带 `evidence`（一句话来源线索）。
- `positive` 为好评精选 8-10 条（不足时如实少给，禁止编造）；`suggestions` 3-8 条；均需有 cleaned 数据或 checklist 依据。
- faq 模式（意图 E）不采集不清洗：只填 question/sections/risk_notes/action_checklist（见工作流 E），coverage 等字段留空；无标的的 FAQ 可**整体省略 `target`**（或 type 填"咨询"），渲染层已容错。
- **禁止捏造通勤**：仅当用户明确给出上班地/通勤目的地才做通勤分析与地图画线，绝不假设、绝不替用户编工作地。用户提供了就在 coverage.note 写明「通勤目的地：X」，报告模板据此才渲染通勤块并画线；未提供时 geo.json 里残留的 route 数据不展示、不参与结论。
- **默认不做周边噪音查询**：地理层默认只跑 geocode + around 配套查询；noise 仅用户主动问噪音时单独跑（geocode.py 的 noise 能力保留），geo.json 默认不含 noise 段，报告默认无噪音区块。
- **报告内容矩阵（报告是全屏卡片左右翻页；分析层按此组织字段，第一卡永远是用户问题的直接答案）**：
  - A 尽调：卡1 结论(overall_score/risk_level/verdict 要点摘要)+八维(用户关注维前置、无数据维沉底) → 卡2 关键发现(按 severity 排、每条带 evidence 可点溯源) → 卡3 房源/价格 → 卡4 好评精选 → 末卡 数据说明(coverage+方法+免责，紧凑)
  - B 推荐：卡1 大字 verdict+评分（适合人群仅当用户明说约束时出现）→ 卡2 正反理由+证据 → 卡3 价格与房源 → 卡4 配套(仅当用户给了通勤地或主动问配套才有) → 末卡 数据说明
  - C 选址：卡1 数据归纳的候选片区排名（数量由数据定，每条带证据）+一句话理由 → 卡2 片区对比(价格/通勤/口碑 对齐) → 卡3 各区代表小区+口碑 → 卡4 地图 → 末卡
  - D 找房源：卡1「近7天 N 条、预算内 M 条」→ 卡2 房源卡流(整卡可点开原帖，按新鲜度) → 卡3 价格分布 → 末卡
  - E FAQ：问题原话 → 分节要点(2-4 节、每节 3-6 条) → 风险提示 → 可勾选行动清单（无数据说明卡，明示基于通用知识与会话材料）
  - A/B 输入为超大片区时报告照 A/B 卡序，子区对比内容并入卡 2（数据里提到几个子区写几个）
  - 溯源样式=「平台 · 标题(链接) · 发布日期」，不许出现 tag/chip 标签堆与裸长 URL；无数据的卡不生成，不放空壳区块。

## 排序组合矩阵（时间窗 × 排序，铁律摘要；完整正本见 references/queries.md §2）

1. 排序键三种（collect_xhs/douban/web/douyin 与 run_collect 均支持，可逗号组合成多队列、两条队列都爬）：`discussion`（评论最多）、`hot`（点赞最热）、`time`（发布时间最新在前）。
2. **只有意图 D 用 time + 近 7 天**（超 7 天转租帖基本失效，只作参考且必须标注发布时间）；口碑/讨论类（意图 A/B/C 一律）`--days 180 --sort discussion,hot` 双队列；争议热点（近期风波、舆情发酵中）改近 7 天、排序不变。
3. 用户明确指定时间范围或排序口径时按用户的组合传参（例："最近一个月讨论最多的" → `--days 30 --sort discussion`）。
4. 讨论类评论必抓（`--top-comments` 按最热排序取），选证据优先看高评论数帖，低互动帖不作为主证据（评论优先原则见 queries.md §3）。

## 爬取下限表（正式查询最低采集量）

按 clean 后各源有效帖数计（与 coverage 同口径）：

| 意图 | 各源下限 |
|---|---|
| A/B | xhs ≥20、douban ≥10、web ≥10、douyin ≥15 |
| C | 按问法采集后总有效帖 ≥15（轻量档 60 通常足够）；不足先换问法补采，仍不足在 coverage.note 说明并降 confidence |
| D | douban ≥10、xhs ≥10 |

不达标处置（依次）：

1. 换场景词补采：重跑 run_collect 并加 `--extra-queries "新词1,新词2"`（仍守频控红线：每批每平台 limit 20、批间随机间隔 10-30s，由编排器执行）。
2. 补采后仍不足：在报告 `coverage.note` 明示"数据量不足"，对应 findings 的 confidence 降一档，`risk_level` 倾向给"数据不足"。
3. 禁止硬凑：不拿广告帖、无关帖、重复帖充数，不虚报 coverage。

## 时效规则（老帖降权，铁律）

2017 年的证据不能和 2026 年的同权重，分析时必须按发布时间降权：

1. `clean.py` 会给发布时间 **>24 个月**的帖子打 `old = true` 标记，并在输出中给出时间分布统计（近 1 年 / 1-2 年 / 更早 三档计数）。
2. LLM 分析铁律：
   - `old = true` 的证据，其 findings 的 `confidence` 自动**降一档**（高→中、中→低）。
   - 发布时间 **>48 个月**的证据只作背景参考，不进 findings 主证据链。
   - findings 里确需引用老证据时，必须在 summary 中标注"较早证据（YYYY年）"。
3. `coverage.note` 里必须写一句时间分布（例："时间分布：近1年 12 条 / 1-2年 5 条 / 更早 3 条"）。
4. 同一问题新旧证据冲突时，**近期证据优先**，并在 summary 中说明变化（例："早年投诉较多，近一年未见同类反馈"）。

## 脚本与数据约定

| 脚本 | CLI | 输出 |
|---|---|---|
| check_deps.py | 无参数 | 退出码 0=就绪；缺依赖打印安装指引后退出 |
| run_collect.py | `--per-platform N`（每平台上限，推荐，消除总量歧义）或 `--target`（总量停止线）；`--queries "Q1,Q2" --platforms "xhs,douban,web,douyin" --days --sort "discussion,hot" --min-per-platform [--extra-queries --batch-limit --top-comments 20 --parallel --douban-intent word\|listing --refresh --reuse-min N]`。`--parallel` 分平台并行子进程（各平台独立账本/日志，默认串行）；7 天复用闸门自动跳过已采组合，`--refresh` 强制重采 | <data>/raw/*.jsonl + progress/<slug>.summary.json |
| collect_xhs.py | `--query --limit --top-comments --days --sort discussion\|hot\|time（可逗号组合）`；note/comments 失败自动重试 1 次 | <data>/raw/xhs_<标的slug>_<日期>.jsonl |
| collect_douyin.py | `--query --limit --top-comments --days --sort discussion\|hot\|time（可逗号组合） --get-video N`（N≤3 下载 top 视频并口播转写，须配小 limit） | <data>/raw/douyin_*.jsonl |
| collect_douban.py | `--query --limit --top-comments --days --sort --intent word\|listing`（口碑=word 裸搜评价词；找房=listing 拼租赁词）`--rental-words --group` | <data>/raw/douban_*.jsonl |
| collect_web.py | `--query（可传多次） --limit --days --sort discussion\|hot\|time（可逗号组合）`；Exa 失败自动重试、Jina 失败降级直连再降级摘要 | <data>/raw/web_*.jsonl |
| collect_12315.py | `--query --limit --days` | <data>/raw/complaint12315_*.jsonl |
| ensure_auth.py | `--platform xhs\|douban\|douyin\|all` | 子脚本退出码 3 后先跑它恢复登录态（douban 弹浏览器轮询等扫码，绝不秒退） |
| fetch_media.py | `--note-ids "id1,id2"`（自动回 raw 查签名 URL）或直接传完整 URL；`--max-images-per-note 5` | <data>/media/<note_id>/（图片已压缩，供 Claude 视觉读图） |
| asr.py | `--video <文件>` 或 `--video-dir <目录>`（sherpa-onnx+SenseVoice int8，已装 <tools>/asr-venv+models；缺依赖 exit 3 带指引） | 同名 .txt 转写文本（自动 ffmpeg 抽 16k wav） |
| clean.py | `--query <标的>`（必填）；可选 `--raw-dir --out --aliases --city --max-age-months --no-entity-filter --keep-seek`（`--city <城市>` 城市消歧：黑名单 72 城+北京信号白名单，剔外地同名小区帖，meta 计 city_mismatch/city_kept_signal；默认剔除求租帖并计 meta.seek_posts） | <data>/cleaned/<标的>.json |
| render.py | `--analysis <data>/analysis/<标的>.json [--cleaned <data>/cleaned/<标的>.json] [--geo <data>/geo/<标的>.json] [--out]` | <data>/reports/<标的>_<日期>.html |
| serve_home.py | 门面页本地服务（8770，默认绑 0.0.0.0 手机同 WiFi 可访问，RENT_ASSIST_BIND=127.0.0.1 可仅本机）。无参数直接跑；测试开关 RENT_ASSIST_PORT / RENT_ASSIST_NO_BROWSER / RENT_ASSIST_LAUNCH_CMD | 浏览器自动开门面页；输入需求拉终端跑 claude；盯 reports 新报告自动打开 |
| test_render_check.py | 无参数 | 报告模板数据完整性自检（改模板前后必跑） |
| test_*_offline.py（asr/auth/clean/intent/media/reuse/web 共 7 个） | 无参数 | 对应脚本的离线自检（改动对应脚本后运行该测试） |

### 门面页与本地入口（2026-08-15 新增）

- **用户入口 = 双击启动 bat（开发机为 `E:\租房\启动租房助手.bat`；他机自建 bat 跑 `python <skill>/scripts/serve_home.py`，可用 RENT_ASSIST_WORK_DIR 指定 claude 工作目录）**：起本地服务（8770；默认绑 0.0.0.0，手机同 WiFi 可访问，设 RENT_ASSIST_BIND=127.0.0.1 可仅本机）并自动打开浏览器门面页（templates/landing.html，蓝白风+SVG楼群动画，手机/桌面双端适配）。
- 门面页输入任意租房需求 → 点"开始查询" → 服务拉起新终端窗口跑 `claude "<需求>"`（在项目目录内跑，权限生效；量级菜单等交互在该终端里完成）→ 服务每 5s 盯 data/reports，新报告生成后页面自动弹出。
- **历史报告与报告列表**：门面页有"历史报告"入口，或直接访问 `http://<主机>:8770/reports/`（索引页，mtime 倒序）；inbox 收到非租房输入时用 `scripts/out_of_scope.py` 生成范围外说明回传手机。
- 没起服务时直接双击打开 landing.html 会自动降级为"复制提问"模式（零后端可用）。**会话衔接**：Claude 量级确认前**主动拉起门面页并挂队列接收**（探测→后台启动→Monitor 接收→报告原页自动弹出，完整规则见工作流 A 第 3 步「前端衔接」）；页面提交的文本自带"【量级：X】"标注，Claude 据此直接定档，不再重复追问。
- **报告模板改版纪律**：改 templates/report.html.j2 前先跑 `python <skill>/scripts/test_render_check.py` 记基线，改完再跑（不变量：新模板不得丢失旧模板已展示字段）；视觉验证用 Playwright 375/1280 截图。

raw jsonl 每行 schema：`{"platform","query","collected_at","url","title","content"(≤2000字),"author","published_at","likes","comments_count","comments":[{"text","likes","author"}],"extra":{}}`（xhs 源的 extra 含 `note_type: video|image|unknown`）。

### 抖音源（MediaCrawler）

- 位置：`<tools>/MediaCrawler`（本地部署，要求 Python ≥3.11 与 Node.js ≥16，用其 `.venv` 运行）。
- **首次使用需扫码登录**：collect_douyin.py 会拉起 MediaCrawler 浏览器窗口，用抖音 App 扫码一次，登录态缓存在 MediaCrawler/browser_data/，之后免扫。缺 venv/登录失效时脚本会在 stderr 给重建指引并 exit 2，跳过该源不阻塞其他源。
- 用法：`python <skill>/scripts/collect_douyin.py --query "天通苑住过" --limit 20 --top-comments 10 --days 180 --sort discussion,hot`（经 run_collect 编排时 --platforms 加 douyin 即可）。query **原样透传**（组合规则见 references/queries.md，评价词/供给侧词按意图选），抖音标题带小区名比例低、信号在正文+评论，结果侧不过滤交给 clean.py。要口播转写加 `--get-video N`（N≤3，务必配小 limit——MediaCrawler 开视频会下载搜索页全部结果；与 --parallel 互斥）。
- **测试纪律同样适用**：联调一律 `--limit 3-5`；频控与 7 天缓存规则与其他源一致。
- Playwright 内核集中安装：`PLAYWRIGHT_BROWSERS_PATH=<tools>/playwright-browsers`（不设则用 playwright 默认位置）。collect_douyin.py 启动 MediaCrawler 时已自动注入，手动跑 MediaCrawler 时需自己 export。

## Token 纪律（铁律）

1. **永不读取 <data>/raw/*.jsonl**，原始数据只供脚本消费。
2. 采集/编排脚本 stdout **只看末尾统计行**（run_collect 读 `[汇总]` 行，单脚本读摘要行），不读全文、不把输出重定向进上下文。
3. 评论只看 clean.py 截取后的 top 评论，不看全量评论（小红书已不默认读图，用户点名时 fetch_media 每帖 ≤5 张）。
4. 报告一律由 render.py 渲染，Claude 不写 HTML/CSS。
5. evidence 引文每条 ≤100 字，同帖多句合并。

## 频控与缓存（红线）

- 采集以批次为单位：每批每平台 ≤20 帖，run_collect 自动多批累计到 target（正式建议 300、测试 --target 5），批间随机间隔 10-30s（脚本内实现，不并发轰炸单平台）。
- 评论按点赞（最热）排序取 top，正式 --top-comments 20、测试 5。
- 同标的 7 天内复用 <data>/raw 已有数据，不重复采集。
- 定位为个人低频自用，禁止批量爬取、禁止并发多开、禁止绕过平台限制。

## 第三方工具排障原则

- 用别人的工具/skill（opencli/agent-reach、MediaCrawler、mcporter/Exa、r.jina.ai、sherpa-onnx）报错时：**先查源头再动手**——原仓库 issue、官方文档、联网搜报错原文，定位根因后再修；不许只在自己代码里绕过。绕过式修复必须在输出里说明根因与绕过理由。
- 第三方配置的"拼写错误"照抄别修正（例：MediaCrawler 的下载开关就叫 `ENABLE_GET_MEIDAS`）。
- 根因结论记进项目问题记录留痕（开发机为 E:\租房\终测记录.md；例：r.jina.ai 免费无 key 走共享池约 20 RPM 不稳，加免费 key 后独立配额约 200 RPM）。

## 密钥安全

- 高德密钥只放 `<keys>`（数据盘，skill 目录外；旧 ~/.rent-assist/keys.env 仅回退兼容）；**绝不**放进 skill 目录、报告、缓存或任何仓库。
- `AMAP_WEB_KEY` 只供 geocode.py 本地调用 REST，禁止出现在任何 HTML/日志/输出；`AMAP_JSAPI_KEY` 仅允许出现在 reports/*.html（前端加载地图必需）。
- 分发/拷贝 skill 时默认不含任何 key，接收方需自建 `<keys>`（申请方法见 references/amap-api.md）。

## 参考文件

- `references/queries.md`：检索策略正本：动态搜索词组合规则（小区名+场景词，五元组现场组词后拼 run_collect 的 --queries 串）、排序组合矩阵、评论优先原则、完整示例。
- `references/risk-signals.md`：8 类风险分类法 + 关键词词典（clean.py 粗分类正本）+ 严重度分级 + 广告贴识别。
- `references/checklist.md`：看房前/看房时/签合同前/入住后四阶段清单（意图 E 知识库 + 降级输出）。
- `references/amap-api.md`：高德 Web REST + JS API 速查（M3 通勤与地图开发用）。
- `references/auth.md`：三平台登录态排障（xhs/豆瓣/抖音探测与恢复路径、ensure_auth 用法，含何时需再扫码）。

