93 lines
4.6 KiB
Python
93 lines
4.6 KiB
Python
"""计划装配接入逻辑状态四态的离线单测(不连库)。
|
||
|
||
钉住三件事:
|
||
一,四态随每张卡一起产出,并按约定的形状发给下游(每路带截止日与一句话说明)。
|
||
二,四态**不改判决**。候选卡的三门槛判决与逻辑四态是正交的两维,收敛规则是单调的:
|
||
逻辑强化只能提前卡内序,永远不能把判决往上升一档;逻辑存疑只能改分流通道,
|
||
不能把仅展示变成可执行。
|
||
三,取数任一路读不到都不让计划断产,那一路记缺失。
|
||
|
||
跑法:python3 test_plan_logic_state.py 或 pytest test_plan_logic_state.py
|
||
"""
|
||
import logic_state as ls
|
||
import plan
|
||
|
||
DS = "2026-09-03"
|
||
|
||
|
||
def t(name, cond):
|
||
assert cond, name
|
||
print(" ok", name)
|
||
|
||
|
||
def claim(date, direction="利好"):
|
||
return {"disclosure_date": date, "direction": direction, "mechanism": "机制",
|
||
"doc_title": "研报", "claim_id": "c" + date.replace("-", "")}
|
||
|
||
|
||
def jrow(leaning="偏多", verified=1, migrated=0, stale=3, seg="固态电解质"):
|
||
return {"leaning": leaning, "leaning_prev": None, "migrated": migrated,
|
||
"stale_days": stale, "verified": verified, "review_date": "2026-09-03",
|
||
"n_materials": 43, "subject_name": seg, "segment_name": seg,
|
||
"n_bull": 3, "n_bear": 3}
|
||
|
||
|
||
def main():
|
||
print("四态随卡产出")
|
||
evd = {"logic": [claim("2026-08-25")]}
|
||
seg_of = {"SH600000": ["固态电解质"]}
|
||
seg_view = {"固态电解质": jrow()}
|
||
broker = {"SH600000": ls.signal(ls.PATH_BROKER, ls.SIG_FLAT, as_of=DS,
|
||
coverage=6, why="预测中位数变化 +1%,在阈值之内")}
|
||
st = plan._logic_state_of("SH600000", evd, seg_of, seg_view, broker, DS) # noqa: SLF001
|
||
t("三路都在且无负面 -> 逻辑成立", st["state"] == ls.STATE_HOLD)
|
||
t("四路都记了名,缺的那一路写明是公司事件",
|
||
len(st["paths"]) == 4 and ls.PATH_EVENT in st["missing"])
|
||
|
||
print("取数缺席时不断产")
|
||
st = plan._logic_state_of("SH600000", {}, {}, {}, {}, DS) # noqa: SLF001
|
||
t("四路全缺 -> 无法判断加证据不足,不抛异常",
|
||
st["state"] == ls.STATE_UNKNOWN and st["why"] == ls.WHY_THIN)
|
||
t("缺失名单写全了四路", len(st["missing"]) == 4)
|
||
|
||
st = plan._logic_state_of("SH999999", evd, seg_of, seg_view, broker, DS) # noqa: SLF001
|
||
t("这只票不在任何被指向环节上 -> 产业研判缺席,其余照算",
|
||
ls.PATH_JUDGE in st["missing"] and ls.PATH_CLAIM in st["usable"])
|
||
|
||
print("一票挂多个环节")
|
||
st = plan._logic_state_of( # noqa: SLF001
|
||
"SH600000", evd, {"SH600000": ["没评过的环节", "固态电解质"]}, seg_view, broker, DS)
|
||
t("取第一个有行业观点的那个环节", ls.PATH_JUDGE in st["usable"])
|
||
|
||
print("发给下游的形状")
|
||
out = plan._state_out(st) # noqa: SLF001
|
||
t("状态、子因、截止日都在", set(out) >= {"state", "why", "as_of", "usable", "missing",
|
||
"reasons", "paths"})
|
||
t("每一路都带自己的截止日与一句话说明",
|
||
all(set(p) == {"path", "signal", "as_of", "why"} for p in out["paths"]))
|
||
t("不发内部中间量(硬触发标记、出处原值不外泄)",
|
||
all("hard" not in p and "refs" not in p for p in out["paths"]))
|
||
t("没有状态时发 None,不硬拼一个空壳", plan._state_out(None) is None) # noqa: SLF001
|
||
|
||
print("四态不改判决(收敛规则单调)")
|
||
for state in (ls.STATE_STRONG, ls.STATE_HOLD, ls.STATE_UNKNOWN, ls.STATE_DOUBT):
|
||
for verdict in ("候选", "关注", "仅展示"):
|
||
r = ls.apply_to_card(verdict, state)
|
||
assert r["verdict"] == verdict, (verdict, state, r)
|
||
t("十二种组合逐个扫过,判决一次都没被四态改动", True)
|
||
t("逻辑强化最多提前卡内序", ls.apply_to_card("关注", ls.STATE_STRONG)["rank_bonus"] == 1)
|
||
t("逻辑存疑不把仅展示变成可执行",
|
||
not ls.apply_to_card("仅展示", ls.STATE_DOUBT)["force_confirm"])
|
||
t("候选加逻辑存疑是唯一需要新语义的一格:强制人工确认",
|
||
ls.apply_to_card("候选", ls.STATE_DOUBT)["force_confirm"])
|
||
|
||
print("计划日与数据日差一天")
|
||
t("次日推算正确", plan._next_day("2026-09-03") == "2026-09-04") # noqa: SLF001
|
||
t("认不出的日期原样返回,不抛异常", plan._next_day("不是日期") == "不是日期") # noqa: SLF001
|
||
|
||
print("ALL OK — 四态随卡产出 / 缺席不断产 / 下游形状 / 判决不被改动 / 计划日推算 全部通过")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|