Skip to content

Commit ac76234

Browse files
committed
fix(review-feedback-1235): address latest review comments
1 parent 688cfdf commit ac76234

4 files changed

Lines changed: 65 additions & 3 deletions

File tree

src/services/stock_code_utils.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import re
99
from typing import Optional
1010

11+
from data_provider.base import is_bse_code
12+
1113

1214
# Known exchange prefixes (case-insensitive) and the digit lengths they accept.
1315
# e.g. SH600519 -> 600519, HK00700 -> 00700
@@ -28,12 +30,20 @@
2830
}
2931

3032

33+
def _valid_exchange_code(exchange: str, base: str, digit_lens: tuple[int, ...]) -> bool:
34+
if not (base.isdigit() and len(base) in digit_lens):
35+
return False
36+
if exchange == "BJ":
37+
return is_bse_code(base)
38+
return True
39+
40+
3141
def _strip_exchange_prefix(text: str) -> Optional[str]:
3242
"""Strip leading exchange prefix (SH/SZ/HK etc.) and return the bare digits, or None."""
3343
for prefix, digit_lens in _PREFIX_DIGIT_LENS.items():
3444
if text.startswith(prefix):
3545
base = text[len(prefix):]
36-
if base.isdigit() and len(base) in digit_lens:
46+
if _valid_exchange_code(prefix, base, digit_lens):
3747
return base.zfill(5) if prefix == "HK" else base
3848
return None
3949

@@ -43,7 +53,8 @@ def _strip_exchange_suffix(text: str) -> Optional[str]:
4353
for suffix, digit_lens in _SUFFIX_DIGIT_LENS.items():
4454
if text.endswith(suffix):
4555
base = text[: -len(suffix)].strip()
46-
if base.isdigit() and len(base) in digit_lens:
56+
exchange = suffix.lstrip(".")
57+
if _valid_exchange_code(exchange, base, digit_lens):
4758
return base.zfill(5) if suffix == ".HK" else base
4859
return None
4960

@@ -70,7 +81,7 @@ def normalize_code(raw: str) -> Optional[str]:
7081
7182
Supports:
7283
- Plain digit codes: 600519, 00700
73-
- Suffix format: 600519.SH, 600519.SZ, 00700.HK
84+
- Suffix format: 600519.SH, 600519.SZ, 920493.BJ, 00700.HK
7485
- Prefix format: SH600519, SZ000001, BJ920493, HK00700 (case-insensitive)
7586
- US ticker symbols: AAPL, TSLA
7687
"""

tests/test_analysis_api_contract.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -725,6 +725,37 @@ def test_trigger_analysis_accepts_bse_suffix_code_from_autocomplete(self) -> Non
725725
notify=True,
726726
)
727727

728+
def test_trigger_analysis_rejects_non_bse_code_with_bj_exchange_hint(self) -> None:
729+
if trigger_analysis is None:
730+
self.skipTest("fastapi is not installed in this test environment")
731+
732+
for bad_code in ("600519.BJ", "BJ600519"):
733+
with self.subTest(bad_code=bad_code):
734+
queue = MagicMock()
735+
736+
with patch("api.v1.endpoints.analysis.get_task_queue", return_value=queue), \
737+
patch("api.v1.endpoints.analysis.resolve_name_to_code") as resolve_mock:
738+
with self.assertRaises(Exception) as exc:
739+
trigger_analysis(
740+
request=SimpleNamespace(
741+
stock_code=bad_code,
742+
stock_codes=None,
743+
stock_name=None,
744+
original_query=bad_code,
745+
selection_source="manual",
746+
report_type="detailed",
747+
force_refresh=False,
748+
async_mode=True,
749+
notify=True,
750+
),
751+
config=SimpleNamespace(),
752+
)
753+
754+
self.assertEqual(exc.exception.status_code, 400)
755+
self.assertEqual(exc.exception.detail["error"], "validation_error")
756+
resolve_mock.assert_not_called()
757+
queue.submit_tasks_batch.assert_not_called()
758+
728759
def test_trigger_analysis_accepts_hk_prefixed_code(self) -> None:
729760
if trigger_analysis is None:
730761
self.skipTest("fastapi is not installed in this test environment")

tests/test_name_to_code_resolver.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ def test_bse_with_exchange_hint(self):
3737
assert _is_code_like("920493.BJ") is True
3838
assert _is_code_like("BJ920493") is True
3939

40+
def test_bj_exchange_hint_rejects_non_bse_code(self):
41+
assert _is_code_like("600519.BJ") is False
42+
assert _is_code_like("BJ600519") is False
43+
4044
def test_hk_5_digits(self):
4145
assert _is_code_like("00700") is True
4246

@@ -70,6 +74,10 @@ def test_strips_suffix(self):
7074
def test_strips_bse_prefix(self):
7175
assert _normalize_code("BJ920493") == "920493"
7276

77+
def test_bj_exchange_hint_rejects_non_bse_code(self):
78+
assert _normalize_code("600519.BJ") is None
79+
assert _normalize_code("BJ600519") is None
80+
7381
def test_preserves_us_stock(self):
7482
assert _normalize_code("AAPL") == "AAPL"
7583
assert _normalize_code("brk.b") == "BRK.B"

tests/test_stock_code_utils.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ def test_suffix_sz(self):
3030
def test_suffix_bj(self):
3131
assert is_code_like("920493.BJ") is True
3232

33+
def test_suffix_bj_rejects_non_bse_base(self):
34+
assert is_code_like("600519.BJ") is False
35+
3336
def test_suffix_lowercase(self):
3437
assert is_code_like("600519.sh") is True
3538

@@ -62,6 +65,9 @@ def test_prefix_sz(self):
6265
def test_prefix_bj(self):
6366
assert is_code_like("BJ920493") is True
6467

68+
def test_prefix_bj_rejects_non_bse_base(self):
69+
assert is_code_like("BJ600519") is False
70+
6571
def test_prefix_hk(self):
6672
assert is_code_like("HK00700") is True
6773

@@ -113,6 +119,9 @@ def test_suffix_sz_strips(self):
113119
def test_suffix_bj_strips(self):
114120
assert normalize_code("920493.BJ") == "920493"
115121

122+
def test_suffix_bj_rejects_non_bse_base(self):
123+
assert normalize_code("600519.BJ") is None
124+
116125
def test_suffix_ss_strips(self):
117126
assert normalize_code("600000.SS") == "600000"
118127

@@ -144,6 +153,9 @@ def test_prefix_sz(self):
144153
def test_prefix_bj(self):
145154
assert normalize_code("BJ920493") == "920493"
146155

156+
def test_prefix_bj_rejects_non_bse_base(self):
157+
assert normalize_code("BJ600519") is None
158+
147159
def test_prefix_hk(self):
148160
assert normalize_code("HK00700") == "00700"
149161

0 commit comments

Comments
 (0)