"""
该不该上 RL？开题提纲生成器（阶段 4 配套）
------------------------------------------
非交互默认模式：可用命令行参数；无参数时走「生物 CPT/多组学问答」示例配置，
打印可粘贴进开题/组会的一页论证提纲。

运行：
  python rl_go_nogo_checklist.py
  python rl_go_nogo_checklist.py --interactive
"""

from __future__ import annotations

import argparse
from dataclasses import dataclass, field


@dataclass
class ProjectSignals:
    title: str = "跨物种多组学证据整合问答（示例）"
    science_question: str = "如何把文献与组学数据库证据整合成可引用的专家级回答？"
    state_desc: str = "用户问题 + 检索到的文献/数据库片段"
    action_desc: str = "生成回答；可选工具调用（检索、比对）"
    multi_step: str = "可多步（检索→阅读→作答）"
    has_unlabeled: bool = True
    has_sft: bool = True
    has_bc_traj: bool = False
    has_pref: bool = True
    has_verifiable: bool = False
    has_online_env: bool = False
    notes: str = "偏好对规模小；暂无稳定自动验证器"


def decide(sig: ProjectSignals) -> dict:
    """返回推荐、对照、风险、表述纪律。"""
    if sig.has_verifiable and sig.has_online_env:
        primary = "SFT 轨迹 → GRPO/PPO（可验证奖励）"
        reason = "存在可自动验证的成功标准，适合策略梯度族。"
        go = "GO-GRPO/PPO"
    elif sig.has_verifiable:
        primary = "SFT → GRPO/PPO 或 Best-of-N + 验证器"
        reason = "有验证器但交互/采样成本需控制。"
        go = "GO-VERIFIABLE"
    elif sig.has_pref:
        primary = "CPT（若有领域语料）→ SFT → 小规模 DPO"
        reason = "有偏好对，主诉求更像专家偏好对齐，而非在线控制。"
        go = "GO-DPO"
    elif sig.has_bc_traj:
        primary = "行为克隆 / 工具轨迹 SFT"
        reason = "有专家轨迹，先模仿再评估是否需要离线 RL。"
        go = "GO-BC"
    elif sig.has_unlabeled:
        primary = "领域 CPT + 数据混合/回放 → 必要时 SFT"
        reason = "仅有无标注序列时，监督信号是 next-token，不是回报。"
        go = "NO-GO-RL（做 CPT/SFT）"
    else:
        primary = "先建数据与评估套件，不上算法创新叙事"
        reason = "信号不足，上 RL 只会优化噪声。"
        go = "NO-GO"

    # 对照
    baselines = ["强检索 RAG + 提示工程", "仅 SFT（无 CPT）"]
    if "CPT" in primary:
        baselines.append("CPT + SFT（无 DPO/RL）")
    if "DPO" in primary:
        baselines.append("CPT + SFT + DPO 的去 DPO 消融")
    if "GRPO" in primary or "PPO" in primary:
        baselines.append("SFT + Best-of-N（同验证器）")

    # 明确不推荐
    avoid = []
    if not sig.has_verifiable and not sig.has_pref and not sig.has_bc_traj:
        avoid.append("端到端 PPO/RLHF（无稳定奖励/偏好）")
    if not sig.has_online_env:
        avoid.append("大规模 on-policy 在线试错（无环境）")
    avoid.append("把 CPT 写成「强化学习优化领域能力」")
    if not sig.has_online_env:
        avoid.append("无博弈设定却宣称多智能体 MARL 贡献")

    risks = [
        ("奖励/偏好黑客", "人工金标集；换奖励或扰动偏好再测", "主指标升但虚构事实率升"),
        ("灾难性遗忘", "通用 + 旧领域套件", "超过预定跌幅"),
        ("评估泄漏", "时间/物种/基因家族切割检查", "测试信息进入 CPT 语料"),
        ("不可复现", "多 seed、固定解码与数据版本", "效应被方差淹没"),
    ]

    wording = [
        "损失为 next-token 的阶段写「CPT/自监督」，不写 RL。",
        "DPO 写「偏好优化/对齐」，可注明源自 RLHF 目标，但实现非 PPO 环。",
        "仅当存在多步环境/工具轨迹优化时使用「Agent + RL」表述。",
    ]

    return {
        "go": go,
        "primary": primary,
        "reason": reason,
        "baselines": baselines,
        "avoid": avoid,
        "risks": risks,
        "wording": wording,
    }


