alpha-dc-client 0.2.0__tar.gz → 0.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {alpha_dc_client-0.2.0 → alpha_dc_client-0.2.2}/.gitignore +4 -0
- {alpha_dc_client-0.2.0 → alpha_dc_client-0.2.2}/PKG-INFO +4 -4
- alpha_dc_client-0.2.2/pyproject.toml +33 -0
- {alpha_dc_client-0.2.0 → alpha_dc_client-0.2.2}/src/dc_client/data/discovery.py +1 -0
- {alpha_dc_client-0.2.0 → alpha_dc_client-0.2.2}/src/dc_client/data/io.py +146 -15
- {alpha_dc_client-0.2.0 → alpha_dc_client-0.2.2}/src/dc_client/data/registry.json +714 -51
- {alpha_dc_client-0.2.0 → alpha_dc_client-0.2.2}/src/dc_client/data/registry.py +4 -0
- {alpha_dc_client-0.2.0 → alpha_dc_client-0.2.2}/src/dc_client/data/remote.py +65 -8
- {alpha_dc_client-0.2.0 → alpha_dc_client-0.2.2}/src/dc_client/data/sync.py +109 -26
- alpha_dc_client-0.2.2/src/dc_client/tests/test_review_260918_fixes.py +181 -0
- alpha_dc_client-0.2.2/src/dc_client/tests/test_supply_followup.py +278 -0
- alpha_dc_client-0.2.2/src/dc_client/tests/test_supply_review_fixes.py +206 -0
- alpha_dc_client-0.2.0/pyproject.toml +0 -29
- alpha_dc_client-0.2.0/src/dc_client/base.py +0 -69
- {alpha_dc_client-0.2.0 → alpha_dc_client-0.2.2}/DATA.md +0 -0
- {alpha_dc_client-0.2.0 → alpha_dc_client-0.2.2}/USAGE.md +0 -0
- {alpha_dc_client-0.2.0 → alpha_dc_client-0.2.2}/src/dc_client/__init__.py +0 -0
- {alpha_dc_client-0.2.0 → alpha_dc_client-0.2.2}/src/dc_client/client.py +0 -0
- {alpha_dc_client-0.2.0 → alpha_dc_client-0.2.2}/src/dc_client/client.pyi +0 -0
- {alpha_dc_client-0.2.0 → alpha_dc_client-0.2.2}/src/dc_client/data/__init__.py +0 -0
- {alpha_dc_client-0.2.0 → alpha_dc_client-0.2.2}/src/dc_client/data/__main__.py +0 -0
- {alpha_dc_client-0.2.0 → alpha_dc_client-0.2.2}/src/dc_client/data/cli.py +0 -0
- {alpha_dc_client-0.2.0 → alpha_dc_client-0.2.2}/src/dc_client/data/config.py +0 -0
- {alpha_dc_client-0.2.0 → alpha_dc_client-0.2.2}/src/dc_client/data/namespace.py +0 -0
- {alpha_dc_client-0.2.0 → alpha_dc_client-0.2.2}/src/dc_client/exceptions.py +0 -0
- {alpha_dc_client-0.2.0 → alpha_dc_client-0.2.2}/src/dc_client/ops_trader.py +0 -0
- {alpha_dc_client-0.2.0 → alpha_dc_client-0.2.2}/src/dc_client/run_tests.py +0 -0
- {alpha_dc_client-0.2.0 → alpha_dc_client-0.2.2}/src/dc_client/tests/__init__.py +0 -0
- {alpha_dc_client-0.2.0 → alpha_dc_client-0.2.2}/src/dc_client/tests/base.py +0 -0
- {alpha_dc_client-0.2.0 → alpha_dc_client-0.2.2}/src/dc_client/tests/test_data.py +0 -0
|
@@ -163,3 +163,7 @@ ic_backfill_*.err
|
|
|
163
163
|
# 港股回补运行时状态(断点续传,本地产物)
|
|
164
164
|
dc_quant/scripts/_hk_*_progress.json
|
|
165
165
|
dc_quant/scripts/check/_tmp_*
|
|
166
|
+
dc_quant/scripts/_us_edgar_repull_progress.json
|
|
167
|
+
dc_quant/scripts/check/_layer_reconcile_done.json
|
|
168
|
+
dc_quant/scripts/_cn_index_full_progress.json
|
|
169
|
+
dc_quant/scripts/_etf_fundamentals_progress.json
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: alpha-dc-client
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Author-email: Hang GuangLiang <hanguangliang@alphaaidig.com>
|
|
5
|
-
Requires-Dist: alpha-dc-common
|
|
6
|
-
Requires-Dist:
|
|
7
|
-
Requires-Dist: python-dotenv
|
|
5
|
+
Requires-Dist: alpha-dc-common>=0.1.26
|
|
6
|
+
Requires-Dist: requests
|
|
8
7
|
Provides-Extra: parquet
|
|
9
8
|
Requires-Dist: duckdb>=0.10; extra == 'parquet'
|
|
10
9
|
Requires-Dist: pandas>=2.0; extra == 'parquet'
|
|
11
10
|
Requires-Dist: paramiko>=3.0; extra == 'parquet'
|
|
11
|
+
Requires-Dist: python-dotenv; extra == 'parquet'
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "alpha-dc-client"
|
|
7
|
+
version = "0.2.2" # common 锁定放宽为 >=(内核可独立发版)
|
|
8
|
+
authors = [
|
|
9
|
+
{name = "Hang GuangLiang", email = "hanguangliang@alphaaidig.com"},
|
|
10
|
+
]
|
|
11
|
+
dependencies = [
|
|
12
|
+
# client.py 直接使用 requests(POST + Timeout/ConnectionError 异常处理),
|
|
13
|
+
# workspace 内曾靠 tushare 等传递带入未暴露,独立安装必炸,必须显式声明
|
|
14
|
+
"requests",
|
|
15
|
+
"alpha-dc-common>=0.1.26" # 内核独立演进发版,client 不强制随发 # +strategies 内核,
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
[project.optional-dependencies]
|
|
19
|
+
# 因子 Parquet 同步与读取(dc_client.data):duckdb 读取 + paramiko sftp 兜底通道
|
|
20
|
+
parquet = [
|
|
21
|
+
"duckdb>=0.10",
|
|
22
|
+
"pandas>=2.0",
|
|
23
|
+
"paramiko>=3.0",
|
|
24
|
+
"python-dotenv", # data/config.py 懒加载 .env
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[tool.hatch.build.targets.wheel]
|
|
28
|
+
packages = ["src/dc_client"]
|
|
29
|
+
# 测试与 run_tests 不进发布产物(tests/base.py 含硬编码 API token,绝不随 wheel 外发)
|
|
30
|
+
exclude = ["src/dc_client/tests", "src/dc_client/run_tests.py"]
|
|
31
|
+
|
|
32
|
+
[tool.uv.sources]
|
|
33
|
+
alpha-dc-common = { workspace = true }
|
|
@@ -235,6 +235,7 @@ def effective_registry(cfg: SyncConfig | None = None) -> Registry:
|
|
|
235
235
|
date_column=bundled_meta.date_column,
|
|
236
236
|
entity_column=bundled_meta.entity_column,
|
|
237
237
|
tier=bundled_meta.tier,
|
|
238
|
+
month_continuity=bundled_meta.month_continuity,
|
|
238
239
|
)
|
|
239
240
|
datasets[name] = meta
|
|
240
241
|
|
|
@@ -172,6 +172,8 @@ def _sql_str_list(paths: list[Path]) -> str:
|
|
|
172
172
|
def _check_month_continuity(files: list[Path], dataset: str) -> None:
|
|
173
173
|
"""月份连续性(P0-3):本地覆盖区间内的中间月缺失即抛错——静默丢整月数据。
|
|
174
174
|
|
|
175
|
+
仅适用于日频表。稀疏事件/报告期表(month_continuity="skip")走
|
|
176
|
+
_check_sparse_files(DC05, review 260918)。
|
|
175
177
|
区间外(数据开始前/结束后)的月份不要求(新 dataset 起点晚是正常的)。
|
|
176
178
|
"""
|
|
177
179
|
months = sorted(int(m.group(1)) for f in files
|
|
@@ -183,6 +185,8 @@ def _check_month_continuity(files: list[Path], dataset: str) -> None:
|
|
|
183
185
|
nxt = cur + 1 if cur % 100 < 12 else (cur // 100 + 1) * 100 + 1
|
|
184
186
|
while nxt < ym:
|
|
185
187
|
gaps.append(nxt)
|
|
188
|
+
# 12 月跳月必须基于 nxt 自身推进——用 cur 会在缺口跨年时
|
|
189
|
+
# 把 nxt 跳回 cur 的下一年 1 月,形成死循环(DC05 排查时发现)
|
|
186
190
|
nxt = nxt + 1 if nxt % 100 < 12 else (nxt // 100 + 1) * 100 + 1
|
|
187
191
|
cur = ym
|
|
188
192
|
if gaps:
|
|
@@ -192,24 +196,127 @@ def _check_month_continuity(files: list[Path], dataset: str) -> None:
|
|
|
192
196
|
f"请 sync 修复或检查数据")
|
|
193
197
|
|
|
194
198
|
|
|
199
|
+
def _check_sparse_files(
|
|
200
|
+
dataset: str, cfg: SyncConfig, start: date | None = None, end: date | None = None,
|
|
201
|
+
) -> None:
|
|
202
|
+
"""稀疏事件/报告期表的完整性校验(DC05,review 260918)。
|
|
203
|
+
|
|
204
|
+
事件表(分红/持仓披露等)合法无事件月不落文件,月份连续性检查会把
|
|
205
|
+
全历史读取误判为缺月。改为对照 sync manifest:manifest 记录过"已同步"
|
|
206
|
+
的文件若在本地消失,才是真正丢文件,报错;manifest 无记录(未 sync 过
|
|
207
|
+
或旧版本账本)时只放行——远端清单校验是 sync 的职责。
|
|
208
|
+
"""
|
|
209
|
+
manifest_path = cfg.resolved_local_root() / ".manifest.json"
|
|
210
|
+
if not manifest_path.exists():
|
|
211
|
+
return
|
|
212
|
+
import json as _json
|
|
213
|
+
|
|
214
|
+
try:
|
|
215
|
+
manifest = _json.loads(manifest_path.read_text(encoding="utf-8"))
|
|
216
|
+
except (OSError, ValueError) as exc:
|
|
217
|
+
logger.warning("%s: manifest 损坏,跳过稀疏表文件校验: %s", dataset, exc)
|
|
218
|
+
return
|
|
219
|
+
root = cfg.resolved_local_root()
|
|
220
|
+
expected = {
|
|
221
|
+
rel for rel in manifest
|
|
222
|
+
if isinstance(rel, str) and rel.startswith(dataset + "/")
|
|
223
|
+
and (root / rel).suffix == ".parquet"
|
|
224
|
+
}
|
|
225
|
+
def in_window(rel: str) -> bool:
|
|
226
|
+
match = re.search(r"(\d{6})(?=\.parquet$)", Path(rel).name)
|
|
227
|
+
if match is None:
|
|
228
|
+
return True # cannot prove this publication is outside the request
|
|
229
|
+
ym = int(match.group(1))
|
|
230
|
+
return ((start is None or ym >= start.year * 100 + start.month)
|
|
231
|
+
and (end is None or ym <= end.year * 100 + end.month))
|
|
232
|
+
|
|
233
|
+
missing = sorted(rel for rel in expected if in_window(rel) and not (root / rel).is_file())
|
|
234
|
+
if missing:
|
|
235
|
+
raise FileNotFoundError(
|
|
236
|
+
f"{dataset}: sync manifest 记录的已同步文件本地缺失 {missing[:6]}"
|
|
237
|
+
f"{'...' if len(missing) > 6 else ''}——事件表无事件月不落文件是正常的,"
|
|
238
|
+
f"但已发布文件丢失必须报错,请重新 sync")
|
|
239
|
+
|
|
240
|
+
|
|
195
241
|
def _d(v: date | str | None) -> date | None:
|
|
196
242
|
if v is None or isinstance(v, date):
|
|
197
243
|
return v
|
|
198
244
|
return date.fromisoformat(v)
|
|
199
245
|
|
|
200
246
|
|
|
247
|
+
_IDENT_RE = None # 惰性编译的 where 列名提取正则
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def _where_columns(where: str) -> set[str]:
|
|
251
|
+
"""提取 where 表达式引用的裸列名(不含引号字符串/数字/函数/操作符)。
|
|
252
|
+
|
|
253
|
+
用于判断 where 能否安全下推到某层:只提取 [A-Za-z_][A-Za-z0-9_.]* 形态的
|
|
254
|
+
标识符,排除带引号字符串字面量;带表别名/点路径的取末段。
|
|
255
|
+
"""
|
|
256
|
+
global _IDENT_RE
|
|
257
|
+
import re as _re
|
|
258
|
+
if _IDENT_RE is None:
|
|
259
|
+
_IDENT_RE = _re.compile(r'"([A-Za-z_][A-Za-z0-9_ ]*)"' # 双引号列名
|
|
260
|
+
r"|'[^']*'" # 字符串字面量(丢弃)
|
|
261
|
+
r'|\b([A-Za-z_][A-Za-z0-9_]*)\b') # 裸标识符
|
|
262
|
+
cols: set[str] = set()
|
|
263
|
+
text = where
|
|
264
|
+
for m in _IDENT_RE.finditer(text):
|
|
265
|
+
if m.group(1):
|
|
266
|
+
cols.add(m.group(1))
|
|
267
|
+
text = text.replace(m.group(0), " ", 1) # 双引号列已消费,防再匹配
|
|
268
|
+
elif m.group(2):
|
|
269
|
+
cols.add(m.group(2))
|
|
270
|
+
sql_keywords = {"and", "or", "not", "in", "is", "null", "between", "like",
|
|
271
|
+
"true", "false", "date", "timestamp"}
|
|
272
|
+
return {c for c in cols if c.lower() not in sql_keywords}
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def _layer_pushable_where(meta: "LayerMeta", hits: list[str], where: str | None) -> str | None:
|
|
276
|
+
"""where 仅在引用列全部为该层可用列时下推(否则跳过,由外层 JOIN 后过滤)。
|
|
277
|
+
|
|
278
|
+
可用列 = 键列 + 命中列 + 层已声明物理列(columns 已知时)。列未知的层
|
|
279
|
+
不下推 where(保守,正确性优先)——修复跨层筛选把 pe 条件推进只有
|
|
280
|
+
momentum 的层导致的 DuckDB BinderException(review 2026-09-14 P2)。
|
|
281
|
+
"""
|
|
282
|
+
if not where:
|
|
283
|
+
return None
|
|
284
|
+
available: set[str] = set(hits)
|
|
285
|
+
if meta.date_column:
|
|
286
|
+
available.add(meta.date_column)
|
|
287
|
+
if meta.entity_column:
|
|
288
|
+
available.add(meta.entity_column)
|
|
289
|
+
if meta.columns:
|
|
290
|
+
available.update(meta.columns)
|
|
291
|
+
if not available - {meta.date_column, meta.entity_column}:
|
|
292
|
+
return None # 列未知(本机无 footer)——不下推
|
|
293
|
+
refs = _where_columns(where)
|
|
294
|
+
return where if refs <= available else None
|
|
295
|
+
|
|
296
|
+
|
|
201
297
|
def _date_conditions(meta: LayerMeta, start: date | None,
|
|
202
298
|
end: date | None, where: str | None) -> str:
|
|
299
|
+
# DC06(review 260918):历史月文件 trade_date 可能是 VARCHAR(旧写入路径),
|
|
300
|
+
# 与 DATE 字面量直接比较会抛 Binder 异常。CAST 到 DATE 统一谓词类型;
|
|
301
|
+
# 解析失败会响亮报错(缺失必须报告,不静默丢行),ISO 字符串无副作用。
|
|
302
|
+
date_expr = f"CAST({meta.date_column} AS DATE)"
|
|
203
303
|
conds = []
|
|
204
304
|
if start:
|
|
205
|
-
conds.append(f"{
|
|
305
|
+
conds.append(f"{date_expr} >= DATE '{start.isoformat()}'")
|
|
206
306
|
if end:
|
|
207
|
-
conds.append(f"{
|
|
307
|
+
conds.append(f"{date_expr} <= DATE '{end.isoformat()}'")
|
|
208
308
|
if where:
|
|
209
309
|
conds.append(f"({where})")
|
|
210
310
|
return " AND ".join(conds)
|
|
211
311
|
|
|
212
312
|
|
|
313
|
+
def _dated_source(files: list[Path], meta: LayerMeta) -> str:
|
|
314
|
+
"""Normalize the date before user predicates, joins and projections."""
|
|
315
|
+
col = '"' + meta.date_column.replace('"', '""') + '"'
|
|
316
|
+
return (f"(SELECT * REPLACE (CAST({col} AS DATE) AS {col}) "
|
|
317
|
+
f"FROM read_parquet({_sql_str_list(files)}, union_by_name=true))")
|
|
318
|
+
|
|
319
|
+
|
|
213
320
|
def load_dataset(
|
|
214
321
|
dataset: str,
|
|
215
322
|
start: date | str | None = None,
|
|
@@ -232,6 +339,10 @@ def load_dataset(
|
|
|
232
339
|
if meta is None:
|
|
233
340
|
meta = _meta(dataset, cfg)
|
|
234
341
|
start_d, end_d = _d(start), _d(end)
|
|
342
|
+
if meta.month_continuity == "skip":
|
|
343
|
+
# Even on_missing=empty cannot turn a known lost publication into a
|
|
344
|
+
# legitimate month with no events. Check before the no-files return.
|
|
345
|
+
_check_sparse_files(dataset, cfg, start_d, end_d)
|
|
235
346
|
files = _month_files(
|
|
236
347
|
cfg.resolved_local_root() / dataset / "monthly", start_d, end_d,
|
|
237
348
|
allow_missing=(on_missing == "empty"),
|
|
@@ -240,7 +351,8 @@ def load_dataset(
|
|
|
240
351
|
if on_missing == "empty":
|
|
241
352
|
return pd_empty(columns, meta.date_column)
|
|
242
353
|
raise FileNotFoundError(f"{dataset}: 日期范围内无本地数据文件")
|
|
243
|
-
|
|
354
|
+
if meta.month_continuity != "skip":
|
|
355
|
+
_check_month_continuity(files, dataset)
|
|
244
356
|
|
|
245
357
|
cols = list(columns) if columns else None
|
|
246
358
|
if cols and meta.date_column not in cols:
|
|
@@ -248,10 +360,11 @@ def load_dataset(
|
|
|
248
360
|
select = ", ".join(f'"{c}"' for c in cols) if cols else "*"
|
|
249
361
|
cond = _date_conditions(meta, start_d, end_d, where)
|
|
250
362
|
where_sql = f" WHERE {cond}" if cond else ""
|
|
363
|
+
# DC06:ORDER BY 同样 CAST——union_by_name 下混型日期列(string/date32)
|
|
364
|
+
# 直接排序会和 WHERE 一样抛 Binder 异常
|
|
251
365
|
sql = (
|
|
252
|
-
f"SELECT {select} FROM
|
|
253
|
-
f"{
|
|
254
|
-
f"ORDER BY {meta.date_column}"
|
|
366
|
+
f"SELECT {select} FROM {_dated_source(files, meta)}{where_sql} "
|
|
367
|
+
f"ORDER BY CAST({meta.date_column} AS DATE)"
|
|
255
368
|
+ (f", {meta.entity_column}" if meta.entity_column else "")
|
|
256
369
|
)
|
|
257
370
|
return _normalize_frame(_apply_dtype_contract(
|
|
@@ -383,24 +496,30 @@ def load_view(
|
|
|
383
496
|
LogicalFactorView 语义:filters 由各层文件 schema 自行解析,
|
|
384
497
|
支持引用各层自有列,如 close 在量价层)。
|
|
385
498
|
"""
|
|
499
|
+
if meta.month_continuity == "skip":
|
|
500
|
+
_check_sparse_files(meta.name, cfg, start_d, end_d)
|
|
386
501
|
files = _month_files(root / meta.name / "monthly", start_d, end_d,
|
|
387
502
|
allow_missing=True)
|
|
388
503
|
if not files:
|
|
389
504
|
return None
|
|
505
|
+
# C02/DC-READ-01:参与 JOIN 的每层都查月份连续性(此前只查驱动层)——
|
|
506
|
+
# 中间缺月会被 LEFT JOIN 静默变 NULL,业务缺值与文件未同步不可区分。
|
|
507
|
+
# _check_month_continuity 只查现有文件集内部空洞,层合法晚起不误判
|
|
508
|
+
if meta.month_continuity != "skip":
|
|
509
|
+
_check_month_continuity(files, meta.name)
|
|
390
510
|
hits = hits_map.get(meta.name, [])
|
|
391
|
-
|
|
511
|
+
# P2 修复(review 2026-09-14):where 只下推到列覆盖的层做剪枝;
|
|
512
|
+
# 不覆盖的层仅日期裁剪,过滤条件由外层 JOIN 后统一应用
|
|
513
|
+
cond = _date_conditions(meta, start_d, end_d,
|
|
514
|
+
_layer_pushable_where(meta, hits, where))
|
|
392
515
|
w = f" WHERE {cond}" if cond else ""
|
|
393
|
-
inner =
|
|
394
|
-
f"union_by_name=true){w}")
|
|
516
|
+
inner = f"SELECT * FROM {_dated_source(files, meta)}{w}"
|
|
395
517
|
return inner, hits
|
|
396
518
|
|
|
397
519
|
# 驱动层固定 = 视图注册首层(与 LogicalFactorView 的 base 语义一致):
|
|
398
520
|
# 行集稳定不随投影变化;首层**必需**——缺文件时 empty 模式返回空表、
|
|
399
521
|
# raise 模式报错,绝不静默换驱动层(换驱动=换行集)。
|
|
400
522
|
base_meta = layer_metas.get(view.layers[0])
|
|
401
|
-
_check_month_continuity(
|
|
402
|
-
_month_files(root / view.layers[0] / "monthly", start_d, end_d,
|
|
403
|
-
allow_missing=True), view.layers[0])
|
|
404
523
|
base_sub = _layer_sub(base_meta) if base_meta is not None else None
|
|
405
524
|
if base_sub is None:
|
|
406
525
|
if on_missing == "empty":
|
|
@@ -411,8 +530,13 @@ def load_view(
|
|
|
411
530
|
# P1-8: 只请求键列 → 直接返回驱动层键行集(对齐旧实现)
|
|
412
531
|
if not wanted:
|
|
413
532
|
drive_sql = base_sub[0]
|
|
414
|
-
|
|
415
|
-
|
|
533
|
+
# DC06:日期键排序 CAST,防混型月文件 Binder 异常
|
|
534
|
+
order_exprs = [f"CAST({c} AS DATE)" if c == anchor.date_column else c
|
|
535
|
+
for c in key_cols]
|
|
536
|
+
sql = (f"SELECT {', '.join(key_cols)} FROM ({drive_sql}) a "
|
|
537
|
+
f"ORDER BY {', '.join(order_exprs)}")
|
|
538
|
+
return _normalize_frame(_apply_dtype_contract(
|
|
539
|
+
_conn().sql(sql).df(), {anchor.date_column: "date32"}))
|
|
416
540
|
|
|
417
541
|
# 命中层(非驱动)缺文件必须抛错(对齐旧 MissingLayerError 语义):
|
|
418
542
|
# 静默跳过会让结果缺请求列——批量因子读取静默减因子,比报错危险。
|
|
@@ -442,7 +566,14 @@ def load_view(
|
|
|
442
566
|
select_parts.extend(f"{alias}.{c}" for c in hits)
|
|
443
567
|
|
|
444
568
|
sql = f"SELECT {', '.join(select_parts)} FROM ({drive_sql}) a{joins}"
|
|
445
|
-
|
|
569
|
+
# 外层统一过滤(P2):层内下推只是剪枝优化,语义过滤在这里兜底——
|
|
570
|
+
# 跨层 where(如 pe 在 t1、momentum 在驱动层)此前既炸层又无外层应用
|
|
571
|
+
if where:
|
|
572
|
+
sql += f" WHERE ({where})"
|
|
573
|
+
# DC06:日期键排序 CAST,防混型月文件 Binder 异常
|
|
574
|
+
order_exprs = [f"CAST({c} AS DATE)" if c == anchor.date_column else c
|
|
575
|
+
for c in key_cols]
|
|
576
|
+
sql += f" ORDER BY {', '.join(order_exprs)}"
|
|
446
577
|
# 契约并集先到先得(与列归属同序:归属层的契约生效)
|
|
447
578
|
view_dtypes: dict[str, str] = {}
|
|
448
579
|
for name in view.layers:
|