# BUGS-LOG · 防回归记录 每个 bug 修完都登记到这里。**未来改这些代码区域时,必须回看本文件确保不引入回归。** 对应单元测试在 `skills/deep-analysis/scripts/tests/test_no_regressions.py` + `tests/test_v2_10_4_fixes.py` + `tests/test_v2_11_scoring_calibration.py` + `tests/test_v2_12_1_data_fixes.py` + `tests/test_v2_13_playwright_strategy.py` + `tests/test_v3_9_2_flow_bugfixes.py` + `tests/test_issue87_em_direct_and_comps.py` + `tests/test_issue90_us_financials_ttm.py`。 **登记规范**:每条必含 症状 / 位置 / 根因 / 影响 / 修法 / 验证 / 回归测试 / "未来改该区域注意事项" --- ## Unreleased (2026-08-28 · 最新报告期营收同比 · issue #103) ### BUG · 年度收入相邻值覆盖最新报告期营收同比 - **症状**:[#103](https://github.com/wbh604/UZI-Skill/issues/103) 中,华夏银行最新报告期营收同比约为 `+35.3%`,报告却展示年度收入历史相邻计算得到的 `-5.4%`。 - **位置**:`fetch_financials.py::_fetch_a_share` / `_fetch_hk`,`lib/stock_features.py::extract_features`。 - **根因**:采集器已调用包含报告期同比的财务分析指标接口,但没有读取其明确同比字段;特征层无条件从年度 `revenue_history` 重算,使年度同比被误标为“最新营收增速”。 - **影响**:成长、PEG、风格识别及投资者评委规则可能使用过时的年度增长率,对季报后发生拐点的公司形成方向性误判。 - **修法**:优先读取最新非空的报告期营收同比,结构化保存 `revenue_growth_yoy`、`period`、`basis`、`source`;只有明确同比不可得时才从年度历史推导,并标记为 `annual_yoy`。港股官方同比同步采用相同契约。 - **验证**:新增回归覆盖最新报告期同比优先、特征层不再重算覆盖、`NaN/Inf` 不得成为有效同比。 - **未来改该区域注意事项**:增长率必须同时携带报告期、口径和来源;不得把 TTM、季度累计值、单季值或年度值混为同一同比序列,也不得用相邻异口径数值直接推导。 --- ## Unreleased (2026-07-18 · 数据完整性 hotfix · issue #87/#90) ### BUG · 东财直连字段/单位误读、Comps 自引用、美股财报只看年报 - **症状**: 1. [#87](https://github.com/wbh604/UZI-Skill/issues/87) · A 股基础数据走 EastMoney push2 直连 fallback 时,把 `f47` 成交量错误当作 `change_pct` 兜底,导致涨跌幅异常;同时 `f116` 市值原始单位是元,下游按“亿”读会把 DCF/市场份额等派生指标放大 1e8 倍。 2. [#87](https://github.com/wbh604/UZI-Skill/issues/87) · Comps 同行估值在只有目标公司自身样本时仍继续计算分位与估值结论,报告可能出现“自己和自己对标”的假结论。 3. [#90](https://github.com/wbh604/UZI-Skill/issues/90) · 美股财务数据只读取 `yfinance.Ticker.financials` 年报,未合并更新的 quarterly financials,财报季后会继续展示旧年报口径,无法暴露 TTM 最新收入/净利。 - **位置**: - `lib/data_sources.py::_fetch_basic_a` / `_fetch_financials_impl` - `lib/stock_features.py::extract_features` - `lib/fin_models.py::build_comps_table` - `fetch_financials.py::_fetch_us` - **根因**: - EastMoney push2 字段 scale 混杂:`f43/f60` 是 price * 100,`f170` 是涨跌幅 * 100,`f47` 是成交量,`f116/f117` 是元;旧代码没有集中解析契约。 - Comps 模型只检查 `peers` 是否为空,没有剔除 `is_self` / 同 ticker / 同 name,也没有最低有效同行数 gate。 - 美股路径把年报列直接作为最新历史序列,没有读取 `quarterly_financials` 做最近 4 季 TTM,也没有 `financial_basis` / `financial_period` 告诉报告当前口径。 - **影响**: - A 股 fallback 下涨跌幅、市值、市占率、DCF per-share 和估值结论可能严重失真。 - 同行样本不足时仍给出估值判断,用户会把无样本报告误读成有效 peer comp。 - 美股在最新季报后仍显示旧年报趋势,尤其对周期股/半导体/高波动成长股会低估或高估盈利拐点。 - **修法**: 1. 新增 `_parse_em_direct_payload` 集中归一化 push2 字段:只用 `f170` 或 `price/prev_close` 计算 `change_pct`,`f47` 只保留为 `volume`,`f116/f117` 转成 `xx亿` 并保留 raw。 2. `stock_features._market_cap_to_yi` 识别原始元单位并转成“亿”,市值、市占率走同一转换函数。 3. `build_comps_table` 剔除目标公司自身样本;有效同行少于 2 家时直接返回“同行样本不足 · 无法对标”,不输出分位和隐含价。 4. `_fetch_us` 读取 quarterly financials 最近 4 季 TTM;季度期末晚于年报时追加 `revenue_ttm` / `net_profit_ttm`,写 `financial_basis=TTM` 和 `financial_period`;最新口径超过 180 天时写 staleness warning。 5. `_fetch_financials_impl` 美股原始源暴露 `quarterly_income`,让数据排查能看到季度输入。 - **验证**: - `tests/test_issue87_em_direct_and_comps.py` 覆盖东财字段不把成交量当涨跌幅、原始元市值转“亿”、self-only Comps 被拒绝。 - `tests/test_issue90_us_financials_ttm.py` 覆盖 quarterly TTM 追加、旧财报时效 warning、raw data source 暴露 `quarterly_income`。 - 全量 `pytest tests -q`:663 passed。 - **未来改该区域注意事项**: - 新增东财 push2 字段时必须先写字段 scale 注释和解析测试,不要在 fetcher 内临时 `(field or other_field) / 100`。 - 下游模型的市值统一使用“亿”口径,raw 元只能作为溯源字段存在。 - Comps 估值必须有真实 peer universe;少于 2 家同行只能展示数据缺口,不能生成估值结论。 - 美股报告展示历史财务时必须同时写 `financial_basis` 和 `financial_period`,避免用户不知道当前是 annual 还是 TTM。 --- ## v3.9.2 (2026-07-07 · 流程与数据契约 hotfix · issue #82/#83) ### BUG · OCF 缺失、industry=None、CLI report 后处理早退、agent_analysis 坏结构继续合并 - **症状**: 1. [#82](https://github.com/wbh604/UZI-Skill/issues/82) · `fetch_financials` 只把经营现金流写成 `fcf`,没有显式 `ocf` / `ocf_history` / `ocf_to_net_income_ratio`,A 股 trap-detector 的现金利润匹配规则会读不到真实 OCF。 2. [#83](https://github.com/wbh604/UZI-Skill/issues/83) · `basic.industry=None` 时 `fetch_peers` 整个 A 股分支跳过,返回空同行表且 `fallback=False`;`fetch_valuation` 也直接丢行业/市场 PE。 3. fund/ETF/LOF 持仓汇总、`--versus`、`--portfolio` 生成 HTML 后直接 `sys.exit(0)`,绕过 `--output-dir` / `--remote` / 浏览器打开。 4. `agent_analysis.json` schema error 只打印 `_agent_analysis_errors.json`,但仍传给 `generate_synthesis`,坏结构可能污染报告或触发 `.get()` 异常。 - **位置**: - `fetch_financials.py` 现金流段 - `fetch_peers.py` A 股 `industry` 分支 - `fetch_valuation.py` cninfo 行业 PE 段 - `run.py` CLI 分支和 remote 后处理 - `run_real_test.py::stage2` - `lib/pipeline/fetchers/registry.py` - **根因**: - 数据契约漂移:legacy fetcher 输出 `financial_health` / `pe_quantile`,registry 却期待顶层 `debt_ratio/current_ratio` / `pe_ttm/pe_percentile`。 - 控制流分散:多报告模式各自早退,没有共享 report post-process。 - schema validator 只写错误清单,没有把 error 级问题转成 fallback。 - **影响**: - 现金流质量被默认值掩盖,可能把 OCF/净利大幅背离的股票误判为通过。 - 行业缺失时报告同行/估值区块空白但不标 fallback。 - SaaS/远程查看模式在 fund/versus/portfolio 下失效。 - 非 Claude/Codex 生成的坏 `agent_analysis.json` 可能导致 stage2 崩溃或错用用户覆盖字段。 - **修法**: 1. `fetch_financials._apply_operating_cash_flow` 显式输出 OCF 字段;`stock_features` 读取 `ocf_to_net_income_ratio`。 2. `fetch_peers` 在 industry 缺失时 self-only fallback,并写 `fallback_reason`。 3. `fetch_valuation` 在 industry 缺失/未匹配时用 cninfo 市场加权 PE 兜底,并写 `industry_pe_fallback_reason`。 4. pipeline registry 对齐 legacy 输出字段。 5. `run.py` 抽出 direct report path + shared post-process,fund summary / versus / portfolio 均复用 `--output-dir` / `--remote`;`cloudflared` 缺失时默认只提示,显式 `--install-cloudflared` 才自动安装。 6. `run_real_test._validate_agent_analysis_or_fallback` 对 error 级 schema issue 直接丢弃 payload,回退脚本骨架。 - **验证**:新增 `tests/test_v3_9_2_flow_bugfixes.py`,覆盖 8 个回归。 - **未来改该区域注意事项**: - 新增/改名 fetcher 字段时,同步更新 `lib/pipeline/fetchers/registry.py`,并加行为测试,不只 grep 源码。 - 所有“生成 HTML 的 CLI 模式”都必须返回 report path 并进入统一 post-process;不要再在 runner 分支里直接 `sys.exit(0)`。 - `cloudflared` / `brew` / `sudo` 属于系统变更,默认只提示,必须显式 opt-in。 ## v3.8.1 (2026-06-09 · 全面体检 · H/I 两组配套层 6 处补齐) ### BUG · 加评委没加配套 · 6 个静默降级缺陷 - **症状**:v3.6.3 (Serenity I 组) / v3.7.0 (13 位科技大佬 H 等组) 上线后 · 报告中 ① 14 位新评委头像破图 ② 流派评分卡永远只显示 7 派(H/I 静默消失)③ H/I 组标签 显示裸字母 ④ H/I 评委的 time_horizon/position_sizing 全 "—" ⑤ 风格动态加权对 H/I 失效 ⑥ 新评委群聊台词全是 generic 套话 - **根因**:加评委只改了 investor_db + investor_criteria · 但仓库里有 **6 处按组 遍历/查表的硬编码 A-G 配套层** · 全部用 `.get(g, default)` 优雅降级 → 不崩 · 所以 CI 全绿 · 视觉缺陷一直没暴露 - **修法**: 1. `gen_pixel_avatars.py` 重跑 → 补 14 头像(脚本本身就支持增量 · 之前没人跑) 2. `special_cards.render_school_scores` order A-G → A-I 3. `panel_cards.GROUP_LABELS` + `special_cards` 内联副本 → 补 H/I 4. `investor_profile.GROUP_DEFAULT` → 补 H/I 流派档案 5. `stock_style.STYLE_GROUP_WEIGHTS` 8 风格 × 补 H/I 列 6. `MARKET_SCOPE` 13 人显式登记 + `PERSONAS` 13 人 voice 台词 - **验证**:10 个体检回归测试 (`test_v3_8_1_audit_fixes.py`) · 632 passed - **未来改该区域注意事项**(防再犯 · 关键): - **加新评委的 checklist**(缺一不可):investor_db → investor_criteria → `gen_pixel_avatars.py` 重跑 → MARKET_SCOPE → PERSONAS 台词 → 若新增组: GROUP_LABELS ×2 / render_school_scores order / GROUP_DEFAULT / STYLE_GROUP_WEIGHTS / SCHOOL_LABELS / run.py --school choices / _render_school_lock_banner THEMES / score_fns GROUP_META - 体检测试 `test_v3_8_1_audit_fixes.py` 已把以上大部分变成硬断言 · 新加组时这些测试会先红 · 跟着修就不会漏 - 不要依赖 `.get(g, 1.0)` 这类优雅降级当"没问题"的证据 —— 它恰恰是 本次 6 个缺陷能潜伏两个版本的原因 --- ## v3.6.3 (2026-06-03 · 重磅角色 Serenity · AI 卡位/瓶颈猎手 I 组) ### BUG · Serenity 卡位关键词库漏掉 AR/消费光学,把光学股误判成「不在 AI 链」 - **症状**:实测 `python run.py 002273 --depth lite`(水晶光电,真实行业「光学光电子」· AR/VR + 车载光学 + iPhone 相机模组 · 市值 404 亿)· Serenity 给出 `bearish / 0`,headline「不在 AI 产业链上 —— 对我没有意义」。但水晶光电明显踩在 AR/AI 光学链上,应识别为「在链但卡位不够硬 → neutral」,而非一票否决。 - **位置**:`skills/deep-analysis/scripts/lib/stock_features.py` · 派生特征 `ai_chokepoint_score` 的 `_AI_CHOKEPOINT_KW` 关键词库 - **根因**:关键词库只覆盖了**数据中心光**(光模块/CPO/光通信/光器件/激光器)+ 先进封装 + 化合物半导体 + 互连 + 算力,**漏了 AR/消费/车载光学族**(光学/光电子/光学元件/光波导/滤光片/镀膜/棱镜/镜头/相机模组/AR-VR/近眼显示/车载光学)。`光学光电子` 行业整段命中 0 词 → `ai_chain_hit=False` → 直接腰斩到 score≈0。 - **影响**:所有 AR/VR/消费光学/车载光学方向的真实卡位候选(水晶光电、蓝特光学、舜宇、长光华芯等)会被 Serenity 误判为「不在 AI 链」而错杀,丧失「在链→再按不可替代性/市值判断」的分级能力。 - **修法**:`_AI_CHOKEPOINT_KW` 扩充一组 AR/光学终端侧词条。**注意刻意不加裸词 `ar`/`vr`/`mr`**(会在 lowercase 的 JSON blob 里匹配到 market/margin/warrant 等英文子串造成全局误命中)· 改用中文词(增强现实/虚拟现实/混合现实/头显/近眼显示)+ 带斜杠的 `ar/vr` + `ar眼镜`。 - **验证**: - 水晶光电真实数据重评 → `ai_chain_hit=True`(命中 光学/光电子/相机模组/ar-vr/车载光学)· 卡位分 70.1 · 但不可替代=False(切换5+规模6=11<12) + 市值404亿>300 → **neutral / 59**「命中 AI 链但可替代性偏高、市值偏大,不是真瓶颈」(地道的 Serenity 视角) - `test_serenity_rules.py` 7 项不破:白酒(高粱酿造)/银行(存款贷款) 仍 `ai_chain_hit=False` → bearish(无 AR/光学词误命中) - 全量 532 passed - **未来改该区域注意事项**: - **永远不要在 `_AI_CHOKEPOINT_KW` 加 2 字母以内的裸英文词**(ar/vr/mr/ic/ai 单独)· blob 是 lowercase 拼接的中英文 + JSON · 短英文子串必然误命中。要加英文必须够长够特异(waveguide / micro-led / cowos)。 - 扩词只影响 `ai_chain_hit` 这道**门槛**;是否 bullish 仍由 `chokepoint≥70 + 不可替代 + 中小市值` 三条 weight-5 规则把关。所以扩词宁可**宽进严出**,不会让普通光学股变成 Serenity 重仓。 - 改词库后必跑 `test_serenity_rules.py::test_bearish_on_non_ai_regardless_of_moat`(确保非 AI 股仍被否)。 --- ## v3.6.2 (2026-06-03 · cninfo 翻页长尾 #68 + install-hermes.sh pip 探测 #69) ### BUG #68 · cninfo 公告分页 854 页拖几小时 - **症状**:用户 [@xy2yp](https://github.com/wbh604/UZI-Skill/issues/68) 反馈 `python run.py --versus 000958 600406 --depth lite` 卡在 15_events 维度 · 进度条 `0/854 [01:53<6:11:58, 26.44s/it]` · 单股 4-6 小时 - **位置**:`skills/deep-analysis/scripts/fetch_events.py::_cninfo_disclosures` - **根因**:调 `akshare.stock_zh_a_disclosure_report_cninfo` · 该 akshare 函数内部用循环翻完全部分页(cninfo 一只票常有 800+ 页公告)才把 DataFrame 返给我们 · 后续 `.head(30)` 截取已无用 · 翻页时间已经花掉了 - **修法**: 1. 新增 `_cninfo_direct_api(code, page_size=30, timeout=15)` · 直接 `requests.post` 到 `http://www.cninfo.com.cn/new/hisAnnouncement/query` · `pageNum=1 + pageSize=30` · 一次 HTTP ≤15s · 永远不翻全部页 2. 板块路由:`000/001/002/3xx → szse` · `6xx/688 → sse` · `8xx → bse` 3. 响应解析:`announcements[*].announcementTime` (毫秒) / `announcementTitle` / `adjunctUrl` (拼 `http://static.cninfo.com.cn/` 前缀) 4. `_cninfo_disclosures` 优先调直连 API · 直连失败时**默认不调 akshare**(防长尾)· 仅 `UZI_AK_CNINFO_FALLBACK=1` 显式启用时才走 akshare 慢路径 - **验证**: - mock `requests.post` ConnectionError → 返 [] · 不抛 - mock 200 + 合法 JSON → 解析正确 / 路由正确 - 网络失败 + 未设 fallback env → 不调 akshare(关键 · 防止再次踩坑) - **未来改该区域注意事项**: - **永远不要回去用 `ak.stock_zh_a_disclosure_report_cninfo`** · 这是 akshare 实现的死结 · 它会翻完所有页 - 直连 API 的 pageSize 上限 cninfo 文档说 30 · 别贪心设大数字(会被服务端拒) - cninfo 的时间戳是 **毫秒** · 不要忘了 `/ 1000` 再 `fromtimestamp` - 板块路由 prefix 列表要保持齐:未来 cninfo 加新板块要更新(如果 北交所 8xx 后还有新代码段) ### BUG #69 · install-hermes.sh 在 Linux 找不到 pip - **症状**:用户 [@FrankHuy](https://github.com/wbh604/UZI-Skill/issues/69) 在 CentOS-like + Python 3.11 跑一键脚本:`line 95: pip: command not found` + akshare 装不上 - **位置**:`install-hermes.sh` line ~94(装依赖段) - **根因**:很多 Linux 发行版(Debian/Ubuntu/CentOS/RHEL)**默认不提供 plain `pip` 命令** · 用户必须用 `pip3` / `python3 -m pip` · 我们脚本只试 `pip` 直接报错 · 后续 akshare 报"找不到 wheel"其实是因为底层 pip 不存在 · 不是真的 wheel 不兼容 - **修法**: 1. 启动加 Python 版本预检(`python3` → `python` 探测 + 版本 ≥3.10 检查)· 警告而非阻断 · 给三种系统的安装命令 2. pip 探测改为 5 层级联:`$HERMES/venv/bin/pip` → `$HERMES/.venv/bin/pip` → `pip` → `pip3` → `$PY_BIN -m pip` 3. 全部探测失败 → exit 4 + 给 apt / yum / ensurepip / get-pip 四种安装路径 4. `pip install` 失败 → exit 5 + 提示版本/镜像源/升级 pip - **验证**: - `bash -n install-hermes.sh` 语法过 - 测试 grep 验证脚本含 pip3 / -m pip / tuna.tsinghua / upgrade pip 等关键字 - **未来改该区域注意事项**: - 不要在主体代码里假设 `pip` 命令存在 · 用 `command -v` 探测 - `set -euo pipefail` 严格模式下任何 fail 立即退出 · 必须 *先* 探测再 *用* - 镜像源 fallback 只在文案里建议 · 不要默认走清华源(部分海外用户访问慢)· 让用户主动加 `-i` --- ## v3.6.1 (2026-05-29 · Hermes Skills Guard 假阳性绕过 · issue #66) ### BUG · `hermes skills install` 报 DANGEROUS · `--force` 覆盖不了 - **症状**:用户 @zodiacg ([#66](https://github.com/wbh604/UZI-Skill/issues/66)) 反馈 `hermes skills install wbh604/UZI-Skill/skills/deep-analysis` 失败 · scanner 168 findings · DANGEROUS verdict - **位置**:Hermes Skills Guard 模式匹配扫描器(NousResearch/hermes-agent · 上游 bug · 不是 UZI-Skill 的问题) - **根因**:Skills Guard 是 v0.x 纯模式匹配 (`r'os\.environ\b'`) · 不区分"读自己配置"vs"窃取用户敏感 env" · 也不识别 docstring / HTML 注释 / opt-in 用户功能 - **影响**:community 源任何 finding 都会 BLOCK · `--force` 设计上不能覆盖 DANGEROUS · 用户 Hub 装不下来 - **修法**:提供 `install-hermes.sh` 一键脚本 · `git clone + ln -sfn` 到 `~/.hermes/skills/` · 跳过 Hub quarantine 扫描 · Hermes 跑时只看目录 layout · 完全等价 - **绝不能做的事**: - ❌ 用 dynamic import + 字符串拼接绕 Skills Guard 检测(这是上游 issue #7072 提到的恶意绕过 · 我们绝不走这条路 · 那是窃取信任模型) - ❌ 删除合法的 `os.environ.get` 代码来降 findings · 那是把功能砍了 - ✅ 只提供 clone+symlink 路径 · 用户主动决定信任我们 · 而不是欺骗 Hub 让它判 "safe" - **验证**: - `bash install-hermes.sh` 在干净环境跑通 · 4 个 skill symlink + venv pip 装包 + SKILL.md 版本验证 - 11 个回归测试 (`test_v3_6_1_install_hermes.py`) - **未来改该区域注意事项**: - 若 Hermes Skills Guard 升级到 allowlist 模型 · `hermes skills install` 重新可用时 · 在 INSTALL-HERMES.md 顶部加 "Skills Guard 已修 · 直接 hub 装" 提示 - 但 install-hermes.sh 应该保留作为 dev 路径(clone + symlink 让 git pull 立刻生效 · Hub 装是 snapshot) - 改 skill 目录结构时 · 必须同步更新脚本里的 `SKILLS=(deep-analysis ...)` 数组 - 永远不要"为了 Skills Guard 评分好看"砍合法功能 · `--remote` cloudflared 是用户主动 opt-in · 默认不跑 --- ## v3.6.0 (2026-05-29 · 视觉升级 + 多股对比 + 组合分析) ### FEATURE A1 · 暗色模式 toggle - **位置**:`assets/report-template.html`(CSS `:root` + `[data-theme="dark"]` 块 · 末尾 `