mirror of
https://github.com/ZhuLinsen/daily_stock_analysis
synced 2026-09-20 10:53:33 +08:00
fix: exclude ambiguous profile history aliases
This commit is contained in:
@@ -28,7 +28,7 @@
|
||||
## 数据来源与边界
|
||||
|
||||
- quote/history:复用 `StockService`,不新增数据获取器。
|
||||
- research:复用 `HistoryService` 和 #2291 的 `ResearchArtifact` builder;本 PR 因此堆叠在 #2291 上。报告查询把档案入口已经解析出的 market hint 传到 `HistoryService` 的候选生成层:A 股裸 canonical code 只展开对应 `SH` / `SZ` / `BJ` 代码,日韩台查询保留交易所后缀及其他无歧义别名,但不展开可能命中其他市场历史记录的裸数字代码;市场限定后候选为空时直接返回空分页,不会退化成无过滤查询。artifact 同时复用历史详情的上下文、raw result 和独立基本面快照 fallback,保留财报、分红与市场结构专项证据及其 source count。
|
||||
- research:复用 `HistoryService` 和 #2291 的 `ResearchArtifact` builder。报告查询把档案入口已经解析出的 market hint 传到 `HistoryService` 的候选生成层:A 股 canonical code 只保留能在无提示解析时仍确认属于 A 股的裸数字别名;例如 `600519` 可继续兼容旧记录,而与韩股同形的 `SZ000660` 不会查询裸 `000660`。日韩台查询保留交易所后缀及其他无歧义别名,但不展开可能命中其他市场历史记录的裸数字代码;市场限定后候选为空时直接返回空分页,不会退化成无过滤查询。artifact 同时复用历史详情的上下文、raw result 和独立基本面快照 fallback,保留财报、分红与市场结构专项证据及其 source count。
|
||||
- intelligence:复用 `IntelligenceService` 的 symbol scope 查询,并兼容 canonical、交易所前后缀、港股前后缀、混合大小写及日韩台市场限定的裸数字历史别名;无法无提示解析回当前市场的歧义裸码不会进入 `global` 查询。查询别名按 casefold identity 去重,由 repository 的 symbol-only 不区分大小写过滤兼容历史大小写,避免重复 count/select。任一别名/市场查询失败时块保持 partial limitation,即使其余查询成功但为空,也不误报为已确认无资讯。
|
||||
- portfolio:只读 `PortfolioRepository.list_cached_position_identities()`,并使用每条缓存持仓自己的 market 解析旧裸代码;只有 market 与档案身份一致时才算持有。不为了打开个股页触发实时估值或写 snapshot,所以状态固定为 `partial` 并包含 `cached_positions_only`。
|
||||
- monitors:复用 `AlertService.list_rules()`,以不区分大小写的 symbol 过滤分页汇总并去重 canonical code 及等价历史别名下的 `single_symbol` 规则。由于现有告警目标没有独立 market 字段,日韩台档案不会查询可能与 A/HK 同形的裸数字别名,避免跨市场规则误归属。
|
||||
@@ -37,4 +37,4 @@
|
||||
|
||||
## 回滚
|
||||
|
||||
Revert 本 PR 即可移除 profile schema、service、endpoint、测试和文档。没有数据库迁移、配置变更或数据清理步骤。若 #2291 尚未合入,本 PR 需与其一起保持堆叠或在合入后把 base 改回 `main`。
|
||||
Revert 本 PR 即可移除 profile schema、service、endpoint、测试和文档。没有数据库迁移、配置变更或数据清理步骤。
|
||||
|
||||
@@ -127,6 +127,11 @@ class HistoryService:
|
||||
if identity is None or identity.market != trusted_market:
|
||||
return []
|
||||
for candidate in identity.code_candidates:
|
||||
candidate_text = str(candidate or "").strip()
|
||||
if candidate_text.isdigit():
|
||||
unhinted_identity = resolve_daily_stock_identity(candidate_text)
|
||||
if unhinted_identity is None or unhinted_identity.market != trusted_market:
|
||||
continue
|
||||
add(candidate)
|
||||
return candidates
|
||||
|
||||
|
||||
@@ -109,8 +109,23 @@ class TestHistoryCsiCandidateConvergence(unittest.TestCase):
|
||||
queried_codes = db.get_analysis_history_paginated.call_args.kwargs["code"]
|
||||
self.assertIn("SZ000660", queried_codes)
|
||||
self.assertIn("000660.SZ", queried_codes)
|
||||
self.assertNotIn("000660", queried_codes)
|
||||
self.assertNotIn("000660.KS", queried_codes)
|
||||
|
||||
def test_market_hint_keeps_unambiguous_same_market_bare_numeric_alias(self):
|
||||
db = MagicMock()
|
||||
db.get_analysis_history_paginated.return_value = ([], 0)
|
||||
|
||||
HistoryService(db).get_history_list(
|
||||
stock_code="600519",
|
||||
page=1,
|
||||
limit=5,
|
||||
market_hint="cn",
|
||||
)
|
||||
|
||||
queried_codes = db.get_analysis_history_paginated.call_args.kwargs["code"]
|
||||
self.assertIn("600519", queried_codes)
|
||||
|
||||
def test_empty_market_qualified_candidate_set_fails_closed(self):
|
||||
db = MagicMock()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user