fix(factor): 缺口开单权威单机制——maybe_open_gap_issue 增 host 参 vps 侧结构性拒开(NAS 单侧权威,关死双机同日竞态双开)+data_gap_check/monthly_batch CLI --host 透传+NAS sh/VPS ps1 双 wrapper 声明侧别 (审计 P3-14;runbook+因子档 §7.7) [vps]
This commit is contained in:
@@ -228,7 +228,7 @@ ssh 49.232.102.198 "powershell -Command \"Get-Content C:\sanguo_bigqmt\qmt_relog
|
||||
- **反向镜像链(融合 spec §20.11,2026-09-17 新增)**:`push_vps_mirror_daily.sh`+`vps_mirror_lib.py`(纯逻辑,宿主 python3.8 stdlib)——corpus **五子域**(09-22 起 +flash_meta 快讯域)+dmsk 五域 NAS→VPS **推送**(09-18 起挂 `sanguo-corpus-daily` DSM 任务尾部=`run_corpus_standalone.sh` EXIT trap 完赛即推,典型 ~10:1x/backfill 日 ~17:3x;rc 隔离=corpus rc∈0/1/3 才推[09-19 首验勘误:rc() 把「墙停+噪声 failed」报 1,旧 0/3 闸门 11 天只推 1 天,仅 rc=2 限流不推]、推送失败只记日志不进聚合 rc;**admin 账号**=避开 root 属主坑;原独立 16:00 DSM 方案作废),分区粒度 journal 指纹 diff 只增不删;部署位 `/volume1/stock/vps-mirror/`(宿主文件,改 repo 副本后 scp -O 同步,同 corpus/dmsk runner 纪律),journal/watermark_*.json/push.log 同目录;VPS 落 `data\corpus`+`data\fundamentals\dmsk`+族根 `_mirror_watermark.json`(消费方 `get_watermark()` 晨检对表);任一失败不 commit journal/不落水位线=次日 diff 自愈;`--dry-run` 打印将推送清单=验收收敛证明。设计与消费契约=融合 spec §20.11。09-20 增**独立 static manifest lane**(spec §20.11.7):`data_backup/static_overlay/manifests/*.list`(补洗变更清单,每行 `<domain>/<file>`)文件级幂等推送、整单成功归档 pushed/、失败只 WARN 不影响 corpus/dmsk 聚合——三表 per-stock 文件由此 lane 推 VPS(不进 journal 指纹体系)。
|
||||
- **flash 快讯 lane+xcheck 对账 lane(融合 spec §20.12.3/§20.12.4,09-22 上线)**:①`run_corpus_standalone.sh` 第三 lane `--lane flash`(金十快照+东财 kuaixun 翻页 T-1 边界,秒级完赛,rc 隔离不进聚合、独立跑不 gate 于 daily/backfill)→ corpus/flash_meta/ 落盘,镜像车五子域自动携带;②`run_corpus_standalone.sh` 第四 lane `xcheck.py --lane corpus_side`(ann_em 拉取+公告披露日对照+腾讯 qt 全市场估值快照对照,rc 隔离);③`run_dmsk.sh` 第四 lane `xcheck.py --lane fund_side`(读 wash manifests 当日命中股→新浪三表近 12 期+两源 NOTICE_DATE 对照)。xcheck 域落 `corpus/xcheck/` **不推 VPS**(NAS 侧质检设施);findings 报告+trace 追溯=融合 spec §20.12.4;容器名 sanguo-xcheck(杀=`docker stop sanguo-xcheck`)。首跑锚点:corpus cron.log `flash lane exit=0(隔离)` 行 + `xcheck corpus_side exit=0` 行 + dmsk cron.log `xcheck fund_side exit=0` 行 + 次日 push.log 出现 flash_meta 分区计数。
|
||||
- **funnel 漏斗 lane+integrity gate lane(融合 spec §20.16/流水线 spec §4.8 P4-3,10-02 上线)**:`run_corpus_standalone.sh` 第五/六段——⑤`corpus_funnel.py --until 23:00 --llm-cap 200 --token-budget 3000000 --no-llm`(第一层词表八类事件零成本全量→events_llm 直落;第二层 LLM 只碰词表 miss:候选入 `state/funnel_pending.jsonl` 队列、串行慢爬、token 预算断路 rc=3 次夜续——**10-05 审计 P2-16 前该断路仅文档宣称未接线,现已实接 3M=每夜费用上界**(≈整夜墙钟吞吐量级,正常夜 <0.5M 不触;backlog 夜 checkpoint 次夜续);水位 `state/funnel_watermark.json` 只前向,历史回填 `--start` 按预算分批)⑥`integrity_gate.py --profile default`(三层门 crit exit1/warn exit2)。两段 rc 隔离不进聚合。**LLM env 三件与 P4-1 向导同款(§运维表 288 行)**:DSM 任务环境或 run_corpus.sh 头 export 后由 wrapper 条件传递进容器;缺 key 且当晚有候选 → funnel rc=1(fail loud,非故障面=未配 env)。冒烟:corpus cron.log `funnel exit=0` 行+`integrity gate exit=0` 行+`corpus/events_llm/dt=*/` 分区出现;`--no-llm` 可只验第一层。容器名 sanguo-funnel/sanguo-igate(杀=docker stop)。**10-05 用户拍板(B 案):LLM 层停用待令——`--no-llm` 已常态接线**(bigmodel 余额耗尽 429 熔断后裁决):词表层照跑零成本(events_llm 域 model="lexicon" 行继续落)、llm_calls=0、`funnel_pending.jsonl` 冻结只增不减(当夜新命中照常入队,LLM 不消费);开闸=去该 flag 续消化,`--token-budget 3M` 接线保留复用即生效。**P3-23(10-07):全 lane rc 分档打标**——每 lane exit 行后追加 `[verdict] lane=X rc=N outcome=语义词`(通用 0=ok/125=docker_daemon_down/137=sigkilled/130=interrupted/143=terminated;daily|backfill 1=noise_failed/2=rate_limited/3=wallclock_stop;funnel 1=fail_loud/3=checkpoint[锁让路/墙钟/预算次夜续];gate 1=crit/2=warn),值班/守望 grep 语义位不再裸读 exit 码——10-07 停电夜(daily 137+下游 125)需逐条人肉分诊的教训。
|
||||
- **因子月度批评·先批后评四段链(流水线 spec §4.2 决议 H+§12.4 决议 J,09-23 上线;09-27 加 stage4 归因批 10-04 首班起)**:双机同评——NAS+VPS 每月第一个周日 09:30 各跑同一批评链(①②③段),JSON 对拍件(`{host}_{asof}.json`)落各自报告目录,人工/VPS 侧 scp 汇集到 NAS 后 `python -m sanguo_factor.review_compare nas_*.json vps_*.json` 对拍(一致=落档进告警流程;分歧 exit 2=只开排查 issue 不出降权告警)。ⓐ**NAS 侧**:`run_factor_monthly.sh`(源码=repo `scripts/nas_sync/run_factor_monthly_standalone.sh`,改后 scp -O 同步生效位 `/volume1/stock/sanguo_vnpy_v2/`)——一次性容器四段串行 `monthly_batch`(注册表全量 12M 窗重评批,重活)→`monthly_review --host nas`(判定报告,JSON append-only)→`data_gap_check`→`attribution_batch`(归因批: 15 截面增量[12 源+size_log+在位者+挑战者,daily_section 幂等 merge,as_of 撞周末模块内归一最近 bar 日]→归因双跑[#69 影子 A/B 归因侧供给,挑战者落 challenger/ 子目录]→cp 双投递生效位[在位者件→admin 家 `data/attribution/` 根 glob 取最新;挑战者件带因子名;生效位目录容器挂 rw `/effective-data`,代码卷恒 :ro];工作区 `/volume1/stock/factor_cross_section/attribution/` 留全历史);as_of=上月末脚本头自算;rc 语义=①非零停链、②③ exit 2 是有效信号按 0 聚合、④非零=真失败(截面断供/归因挂/投递缺)进 final;容器金丝雀坑=**必须 `-e HOME=/tmp -e MPLCONFIGDIR=/tmp/mpl`**(--user 1024 下 HOME=/ → vnpy import 建不了 `/.vntrader`)。报告目录 `/volume1/stock/sanguo_vnpy_v2/reports/factor_monthly/`;日志=factor_monthly_cron.log。DSM 任务 `sanguo-factor-monthly`=**每周日 09:30 admin(UI 手建)+脚本头日号闸**——DSM 计划任务月度档只有「每月几号」没有「第一个周日」原生选项,调度只能建每周日,「是不是第一个周日」由脚本头 `dom>7 静默退出` 兜住。ⓑ**VPS 侧**:wrapper=`scripts/factor_research/factor_monthly_wrapper.ps1`(三段同链批评部分,`--host vps`,eval_db=`C:\sanguo_vnpy_v2\data\factor_eval.db`,报告/日志=`C:\sanguo_vnpy_v2\reports\factor_monthly\`;ASCII-only 纪律;**stage4 归因批不镜像**——归因双跑落 NAS 工作区+生效位,VPS 侧归因件走 infra 偏差日报链另行供给);schtask 注册=`scripts/factor_research/register_factor_monthly_task.ps1`(**XML ScheduleByMonthDayOfWeek 原生「每月第一个周日」**,无需日号闸——zh-CN schtasks 不吃星期名组合走 ak-monthly 同款 XML 路线;SYSTEM/不限时,monthly_batch 重活首班实测耗时后再定是否拆任务错时)——vps-deploy 把两文件推上 VPS 后 ssh 跑一次注册脚本即成。首班=部署后第一个周日人工盯全程。
|
||||
- **因子月度批评·先批后评四段链(流水线 spec §4.2 决议 H+§12.4 决议 J,09-23 上线;09-27 加 stage4 归因批 10-04 首班起)**:双机同评——NAS+VPS 每月第一个周日 09:30 各跑同一批评链(①②③段),JSON 对拍件(`{host}_{asof}.json`)落各自报告目录,人工/VPS 侧 scp 汇集到 NAS 后 `python -m sanguo_factor.review_compare nas_*.json vps_*.json` 对拍(一致=落档进告警流程;分歧 exit 2=只开排查 issue 不出降权告警)。ⓐ**NAS 侧**:`run_factor_monthly.sh`(源码=repo `scripts/nas_sync/run_factor_monthly_standalone.sh`,改后 scp -O 同步生效位 `/volume1/stock/sanguo_vnpy_v2/`)——一次性容器四段串行 `monthly_batch`(注册表全量 12M 窗重评批,重活)→`monthly_review --host nas`(判定报告,JSON append-only)→`data_gap_check`→`attribution_batch`(归因批: 15 截面增量[12 源+size_log+在位者+挑战者,daily_section 幂等 merge,as_of 撞周末模块内归一最近 bar 日]→归因双跑[#69 影子 A/B 归因侧供给,挑战者落 challenger/ 子目录]→cp 双投递生效位[在位者件→admin 家 `data/attribution/` 根 glob 取最新;挑战者件带因子名;生效位目录容器挂 rw `/effective-data`,代码卷恒 :ro];工作区 `/volume1/stock/factor_cross_section/attribution/` 留全历史);as_of=上月末脚本头自算;rc 语义=①非零停链、②③ exit 2 是有效信号按 0 聚合、④非零=真失败(截面断供/归因挂/投递缺)进 final;容器金丝雀坑=**必须 `-e HOME=/tmp -e MPLCONFIGDIR=/tmp/mpl`**(--user 1024 下 HOME=/ → vnpy import 建不了 `/.vntrader`)。报告目录 `/volume1/stock/sanguo_vnpy_v2/reports/factor_monthly/`;日志=factor_monthly_cron.log。DSM 任务 `sanguo-factor-monthly`=**每周日 09:30 admin(UI 手建)+脚本头日号闸**——DSM 计划任务月度档只有「每月几号」没有「第一个周日」原生选项,调度只能建每周日,「是不是第一个周日」由脚本头 `dom>7 静默退出` 兜住。ⓑ**VPS 侧**:wrapper=`scripts/factor_research/factor_monthly_wrapper.ps1`(三段同链批评部分,`--host vps`,eval_db=`C:\sanguo_vnpy_v2\data\factor_eval.db`,报告/日志=`C:\sanguo_vnpy_v2\reports\factor_monthly\`;ASCII-only 纪律;**stage4 归因批不镜像**——归因双跑落 NAS 工作区+生效位,VPS 侧归因件走 infra 偏差日报链另行供给);schtask 注册=`scripts/factor_research/register_factor_monthly_task.ps1`(**XML ScheduleByMonthDayOfWeek 原生「每月第一个周日」**,无需日号闸——zh-CN schtasks 不吃星期名组合走 ak-monthly 同款 XML 路线;SYSTEM/不限时,monthly_batch 重活首班实测耗时后再定是否拆任务错时)——vps-deploy 把两文件推上 VPS 后 ssh 跑一次注册脚本即成。首班=部署后第一个周日人工盯全程。10-07 起:双侧 wrapper 月度批/缺口比对调用均带 `--host nas|vps` 侧别声明(P3-14 缺口开单权威单机制——缺口开单仅 NAS 单侧权威,VPS 侧 `--host vps` 结构性拒开,即便日后两侧同配 token+开单 flag 也不会双机同日双开;NAS sh 生效位改动照旧 scp -O 同步,下次链班=11-01 第一个周日前完成即可)。
|
||||
- **static 三表事件驱动补洗(融合 spec §20.11.7,2026-09-20 上线)**:`run_dmsk.sh` 第三 lane `static_overlay_wash.py`(dmsk daily/backfill 后 ~07:2x 起跑,--until 15:00 墙停,rc 隔离)——dmsk NOTICE_DATE 雷达命中股逐股 F10(emweb) 近 12 期 upsert 重写 `static/{income,balance,cashflow}/` per-stock 文件;pending 重试队列(F10 落库滞后/网络失败次日自愈,30 天老化);周日 sync_parquet 全量回冲=雷达窗内自愈、更深归月度全量洗兜底;变更清单→晚间镜像车 manifest lane 推 VPS。
|
||||
- 数据归属口径四原则与 14 条管线全景=融合 spec §20.10(数据面唯一权威查询入口)。
|
||||
|
||||
|
||||
@@ -243,7 +243,7 @@
|
||||
|
||||
审计杂项收敛批(2026-10-05,P3-15/16/17/18/19+T6-N1;上段 defer 台账「pre_gate_table 硬编码 2.0」就此收编):exam_gate 缺周期语义改 fail-closed——1/5/10d 任一周期任一侧缺 ICIR→INSUFFICIENT_PERIODS 不建议晋级(原「缺周期跳过不计」可退化单周期对比出 SUGGEST_PROMOTE,已废);考场报告文件名带 HHMMSS(同日重跑不覆盖,eval run label 不变);pre_gate REVIEW 阈值收编 _PROMOTION_BAR 常量(负 t 侧无镜像门边带=负向晋级不存在,维持现状);v2a 条目补 `evidence_run: ev_20260911_021832_2f68`(=promotion_t 26.32 锚批,NAS factor_eval.db label=composite-v2a-h2,1d t=26.3192 实查核验;**惯例:promoted 条目 promotion_t 必须可溯源到 evidence_run**);composite_library.build_composite_expression docstring 明示等权交集语义(任一源缺→null,分母恒 N 不重归一);data_gap_check 台账坏行(合法 JSON 非 dict)降级跳过+stderr(T6-N1)。
|
||||
|
||||
开工批收编(2026-10-07,#91①⑤+P3-14;计划=plans/2026-10-07-factor-followup-batch.md):exam_gate 缺周期判定扩 isnan——任一侧 ICIR 为 NaN 视同缺失(部分 NaN=INSUFFICIENT_PERIODS、全 NaN=DATA_INSUFFICIENT),上段 defer 台账「首年校准加 isnan」就此提前闭合(#91⑤)。条目级 origin 写入方落地——upsert_factor 增 origin 参(合法 decomposer/manual;建条即落、同名幂等返回不覆盖、非法值拒),decompose 透传 origin=decomposer,P3-11 条目级分支由向前兼容位转正(人工改判退出=yaml 手改 origin: manual;§4.2 表两行同步勘正)(#91①)。
|
||||
开工批收编(2026-10-07,#91①⑤+P3-14;计划=plans/2026-10-07-factor-followup-batch.md):exam_gate 缺周期判定扩 isnan——任一侧 ICIR 为 NaN 视同缺失(部分 NaN=INSUFFICIENT_PERIODS、全 NaN=DATA_INSUFFICIENT),上段 defer 台账「首年校准加 isnan」就此提前闭合(#91⑤)。条目级 origin 写入方落地——upsert_factor 增 origin 参(合法 decomposer/manual;建条即落、同名幂等返回不覆盖、非法值拒),decompose 透传 origin=decomposer,P3-11 条目级分支由向前兼容位转正(人工改判退出=yaml 手改 origin: manual;§4.2 表两行同步勘正)(#91①)。缺口开单权威单机制(P3-14)——maybe_open_gap_issue 增 host 参,vps 侧结构性拒开(开单仅 NAS 单侧权威);data_gap_check/monthly_batch CLI 增 --host 透传,NAS sh 与 VPS ps1 wrapper 双侧声明侧别(台账仍本地 append-only,跨机第一道去重=Gitea 双层查询,本机制=第二道结构性闸)。
|
||||
|
||||
## 8. 前端与任务态
|
||||
|
||||
|
||||
@@ -160,14 +160,21 @@ def append_ledger(path: str, fingerprint: str, issue_number: int,
|
||||
|
||||
def maybe_open_gap_issue(gaps: dict[str, list[str]],
|
||||
manifest_path: str,
|
||||
ledger_path: str = GAP_ISSUE_LEDGER) -> int | None:
|
||||
ledger_path: str = GAP_ISSUE_LEDGER,
|
||||
host: str | None = None) -> int | None:
|
||||
"""有 token 且同指纹无 open 单(台账+网络双层去重)才开;失败降级返 None.
|
||||
|
||||
去重三重(P2-13): ①本地台账命中→跳过不打网络;②open issue 双层查询
|
||||
(首页 LIST+search 兜底)命中→跳过;③查询网络异常→跳过开单本月
|
||||
(stderr 留痕,下月再试)——只有「确认不存在」才真正开单,
|
||||
开单成功后回写台账。
|
||||
P3-14 开单权威单机制: host="vps" 结构性拒开(开单仅 NAS 单侧权威——
|
||||
台账本地不跨机,双机同查同开窗口就此关死);host=None(人工运行)不限.
|
||||
"""
|
||||
if host == "vps":
|
||||
print("[data_gap_check] host=vps 非开单权威侧(P3-14 权威单机制:"
|
||||
"NAS 单侧开单),跳过开单", file=sys.stderr)
|
||||
return None
|
||||
token = os.environ.get("SANGUO_GITEA_TOKEN")
|
||||
if not token:
|
||||
return None
|
||||
@@ -198,6 +205,9 @@ def main(argv: list[str] | None = None) -> int:
|
||||
ap.add_argument("--gitea-open", action="store_true",
|
||||
help="有缺口时尝试开 Gitea issue(需 env SANGUO_GITEA_TOKEN;"
|
||||
"同指纹 open 单存在则跳过)")
|
||||
ap.add_argument("--host", choices=("nas", "vps"), default=None,
|
||||
help="运行侧别声明(P3-14 开单权威单机制: vps 侧结构性"
|
||||
"拒开缺口单,NAS=权威侧;缺省=人工运行不限)")
|
||||
args = ap.parse_args(argv)
|
||||
if not os.path.exists(args.manifest):
|
||||
print(f"[data_gap_check] manifest 不存在: {args.manifest}", file=sys.stderr)
|
||||
@@ -212,7 +222,7 @@ def main(argv: list[str] | None = None) -> int:
|
||||
print(f"[data_gap_check] {len(gaps)} 因子有数据缺口(详见 data_gaps.json): "
|
||||
+ ", ".join(sorted(gaps)))
|
||||
if args.gitea_open:
|
||||
num = maybe_open_gap_issue(gaps, args.manifest)
|
||||
num = maybe_open_gap_issue(gaps, args.manifest, host=args.host)
|
||||
if num:
|
||||
print(f"[data_gap_check] 已开数据缺口 issue #{num}")
|
||||
return 2
|
||||
|
||||
@@ -143,6 +143,9 @@ def main(argv: list[str] | None = None) -> int:
|
||||
help="底座数据域清单(gap 比对供给侧)")
|
||||
ap.add_argument("--gap-issue", action="store_true",
|
||||
help="有数据缺口时尝试开 Gitea issue(决议 M;默认关)")
|
||||
ap.add_argument("--host", choices=("nas", "vps"), default=None,
|
||||
help="运行侧别声明(--gap-issue 开单权威单机制 P3-14: "
|
||||
"vps 侧拒开,NAS=权威侧;缺省=人工运行不限)")
|
||||
ap.add_argument("--vnpy-db", default=None,
|
||||
help="行情库路径 override(双机 cfg 路径各异时直传,"
|
||||
"缺省走 config 解析)")
|
||||
@@ -191,7 +194,7 @@ def main(argv: list[str] | None = None) -> int:
|
||||
+ "; ".join(f"{f}→{','.join(s)}" for f, s in sorted(gaps.items())),
|
||||
file=sys.stderr)
|
||||
if args.gap_issue:
|
||||
num = dgc.maybe_open_gap_issue(gaps, args.manifest)
|
||||
num = dgc.maybe_open_gap_issue(gaps, args.manifest, host=args.host)
|
||||
if num:
|
||||
print(f"[monthly_batch] 已开数据缺口 issue #{num}")
|
||||
print(f"[monthly_batch] {len(plan['factor_names'])} 因子 "
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
# No SANGUO_GITEA_TOKEN needed here: the monthly chain never calls the Gitea
|
||||
# API (J1 issue opening lives in `python -m sanguo_factor.version_registry
|
||||
# transition --gitea-open`, a manual promotion action).
|
||||
# 2026-10-07: --host vps also refuses gap-issue opening structurally
|
||||
# (P3-14 single-side authority: only NAS opens gap issues).
|
||||
|
||||
$env:http_proxy = ''
|
||||
$env:https_proxy = ''
|
||||
@@ -45,7 +47,7 @@ if (-not (Test-Path $OUT_DIR)) { New-Item -ItemType Directory -Path $OUT_DIR -Fo
|
||||
$stamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
|
||||
Add-Content -Path $LOG -Value "=== $stamp factor-monthly start (as_of=$asof) ==="
|
||||
|
||||
& $PY -X utf8 -m sanguo_factor.monthly_batch --as-of $asof --registry $REGISTRY --eval-db $EVAL_DB --vnpy-db $VNDB --fund-data-dir $FUND_DIR --report-dir $OUT_DIR *>> $LOG
|
||||
& $PY -X utf8 -m sanguo_factor.monthly_batch --as-of $asof --registry $REGISTRY --eval-db $EVAL_DB --vnpy-db $VNDB --fund-data-dir $FUND_DIR --report-dir $OUT_DIR --host vps *>> $LOG
|
||||
$rcBatch = $LASTEXITCODE
|
||||
$stamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
|
||||
Add-Content -Path $LOG -Value "=== $stamp stage1 monthly_batch exit=$rcBatch ==="
|
||||
@@ -59,7 +61,7 @@ $rcReview = $LASTEXITCODE
|
||||
$stamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
|
||||
Add-Content -Path $LOG -Value "=== $stamp stage2 monthly_review exit=$rcReview (2=alert/collective signal, valid) ==="
|
||||
|
||||
& $PY -X utf8 -m sanguo_factor.data_gap_check --registry $REGISTRY --manifest $MANIFEST --out-dir $OUT_DIR *>> $LOG
|
||||
& $PY -X utf8 -m sanguo_factor.data_gap_check --registry $REGISTRY --manifest $MANIFEST --out-dir $OUT_DIR --host vps *>> $LOG
|
||||
$rcGap = $LASTEXITCODE
|
||||
$stamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
|
||||
Add-Content -Path $LOG -Value "=== $stamp stage3 data_gap_check exit=$rcGap (2=gap signal, valid) ==="
|
||||
|
||||
@@ -78,7 +78,8 @@ PY
|
||||
-w /app \
|
||||
sanguo_vnpy_v2:lock-aligned \
|
||||
-m sanguo_factor.monthly_batch --as-of "$AS_OF" \
|
||||
--registry "$REGISTRY" --eval-db "$EVAL_DB" --report-dir "$OUT_DIR"
|
||||
--registry "$REGISTRY" --eval-db "$EVAL_DB" --report-dir "$OUT_DIR" \
|
||||
--host nas
|
||||
rc_batch=$?
|
||||
echo "=== $(date '+%F %T') stage1 monthly_batch exit=$rc_batch ==="
|
||||
if [ "$rc_batch" -ne 0 ]; then
|
||||
@@ -106,7 +107,8 @@ PY
|
||||
-w /app \
|
||||
sanguo_vnpy_v2:lock-aligned \
|
||||
-m sanguo_factor.data_gap_check \
|
||||
--registry "$REGISTRY" --manifest "$MANIFEST" --out-dir "$OUT_DIR"
|
||||
--registry "$REGISTRY" --manifest "$MANIFEST" --out-dir "$OUT_DIR" \
|
||||
--host nas
|
||||
rc_gap=$?
|
||||
echo "=== $(date '+%F %T') stage3 data_gap_check exit=$rc_gap (2=缺口开issue,有效) ==="
|
||||
|
||||
|
||||
@@ -301,3 +301,49 @@ def test_load_ledger_fps_tolerates_non_dict_json_line(tmp_path, capsys):
|
||||
encoding="utf-8")
|
||||
assert dgc.load_ledger_fps(str(p)) == {"abcd1234"}
|
||||
assert "非 dict" in capsys.readouterr().err
|
||||
|
||||
|
||||
# —— P3-14: 开单权威单机制(host=vps 结构性拒开,关死双机同日竞态双开) ——
|
||||
def test_maybe_open_host_vps_refuses_even_with_token(monkeypatch, tmp_path,
|
||||
capsys):
|
||||
import sanguo_factor.data_gap_check as dgc
|
||||
monkeypatch.setenv("SANGUO_GITEA_TOKEN", "tok")
|
||||
|
||||
def _boom(*a, **k):
|
||||
raise AssertionError("host=vps 不得触网查询/开单")
|
||||
monkeypatch.setattr(dgc, "find_open_issue", _boom)
|
||||
monkeypatch.setattr(dgc, "open_gitea_issue", _boom)
|
||||
assert dgc.maybe_open_gap_issue(
|
||||
{"fa_x": ["s_new"]}, "m.yaml",
|
||||
ledger_path=str(tmp_path / "ledger.jsonl"), host="vps") is None
|
||||
assert "权威侧" in capsys.readouterr().err
|
||||
|
||||
|
||||
def test_maybe_open_host_nas_keeps_current_behavior(monkeypatch, tmp_path):
|
||||
import sanguo_factor.data_gap_check as dgc
|
||||
monkeypatch.setenv("SANGUO_GITEA_TOKEN", "tok")
|
||||
monkeypatch.setattr(dgc, "find_open_issue", lambda *a, **k: None)
|
||||
opened = []
|
||||
monkeypatch.setattr(dgc, "open_gitea_issue",
|
||||
lambda *a, **k: opened.append(a) or 7)
|
||||
assert dgc.maybe_open_gap_issue(
|
||||
{"fa_x": ["s_new"]}, "m.yaml",
|
||||
ledger_path=str(tmp_path / "ledger.jsonl"), host="nas") == 7
|
||||
|
||||
|
||||
def test_main_host_flag_wires_into_maybe_open(tmp_path, monkeypatch):
|
||||
_, reg_p = _reg(tmp_path)
|
||||
mf = tmp_path / "manifest.yaml"
|
||||
mf.write_text("domains: {pledge_detail: {desc: x}}\n", encoding="utf-8")
|
||||
seen = {}
|
||||
|
||||
def _fake(gaps, manifest_path, **kw):
|
||||
seen.update(kw)
|
||||
return None
|
||||
import sanguo_factor.data_gap_check as dgc
|
||||
monkeypatch.setattr(dgc, "maybe_open_gap_issue", _fake)
|
||||
rc = dgc.main(["--registry", reg_p, "--manifest", str(mf),
|
||||
"--out-dir", str(tmp_path / "out"),
|
||||
"--gitea-open", "--host", "vps"])
|
||||
assert rc == 2 # 拒开不影响缺口信号 rc
|
||||
assert seen.get("host") == "vps"
|
||||
|
||||
@@ -233,7 +233,7 @@ def test_main_reports_gaps_and_opens_issue(tmp_path, capsys, monkeypatch):
|
||||
"factors_done": 1})
|
||||
opened = []
|
||||
monkeypatch.setattr(dgc, "maybe_open_gap_issue",
|
||||
lambda gaps, mp: opened.append(gaps) or 3)
|
||||
lambda gaps, mp, **kw: opened.append(gaps) or 3)
|
||||
rc = mb.main(["--registry", str(reg), "--as-of", "2026-10-04",
|
||||
"--eval-db", str(tmp_path / "e.db"),
|
||||
"--manifest", str(mani), "--gap-issue",
|
||||
|
||||
Reference in New Issue
Block a user