fix: exclude ambiguous global stock aliases

This commit is contained in:
ZhuLinsen
2026-08-29 14:49:33 +08:00
parent 7533ca4239
commit e6216bf918
3 changed files with 31 additions and 8 deletions

View File

@@ -29,7 +29,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。
- intelligence复用 `IntelligenceService` 的 symbol scope 查询,并兼容 canonical、交易所前后缀、港股前后缀、混合大小写及日韩台市场限定的裸数字历史别名裸数字别名不会进入 `global` 查询。任一别名/市场查询失败时块保持 partial limitation即使其余查询成功但为空也不误报为已确认无资讯。
- intelligence复用 `IntelligenceService` 的 symbol scope 查询,并兼容 canonical、交易所前后缀、港股前后缀、混合大小写及日韩台市场限定的裸数字历史别名无法无提示解析回当前市场的歧义裸码不会进入 `global` 查询。任一别名/市场查询失败时块保持 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 同形的裸数字别名,避免跨市场规则误归属。

View File

@@ -335,13 +335,14 @@ class StockProfileService:
candidates.append(numeric_base)
aliases: List[str] = []
for candidate in candidates or [code]:
if (
not include_ambiguous_numeric
and market in {"jp", "kr", "tw"}
and str(candidate).strip().isdigit()
):
continue
for alias in (str(candidate).strip(), str(candidate).strip().lower()):
candidate_text = str(candidate).strip()
if not include_ambiguous_numeric and candidate_text.isdigit():
if market in {"jp", "kr", "tw"}:
continue
unhinted_identity = resolve_daily_stock_identity(candidate_text)
if unhinted_identity is None or unhinted_identity.market != market:
continue
for alias in (candidate_text, candidate_text.lower()):
if alias and alias not in aliases:
aliases.append(alias)
return aliases

View File

@@ -449,12 +449,34 @@ def test_explicit_cn_identity_never_reexpands_through_a_colliding_kr_alias() ->
monitor_aliases = {
call.kwargs["target"] for call in dependencies["alert_service"].list_rules.call_args_list
}
intelligence_calls = {
(call.kwargs["scope_value"], call.kwargs["market"])
for call in dependencies["intelligence_service"].list_items.call_args_list
}
assert "000660.KS" not in intelligence_aliases
assert "000660.KS" not in monitor_aliases
assert ("000660", "cn") in intelligence_calls
assert ("000660", "global") not in intelligence_calls
assert {"SZ000660", "000660.SZ"} <= intelligence_aliases
assert {"SZ000660", "000660.SZ"} <= monitor_aliases
def test_unambiguous_cn_bare_code_remains_available_to_global_and_monitor_queries() -> None:
service, dependencies = _service()
service.get_profile("600519")
intelligence_calls = {
(call.kwargs["scope_value"], call.kwargs["market"])
for call in dependencies["intelligence_service"].list_items.call_args_list
}
monitor_targets = {
call.kwargs["target"] for call in dependencies["alert_service"].list_rules.call_args_list
}
assert ("600519", "global") in intelligence_calls
assert "600519" in monitor_targets
def test_us_suffix_converges_to_bare_ticker_and_queries_legacy_aliases() -> None:
service, dependencies = _service()