Files
tick-stock-panel/backend/tests/test_ext_time_field.py
T
shy3130 4abe9e9caa feat(ext-data): 日内序列表 — time_field 配置支持集合竞价等多盘数据接入
- PullConfig 新增可选 time_field (空值归一 None, 旧配置兼容); API PullConfigReq 透传, rows 端按时间列升序
- 时序表写入去重键: 配置 time_field 时按 [symbol, 时间列] 去重 (同 symbol 同时刻幂等, 不同盘并存), 未配置行为不变
- enriched/因子链路无需排除: _load_ext_value_maps 与 ext_factors 既有 unique(symbol) 兜底, 多行表确定性收敛
- 前端拉取面板新增时间字段输入框; 配合 hub auction_flow /day 端点逐日回补竞价多盘数据
- 新增 test_ext_time_field.py: 多盘并存/覆盖/默认行为不变
2026-09-13 23:50:10 +08:00

85 lines
2.8 KiB
Python

"""日内序列表 (PullConfig.time_field) 测试: 写入多盘并存 + 默认行为不变。"""
from __future__ import annotations
from datetime import date
from app.services.ext_data import (
ExtConfig,
ExtField,
PullConfig,
rows_to_parquet,
)
def _config(time_field: str | None) -> ExtConfig:
return ExtConfig(
id="auction",
label="集合竞价",
mode="timeseries",
fields=[
ExtField("symbol", "string", "代码"),
ExtField("ts", "string", "时刻"),
ExtField("v", "float", "值"),
],
pull=PullConfig(url="https://x", time_field=time_field),
)
def test_pull_config_time_field_roundtrip_and_normalize():
assert PullConfig().time_field is None # 缺省
assert PullConfig(time_field=" ").time_field is None # 空白归一 None
p = PullConfig(time_field="ts")
assert PullConfig.from_dict(p.to_dict()).time_field == "ts"
assert PullConfig.from_dict({}).time_field is None # 旧配置文件兼容
def test_rows_to_parquet_time_field_keeps_multiple_disks(tmp_path):
cfg = _config(time_field="ts")
day = date(2026, 9, 15)
first = [
{"symbol": "000001.SZ", "ts": "09:15:03", "v": 1.0},
{"symbol": "000001.SZ", "ts": "09:18:00", "v": 2.0},
]
assert rows_to_parquet(first, cfg, tmp_path, snapshot_date=day) == 2
# 重拉同一日: 同 [symbol, ts] 覆盖 (keep=last), 新盘追加
second = [
{"symbol": "000001.SZ", "ts": "09:15:03", "v": 1.5}, # 更新
{"symbol": "000001.SZ", "ts": "09:21:00", "v": 3.0}, # 新盘
]
# 返回值 = 合并去重后的分区总行数 (2 旧 + 2 新 - 1 覆盖 = 3)
assert rows_to_parquet(second, cfg, tmp_path, snapshot_date=day) == 3
import polars as pl
out = pl.read_parquet(
tmp_path / "ext_data" / "auction" / "timeseries"
/ f"date={day.isoformat()}" / "part.parquet"
).sort("ts")
assert out.height == 3
by_ts = {r["ts"]: r["v"] for r in out.to_dicts()}
assert by_ts == {"09:15:03": 1.5, "09:18:00": 2.0, "09:21:00": 3.0}
def test_rows_to_parquet_without_time_field_unchanged(tmp_path):
"""未配置 time_field: 默认按 symbol 去重, 行为与改造前完全一致。"""
cfg = _config(time_field=None)
day = date(2026, 9, 15)
assert rows_to_parquet(
[{"symbol": "000001.SZ", "ts": "09:15:03", "v": 1.0}], cfg, tmp_path,
snapshot_date=day,
) == 1
assert rows_to_parquet(
[{"symbol": "000001.SZ", "ts": "09:18:00", "v": 2.0}], cfg, tmp_path,
snapshot_date=day,
) == 1
import polars as pl
out = pl.read_parquet(
tmp_path / "ext_data" / "auction" / "timeseries"
/ f"date={day.isoformat()}" / "part.parquet"
)
assert out.height == 1
assert out.row(0, named=True)["v"] == 2.0