mirror of
https://github.com/ZhuLinsen/daily_stock_analysis
synced 2026-09-20 10:53:33 +08:00
fix: exclude ambiguous global stock aliases
This commit is contained in:
@@ -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 同形的裸数字别名,避免跨市场规则误归属。
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user