gitlink-cli/skills/draftsmith/scripts/draft.py

190 lines
7.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""draftsmith论文段落起草器。
对着你的结果要点、图表说明、甚至中文草稿,起草成结构合理的 IMRaD 论文段落
Introduction / Methods / Results / Discussion。它不替你编数据而是把你给的
零散要点,套进学术写作的句式骨架,搭出一段像样的初稿,让你在「有结果却憋不出
正文」时有个起点。
三个核心能力:
1. 章节句式骨架——为引言/方法/结果/讨论四个核心章节各准备一套学术句式模板
(研究背景→空白→本文工作;数据→方法→统计;发现→对比→意义),把要点填进去。
2. 要点编织——把用户提供的 bullet 要点,用连接词与句式组织成连贯段落,而不是
简单罗列;自动补「研究表明/结果显示/与…一致」等学术过渡语。
3. 占位提醒——凡是需要具体数字、引用、对象的地方留明确占位([填具体数值]、
[引用]),提醒你补全,绝不编造数据或参考文献。
纯本地模板引擎:不联网、不调用 LLM仅用 Python 标准库把要点结构化为段落。它给
的是「初稿骨架」,学术判断与事实填充由你完成。
用法:
python draft.py --section results --points "实验组准确率更高" "差异显著" --output draft.md
python draft.py --spec paper.json --output draft.md
"""
from __future__ import annotations
import argparse
import json
import sys
from pathlib import Path
from typing import Any
if hasattr(sys.stdout, "reconfigure"):
try:
sys.stdout.reconfigure(encoding="utf-8")
except Exception:
pass
SECTIONS = ["introduction", "methods", "results", "discussion"]
SECTION_CN = {
"introduction": "引言", "methods": "方法",
"results": "结果", "discussion": "讨论",
}
# 各章节的句式骨架:用于把要点编织成段落的开头/过渡/收束语
SECTION_TEMPLATES = {
"introduction": {
"opening": "近年来,{topic}受到广泛关注。",
"transitions": ["然而,", "尽管已有进展,", "值得注意的是,"],
"gap": "然而,[现有方法的不足/尚未解决的问题]仍有待研究。",
"contribution": "为此,本文[提出/研究/分析]……,主要贡献包括:",
"hint": "引言应:交代背景重要性 → 指出研究空白 → 引出本文工作与贡献。",
},
"methods": {
"opening": "本研究采用[数据来源/实验设置]开展。",
"transitions": ["具体而言,", "在此基础上,", "随后,"],
"gap": "",
"contribution": "所有统计分析采用[检验方法],显著性水平设为 α = 0.05。",
"hint": "方法应:可复现地交代数据、流程、参数、统计方法;用过去时、被动语态。",
},
"results": {
"opening": "结果显示,[主要发现]。",
"transitions": ["此外,", "进一步地,", "与之相比,"],
"gap": "",
"contribution": "上述结果在[图/表 X]中汇总([填统计量、p 值、效应量])。",
"hint": "结果应:客观陈述发现 + 对应图表 + 统计证据;不解读意义(留给讨论)。",
},
"discussion": {
"opening": "本研究表明,[核心结论]。",
"transitions": ["这一发现与[文献]一致,", "与既有工作不同,", "需要指出的是,"],
"gap": "本研究存在以下局限:[样本/范围/方法的限制]。",
"contribution": "未来工作可[改进方向]。",
"hint": "讨论应:呼应发现的意义 → 与文献对比 → 承认局限 → 展望。",
},
}
def weave_points(section: str, points: list[str]) -> str:
"""把要点编织成段落。"""
tpl = SECTION_TEMPLATES[section]
transitions = tpl["transitions"]
sentences: list[str] = []
for i, pt in enumerate(points):
pt = pt.strip().rstrip("。.")
if not pt:
continue
if i == 0:
sentences.append(f"{pt}")
else:
conn = transitions[(i - 1) % len(transitions)]
sentences.append(f"{conn}{pt}")
return "".join(sentences)
def draft_section(section: str, points: list[str], topic: str = "该问题") -> dict[str, Any]:
"""起草单个章节。"""
if section not in SECTION_TEMPLATES:
raise ValueError(f"未知章节:{section},应为 {SECTIONS}")
tpl = SECTION_TEMPLATES[section]
body = weave_points(section, points) if points else ""
paragraph_parts = []
if section == "introduction":
paragraph_parts.append(tpl["opening"].format(topic=topic))
if body:
paragraph_parts.append(body)
# 章节特定收束
if section == "introduction":
paragraph_parts.append(tpl["gap"])
paragraph_parts.append(tpl["contribution"])
elif section == "methods":
paragraph_parts.append(tpl["contribution"])
elif section == "results":
paragraph_parts.append(tpl["contribution"])
elif section == "discussion":
paragraph_parts.append(tpl["gap"])
paragraph_parts.append(tpl["contribution"])
return {
"section": section,
"section_cn": SECTION_CN[section],
"paragraph": "".join(paragraph_parts),
"hint": tpl["hint"],
}
def draft_paper(spec: dict[str, Any]) -> list[dict[str, Any]]:
"""按 spec 起草多个章节。spec = {topic, sections: {results: [...], ...}}"""
topic = spec.get("topic", "该问题")
out = []
for section in SECTIONS:
pts = spec.get("sections", {}).get(section)
if pts is not None:
out.append(draft_section(section, pts, topic=topic))
return out
def render_markdown(drafts: list[dict[str, Any]], topic: str = "") -> str:
lines = ["# 论文段落初稿"]
if topic:
lines.append(f"\n主题:{topic}")
lines += ["",
"> 由 draftsmith 起草。这是把要点套进学术句式的初稿骨架,"
"`[...]` 处需你填具体数值、引用与对象,工具不编造数据。", ""]
for d in drafts:
lines.append(f"## {d['section_cn']}{d['section']}")
lines.append("")
lines.append(d["paragraph"])
lines.append("")
lines.append(f"> 写作提示:{d['hint']}")
lines.append("")
lines += ["---", "", "由 draftsmith 生成。初稿仅供起步,事实与表达需你核定。"]
return "\n".join(lines)
def main(argv: list[str] | None = None) -> int:
p = argparse.ArgumentParser(prog="draftsmith", description="论文段落起草器")
p.add_argument("--section", choices=SECTIONS, help="起草单个章节")
p.add_argument("--points", nargs="+", help="要点列表(配合 --section")
p.add_argument("--topic", default="该问题", help="研究主题(用于引言开头)")
p.add_argument("--spec", type=Path, help="多章节 JSON{topic, sections:{results:[...]}}")
p.add_argument("--format", choices=["markdown", "json"], default="markdown")
p.add_argument("--output", type=Path)
args = p.parse_args(argv)
if args.spec and args.spec.exists():
spec = json.loads(args.spec.read_text(encoding="utf-8-sig"))
drafts = draft_paper(spec)
topic = spec.get("topic", "")
elif args.section:
drafts = [draft_section(args.section, args.points or [], topic=args.topic)]
topic = args.topic
else:
print("错误:请用 --section + --points或用 --spec 提供多章节要点。", file=sys.stderr)
return 2
out = (json.dumps(drafts, ensure_ascii=False, indent=2) if args.format == "json"
else render_markdown(drafts, topic))
if args.output:
args.output.parent.mkdir(parents=True, exist_ok=True)
args.output.write_text(out, encoding="utf-8")
print(f"已写入 {args.output}")
else:
print(out)
return 0
if __name__ == "__main__":
raise SystemExit(main())