feat(pipeline): todos 补「因子晋级评审候选」源——触点②另一半上板(assessable 且近窗 t≥REVIEW_T_GATE=2,与评审页下拉同门);待办板「N 件等你决策」与评审页待办同口径,修三矛盾②(评审页等五问/待办却 0 件) [vps] [no-doc]
This commit is contained in:
@@ -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())
|
||||
|
||||
@@ -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("")
|
||||
|
||||
Reference in New Issue
Block a user