feat(pipeline): todos 补「因子晋级评审候选」源——触点②另一半上板(assessable 且近窗 t≥REVIEW_T_GATE=2,与评审页下拉同门);待办板「N 件等你决策」与评审页待办同口径,修三矛盾②(评审页等五问/待办却 0 件) [vps] [no-doc]

This commit is contained in:
2026-10-08 10:15:50 +08:00
parent 196e046187
commit 118c9dfa57
2 changed files with 81 additions and 0 deletions
+28
View File
@@ -78,6 +78,11 @@ def _monthly_dir() -> str:
os.path.join("reports", "factor_monthly"))
# 因子晋级评审门:近窗 t≥2 才值得人评五问(与前端 PromotionReview 的
# promotionFilter.REVIEW_T_GATE 同值镜像,改门须两处同改+spec 注记)
REVIEW_T_GATE = 2.0
def _replay_view(doc: dict | None) -> dict:
"""verdict_replay.json → 前端 replay 形状;无历史触发点 → 功课累积提示."""
from sanguo_portfolio import verdict_records
@@ -299,6 +304,29 @@ def todos() -> dict:
"detail": f"周数 {weeks}/{shadow_total_weeks(e)}",
"path": "/pipeline/incubation", "severity": "warn",
"updatedAt": now})
# ①′ 因子晋级评审候选(触点②另一半,10-08 补源):可判且近窗 t≥2
# = 评审页下拉同门——待办板与评审页从此同口径,「N 件等你决策」说真话
from sanguo_factor import version_registry as vr
from sanguo_portfolio.strategy_registry import ensure_runtime_registry
try:
freg = vr.load_registry(ensure_runtime_registry(
_factor_registry_path(),
os.path.join("config", "factor_registry.yaml")))
points, _ = _eval_latest_points()
for fname, fe in freg["factors"].items():
if fe.get("status") != "assessable":
continue
t = (points.get(fname) or {}).get("t")
if t is None or t < REVIEW_T_GATE:
continue
items.append({"id": f"review-{fname}", "touchpoint": "promotion",
"title": f"{fname} 待晋级评审(五问)",
"detail": f"可判且近窗 t={t:.2f}≥{REVIEW_T_GATE:.0f},"
"评审页等你判定",
"path": "/pipeline/review", "severity": "warn",
"updatedAt": now})
except Exception: # noqa: BLE001 注册表/eval 库异常不拖垮待办聚合
pass
# ② 因子衰减告警+③ 集体水位(本机最新月度批评 JSON;最新有效 JSON,损坏跳过)
from sanguo_factor.verdict_replay import load_latest_monthly
rep = load_latest_monthly(_monthly_dir())
+53
View File
@@ -612,3 +612,56 @@ def test_attribution_factor_contribution_missing_file(client, token, env):
# env 注入目录存在但空 → [];目录整个不存在同路径 fail-soft 返 [] 不 500
doc = client.get("/api/v1/pipeline/attribution", headers=_h(token)).json()
assert doc["factorContribution"] == []
def test_todos_factor_review_candidates(client, token, env, tmp_path, monkeypatch):
# 三矛盾②:触点②补源——assessable 且近窗 t≥2 → 待办「待晋级评审」;
# 无 IC 数据/t 不显著/非 assessable 一律不上板(与评审页下拉同门)
monkeypatch.setenv("SANGUO_FACTOR_REGISTRY",
str(tmp_path / "factor_registry.yaml"))
with open(tmp_path / "factor_registry.yaml", "w", encoding="utf-8") as f:
yaml.safe_dump({"factors": {
"fa_hot": {"name": "fa_hot", "hypothesis": "H-1",
"status": "assessable", "versions": []},
"fa_nodata": {"name": "fa_nodata", "hypothesis": "H-2",
"status": "assessable", "versions": []},
"fa_weak": {"name": "fa_weak", "hypothesis": "H-3",
"status": "assessable", "versions": []},
"fa_prom": {"name": "fa_prom", "hypothesis": "H-4",
"status": "promoted", "promotion_t": 3.0,
"versions": []},
}}, f, allow_unicode=True)
monkeypatch.setenv("SANGUO_FACTOR_EVAL_DB",
str(tmp_path / "no_such_eval.db"))
from datetime import date
from sanguo_api.routes_pipeline import set_pipeline_eval_db_path
from sanguo_factor import eval_store
inj_db = str(tmp_path / "factor_eval.db")
eval_store.init_db(inj_db)
end = date.today().isoformat()
run_id = eval_store.create_run(inj_db, "monthly_2026-09", "sz", 10, 1,
"2025-10-01", end, {})
eval_store.save_results(inj_db, run_id, [
{"factor": "fa_hot", "category": "fundamental", "expression": "x",
"metrics": {"1": {"t_stat": 3.4, "ic_mean": 0.03, "count": 240}}},
{"factor": "fa_weak", "category": "fundamental", "expression": "y",
"metrics": {"1": {"t_stat": -0.2, "ic_mean": 0.001, "count": 240}}},
])
eval_store.finish_run(inj_db, run_id, "done", 1)
set_pipeline_eval_db_path(inj_db)
try:
r = client.get("/api/v1/pipeline/todos", headers=_h(token))
assert r.status_code == 200
items = r.json()["items"]
rev = [i for i in items if i["id"] == "review-fa_hot"]
assert len(rev) == 1
assert rev[0]["touchpoint"] == "promotion"
assert rev[0]["path"] == "/pipeline/review"
assert rev[0]["severity"] == "warn"
assert "3.40" in rev[0]["detail"] or "3.4" in rev[0]["detail"]
ids = [i["id"] for i in items]
assert "review-fa_nodata" not in ids # 无 IC 数据不上板
assert "review-fa_weak" not in ids # t<2 不上板
assert "review-fa_prom" not in ids # 非 assessable 不上板
finally:
set_pipeline_eval_db_path("")