diff --git a/zhixing-server/src/zhixing_server/modules/sector_radar/domain/source.py b/zhixing-server/src/zhixing_server/modules/sector_radar/domain/source.py index 991c255..ea8d379 100644 --- a/zhixing-server/src/zhixing_server/modules/sector_radar/domain/source.py +++ b/zhixing-server/src/zhixing_server/modules/sector_radar/domain/source.py @@ -370,7 +370,7 @@ class SuspendRow: ts_code: str trade_date: date - suspend_timing: str + suspend_timing: str | None suspend_type: str @classmethod @@ -382,7 +382,7 @@ class SuspendRow: return cls( ts_code=_required_text(row, "ts_code"), trade_date=trade_date, - suspend_timing=_required_text(row, "suspend_timing"), + suspend_timing=_optional_text(row, "suspend_timing"), suspend_type=_required_text(row, "suspend_type"), ) diff --git a/zhixing-server/tests/unit/sector_radar/test_normalize.py b/zhixing-server/tests/unit/sector_radar/test_normalize.py index a70db2c..3c9859f 100644 --- a/zhixing-server/tests/unit/sector_radar/test_normalize.py +++ b/zhixing-server/tests/unit/sector_radar/test_normalize.py @@ -188,7 +188,7 @@ def test_stock_fact_normalization_preserves_all_missing_and_zero_states() -> Non SuspendRow( ts_code=codes[1], trade_date=TARGET_DATE, - suspend_timing="09:30", + suspend_timing=None, suspend_type="停牌", ), ) diff --git a/zhixing-server/tests/unit/sector_radar/test_tushare_source.py b/zhixing-server/tests/unit/sector_radar/test_tushare_source.py index 55bbc50..5901ebd 100644 --- a/zhixing-server/tests/unit/sector_radar/test_tushare_source.py +++ b/zhixing-server/tests/unit/sector_radar/test_tushare_source.py @@ -333,6 +333,47 @@ def test_stock_basic_explicitly_requests_all_lifecycle_statuses() -> None: assert [call[1]["list_status"] for call in client.calls] == ["L", "D", "P", "G", "UN"] +def test_suspend_timing_may_be_missing_while_suspend_type_remains_required() -> None: + client = QueryClient( + { + ( + "suspend_d", + "", + ): ( + { + "ts_code": "000001.SZ", + "trade_date": "20260828", + "suspend_timing": None, + "suspend_type": "S", + }, + ) + } + ) + + result = make_adapter(client).fetch_suspensions(TARGET_DATE) + + assert result.rows[0].suspend_timing is None + assert result.rows[0].suspend_type == "S" + + missing_type = QueryClient( + { + ( + "suspend_d", + "", + ): ( + { + "ts_code": "000001.SZ", + "trade_date": "20260828", + "suspend_timing": None, + "suspend_type": None, + }, + ) + } + ) + with pytest.raises(SourceContractError, match="suspend_type must be a non-empty string"): + make_adapter(missing_type).fetch_suspensions(TARGET_DATE) + + def test_source_snapshot_hash_is_order_stable_and_excludes_token_params() -> None: first = build_source_snapshot( api_name="daily",