def render(sig: ProjectSignals, decision: dict) -> str:
    lines = []
    a = lines.append
    a("=" * 60)
    a("开题论证提纲：该不该上强化学习？")
    a("=" * 60)
    a("")
    a("1. 科学问题（非算法问题）")
    a(f"   题目：{sig.title}")
    a(f"   问题：{sig.science_question}")
    a("")
    a("2. 决策对象")
    a(f"   状态/上下文：{sig.state_desc}")
    a(f"   动作：{sig.action_desc}")
    a(f"   多步？：{sig.multi_step}")
    a("")
    a("3. 可用监督信号")
    a(f"   无标注序列：{sig.has_unlabeled}")
    a(f"   SFT 标准答案：{sig.has_sft}")
    a(f"   专家轨迹 BC：{sig.has_bc_traj}")
    a(f"   偏好对：{sig.has_pref}")
    a(f"   可验证奖励：{sig.has_verifiable}")
    a(f"   在线环境：{sig.has_online_env}")
    if sig.notes:
        a(f"   备注：{sig.notes}")
    a("")
    a("4. Go / No-Go 判定")
    a(f"   结论：{decision['go']}")
    a(f"   理由：{decision['reason']}")
    a("")
    a("5. 第一推荐与对照")
    a(f"   第一推荐：{decision['primary']}")
    a("   强基线对照：")
    for b in decision["baselines"]:
        a(f"     - {b}")
    a("   明确不推荐：")
    for b in decision["avoid"]:
        a(f"     - {b}")
    a("")
    a("6. 风险与否决条件")
    for name, mon, veto in decision["risks"]:
        a(f"   - {name}")
        a(f"     监测：{mon}")
        a(f"     否决：{veto}")
    a("")
    a("7. 建议里程碑（可改）")
    a("   - W1-2：数据许可/去污 + 评估栈 L1 草案")
    a("   - W3-4：CPT/SFT 强基线")
    a("   - W5：按判定接入 DPO 或停止在 SFT")
    a("   - W6：L2 专家盲评；写明 L3 下游计划")
    a("")
    a("8. 表述纪律")
    for w in decision["wording"]:
        a(f"   - {w}")
    a("")
    a("9. 评估四元组 M（填写）")
    a("   - m_task（主任务）：________________")
    a("   - m_ref（相对旧模型）：________________")
    a("   - m_safety（虚构/有害）：________________")
    a("   - m_cost（费用/调用次数）：________________")
    a("")
    a("=" * 60)
    a("用法：把上文粘贴进组会文档后，用你的真实数字替换示例字段。")
    a("=" * 60)
    return "\n".join(lines)


def interactive() -> ProjectSignals:
    print("交互模式：直接回车保留默认示例值。\n")
    d = ProjectSignals()
    d.title = input(f"题目标题 [{d.title}]: ").strip() or d.title
    d.science_question = input(f"科学问题 [{d.science_question}]: ").strip() or d.science_question

    def yn(prompt: str, default: bool) -> bool:
        s = input(f"{prompt} [{'Y/n' if default else 'y/N'}]: ").strip().lower()
        if not s:
            return default
        return s in ("y", "yes", "1", "是")

    d.has_unlabeled = yn("有大规模无标注领域语料?", d.has_unlabeled)
    d.has_sft = yn("有 SFT 标准答案?", d.has_sft)
    d.has_bc_traj = yn("有专家工具/操作轨迹?", d.has_bc_traj)
    d.has_pref = yn("有偏好对?", d.has_pref)
    d.has_verifiable = yn("有可自动验证奖励?", d.has_verifiable)
    d.has_online_env = yn("有可交互在线环境?", d.has_online_env)
    notes = input("备注（可选）: ").strip()
    if notes:
        d.notes = notes
    return d


def main():
    parser = argparse.ArgumentParser(description="RL Go/No-Go 开题提纲生成器")
    parser.add_argument("--interactive", action="store_true", help="交互填写信号")
    args = parser.parse_args()

    sig = interactive() if args.interactive else ProjectSignals()
    decision = decide(sig)
    print(render(sig, decision))


if __name__ == "__main__":
    main()
