Compare commits
1 Commits
master
...
skill/poli
| Author | SHA1 | Date |
|---|---|---|
|
|
24e2bd3b4d |
|
|
@ -0,0 +1,62 @@
|
|||
---
|
||||
name: polishpro
|
||||
version: 1.0.0
|
||||
description: "学术英文润色检查器:对英文草稿做句级检查,标出冗余短语(in order to→to)、弱动词/名词化(make an investigation of→investigate)、被动语态滥用、中式英文/赘词(very important、more and more),逐条给出更简洁地道的改法建议。当用户提到「英文润色」「检查英文表达」「中式英文」「句子啰嗦」「英文不地道」「polish」时触发。"
|
||||
metadata:
|
||||
requires:
|
||||
optional_bins: ["python"]
|
||||
---
|
||||
|
||||
# polishpro(学术英文润色检查器)
|
||||
|
||||
英文写得出、但总被说「表达不地道」?polishpro 不改错字,而是把审稿人读着别扭的
|
||||
表达——啰嗦、中式、被动滥用——逐句标出来,给更简洁地道的改法。
|
||||
|
||||
## 何时使用本技能
|
||||
|
||||
- 自己写的英文怕不地道,过一遍
|
||||
- 句子啰嗦,想压简洁
|
||||
- 投稿前做一次表达自查
|
||||
|
||||
## 与同类工具的区别
|
||||
|
||||
同类润色工具多直接调 LLM 重写。本技能是**纯规则引擎**:不联网、不调 LLM,基于学术
|
||||
写作公认的简洁性原则逐条检查,给的是**带解释的改进建议清单**而非黑盒重写。它的价值
|
||||
在「指出问题 + 教你为什么」,最终改不改、怎么改由你定——既可控、可离线,也帮你
|
||||
积累地道表达的直觉。
|
||||
|
||||
## 四类检查
|
||||
|
||||
| 类别 | 例子 |
|
||||
|------|------|
|
||||
| 冗余短语 | in order to→to,due to the fact that→because |
|
||||
| 弱动词/名词化 | make an investigation of→investigate |
|
||||
| 被动语态 | 标出被动结构,提示非方法章节可改主动 |
|
||||
| 中式英文/赘词 | very important、more and more→increasingly |
|
||||
|
||||
另给平均句长与过长句子(>35 词)提示,建议拆分。
|
||||
|
||||
## 工作流
|
||||
|
||||
```bash
|
||||
python scripts/polish.py --input draft.txt --output report.md
|
||||
python scripts/polish.py --text "In order to improve..." --format json
|
||||
```
|
||||
|
||||
| 参数 | 说明 |
|
||||
|------|------|
|
||||
| `--input` | 英文草稿文件(*或用 `--text`) |
|
||||
| `--text` | 直接传入英文文本 |
|
||||
| `--format` | `markdown`(默认)或 `json` |
|
||||
| `--output` | 输出文件 |
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 规则覆盖学术写作高频问题,但非穷尽;最终判断结合语境。
|
||||
- 被动语态在方法章节是规范的,提示仅供参考,不必一律改主动。
|
||||
- 工具只给建议,不替你重写句子——避免改变你的原意。
|
||||
|
||||
## References
|
||||
|
||||
- [rules.md](references/rules.md) — 四类检查的规则与建议对照
|
||||
- [usage.md](references/usage.md) — 使用建议与配合写作流程
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
# 四类检查规则与建议对照
|
||||
|
||||
polishpro 基于学术写作公认的简洁性原则,逐句匹配以下规则。规则定义在 `polish.py`
|
||||
的四个字典/正则中,可按需扩充。
|
||||
|
||||
## 冗余短语(WORDY_PHRASES)
|
||||
|
||||
| 啰嗦写法 | 建议 |
|
||||
|---------|------|
|
||||
| in order to | to |
|
||||
| due to the fact that | because |
|
||||
| in spite of the fact that | although |
|
||||
| a number of | many / few |
|
||||
| in the event that | if |
|
||||
| it is worth noting that | notably / 直接陈述 |
|
||||
| with regard to | regarding / about |
|
||||
| for the purpose of | to / for |
|
||||
| as a matter of fact | 删去 |
|
||||
|
||||
## 弱动词 / 名词化(NOMINALIZATIONS)
|
||||
|
||||
把「动词被名词化再配弱动词(make/conduct/perform…)」还原成强动词:
|
||||
|
||||
| 啰嗦 | 强动词 |
|
||||
|------|--------|
|
||||
| make an investigation of | investigate |
|
||||
| conduct an analysis of | analyze |
|
||||
| perform a comparison of | compare |
|
||||
| give an explanation of | explain |
|
||||
| make use of | use |
|
||||
|
||||
## 被动语态(PASSIVE)
|
||||
|
||||
正则匹配「be 动词 + 过去分词」。被动在方法章节是规范的(强调操作而非操作者),
|
||||
但在引言/讨论中过多被动会让表达迂回。工具只标出、不强制改。
|
||||
|
||||
## 中式英文 / 赘词(CHINGLISH)
|
||||
|
||||
| 表达 | 建议 |
|
||||
|------|------|
|
||||
| more better | better |
|
||||
| very important | 去 very,用精确词 |
|
||||
| more and more | increasingly |
|
||||
| as we all know | 学术写作避免 |
|
||||
| big difference | large / substantial |
|
||||
| get better | improve |
|
||||
|
||||
## 句长
|
||||
|
||||
平均句长与单句词数:超过 35 词的句子会被标为「过长,建议拆分」。学术英文并非越长
|
||||
越严谨,长句易丢失主干。
|
||||
|
||||
## 设计原则
|
||||
|
||||
工具给「建议」不做「重写」——保留你的原意,只指出问题并解释为什么。这样你改的同时
|
||||
也在积累地道表达的判断力。
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
# 使用建议与写作流程
|
||||
|
||||
## 怎么用
|
||||
|
||||
1. 把英文草稿(段落或全文)存成 .txt,或用 `--text` 直接传。
|
||||
2. 运行得到检查报告:分类统计 + 逐条建议 + 过长句提示。
|
||||
3. 对照建议逐条改,改不改由你定——建议都附了「为什么」。
|
||||
|
||||
## 报告怎么读
|
||||
|
||||
- **分类统计**:先看哪类问题最多,集中优化。冗余短语和被动语态通常最常见。
|
||||
- **逐条建议**:每条给出原片段、问题类型、建议改法、所在整句,方便定位。
|
||||
- **过长句子**:>35 词的句子单列,优先考虑拆分。
|
||||
|
||||
## 配合写作流程
|
||||
|
||||
- draftsmith 起草 → 自己填实内容 → **polishpro 过一遍英文** → 投稿。
|
||||
- polishpro 检查的是「表达」,不检查「事实/逻辑」,后者靠 peermirror 自审。
|
||||
|
||||
## 注意
|
||||
|
||||
- 被动语态提示要结合章节:方法章节用被动是规范的,不必强行改主动。
|
||||
- 规则是高频问题的覆盖,不是穷尽;它帮你抓大部分明显问题,细腻语感仍需人来把关。
|
||||
- 工具不重写句子,避免改变原意——它是「教练」不是「代笔」。
|
||||
|
||||
## 局限
|
||||
|
||||
- 基于规则与正则,无法理解语义,可能漏掉「语法对但表达绕」的句子。
|
||||
- 仅针对英文;中英混排文本的非英文部分会被忽略。
|
||||
|
|
@ -0,0 +1,209 @@
|
|||
"""polishpro:学术英文润色检查器。
|
||||
|
||||
对英文草稿做句级检查,把「中式英文 / 啰嗦 / 不地道」的地方标出来,给出更简洁、
|
||||
更地道的改法建议。它不是改错字,而是把审稿人读着别扭的表达,指出来并建议怎么改。
|
||||
|
||||
四类检查(纯规则,不调 LLM):
|
||||
1. 冗余短语——in order to→to、due to the fact that→because、a number of→many 等
|
||||
学术写作里高频的啰嗦表达,建议精简。
|
||||
2. 弱动词 / 名词化——make an investigation of→investigate、conduct an analysis→
|
||||
analyze,把「动词被名词化再配弱动词」的结构还原成强动词。
|
||||
3. 被动语态提示——标出被动结构,提示在方法之外的章节可考虑主动语态。
|
||||
4. 中式英文 / 赘词——more better、according to、very important 等中式或口语化表达。
|
||||
|
||||
每条命中给出:原片段、问题类型、建议改法、所在句子。纯本地、纯标准库,离线可用。
|
||||
它给的是「改进建议清单」,最终改不改、怎么改由你定。
|
||||
|
||||
用法:
|
||||
python polish.py --input draft.txt --output report.md
|
||||
python polish.py --text "In order to improve the performance..." --format json
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
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
|
||||
|
||||
|
||||
# 冗余短语:模式 -> 建议
|
||||
WORDY_PHRASES = {
|
||||
r"\bin order to\b": "to",
|
||||
r"\bdue to the fact that\b": "because",
|
||||
r"\bin spite of the fact that\b": "although",
|
||||
r"\ba (large |small )?number of\b": "many / few",
|
||||
r"\bin the event that\b": "if",
|
||||
r"\bat this point in time\b": "now",
|
||||
r"\bin the near future\b": "soon",
|
||||
r"\bit is worth noting that\b": "notably / (直接陈述)",
|
||||
r"\bit should be noted that\b": "note that / (直接陈述)",
|
||||
r"\bwith regard to\b": "regarding / about",
|
||||
r"\bin terms of\b": "(常可删去或换具体说法)",
|
||||
r"\bfor the purpose of\b": "to / for",
|
||||
r"\bin the case of\b": "for / in",
|
||||
r"\bas a matter of fact\b": "(多余,建议删)",
|
||||
}
|
||||
|
||||
# 名词化 + 弱动词 -> 强动词
|
||||
NOMINALIZATIONS = {
|
||||
r"\bmake an investigation of\b": "investigate",
|
||||
r"\bconduct an analysis of\b": "analyze",
|
||||
r"\bperform a comparison of\b": "compare",
|
||||
r"\bgive an explanation of\b": "explain",
|
||||
r"\bprovide a description of\b": "describe",
|
||||
r"\bcarry out an evaluation of\b": "evaluate",
|
||||
r"\bmake use of\b": "use",
|
||||
r"\bmake a decision\b": "decide",
|
||||
}
|
||||
|
||||
# 中式英文 / 赘词 / 不地道
|
||||
CHINGLISH = {
|
||||
r"\bmore better\b": "better(more 多余)",
|
||||
r"\bvery (important|significant|crucial)\b": "(去掉 very,用更精确的词或直接陈述)",
|
||||
r"\bmore and more\b": "increasingly",
|
||||
r"\bas we all know\b": "(学术写作避免,直接陈述或引用)",
|
||||
r"\bin a word\b": "in short / in summary",
|
||||
r"\bby the way\b": "(学术写作不用)",
|
||||
r"\bso\b": "(句首 so 偏口语,用 therefore/thus)",
|
||||
r"\bbig (difference|improvement|effect)\b": "large / substantial",
|
||||
r"\bget (better|worse)\b": "improve / deteriorate",
|
||||
}
|
||||
|
||||
# 被动语态(粗检:be 动词 + 过去分词)
|
||||
PASSIVE = re.compile(
|
||||
r"\b(is|are|was|were|be|been|being)\s+(\w+ed|done|made|shown|found|used|given|taken|seen|known)\b",
|
||||
re.IGNORECASE)
|
||||
|
||||
|
||||
def split_sentences(text: str) -> list[str]:
|
||||
text = text.lstrip("\ufeff")
|
||||
text = re.sub(r"\s+", " ", text.strip())
|
||||
# 简单断句(润色场景对断句精度要求不高)
|
||||
parts = re.split(r"(?<=[.!?])\s+(?=[A-Z])", text)
|
||||
return [p.strip() for p in parts if p.strip()]
|
||||
|
||||
|
||||
def _check_dict(sentence: str, patterns: dict[str, str], category: str) -> list[dict[str, Any]]:
|
||||
issues = []
|
||||
for pat, suggestion in patterns.items():
|
||||
for m in re.finditer(pat, sentence, re.IGNORECASE):
|
||||
issues.append({
|
||||
"category": category,
|
||||
"fragment": m.group(0),
|
||||
"suggestion": suggestion,
|
||||
"sentence": sentence,
|
||||
})
|
||||
return issues
|
||||
|
||||
|
||||
def check_sentence(sentence: str) -> list[dict[str, Any]]:
|
||||
issues: list[dict[str, Any]] = []
|
||||
issues += _check_dict(sentence, WORDY_PHRASES, "冗余短语")
|
||||
issues += _check_dict(sentence, NOMINALIZATIONS, "弱动词/名词化")
|
||||
issues += _check_dict(sentence, CHINGLISH, "中式英文/赘词")
|
||||
# 被动语态
|
||||
for m in PASSIVE.finditer(sentence):
|
||||
issues.append({
|
||||
"category": "被动语态",
|
||||
"fragment": m.group(0),
|
||||
"suggestion": "方法章节可保留;其余章节考虑改主动语态使表达更直接",
|
||||
"sentence": sentence,
|
||||
})
|
||||
return issues
|
||||
|
||||
|
||||
def polish(text: str) -> dict[str, Any]:
|
||||
sentences = split_sentences(text)
|
||||
all_issues: list[dict[str, Any]] = []
|
||||
for s in sentences:
|
||||
all_issues.extend(check_sentence(s))
|
||||
by_cat: dict[str, int] = {}
|
||||
for it in all_issues:
|
||||
by_cat[it["category"]] = by_cat.get(it["category"], 0) + 1
|
||||
# 简单可读性提示:平均句长
|
||||
words = sum(len(re.findall(r"\b\w+\b", s)) for s in sentences)
|
||||
avg_len = round(words / len(sentences), 1) if sentences else 0
|
||||
long_sents = [s for s in sentences if len(re.findall(r"\b\w+\b", s)) > 35]
|
||||
return {
|
||||
"sentence_count": len(sentences),
|
||||
"issue_count": len(all_issues),
|
||||
"by_category": by_cat,
|
||||
"issues": all_issues,
|
||||
"avg_sentence_length": avg_len,
|
||||
"long_sentences": long_sents,
|
||||
}
|
||||
|
||||
|
||||
def render_markdown(data: dict[str, Any]) -> str:
|
||||
lines = [
|
||||
"# 英文润色检查报告",
|
||||
"",
|
||||
f"共 {data['sentence_count']} 句,发现 {data['issue_count']} 处可改进,"
|
||||
f"平均句长 {data['avg_sentence_length']} 词。",
|
||||
"",
|
||||
"## 分类统计",
|
||||
"",
|
||||
]
|
||||
if data["by_category"]:
|
||||
for cat, n in sorted(data["by_category"].items(), key=lambda kv: -kv[1]):
|
||||
lines.append(f"- {cat}:{n} 处")
|
||||
else:
|
||||
lines.append("- 未发现明显问题,表达较简洁。")
|
||||
lines += ["", "## 逐条建议", ""]
|
||||
if data["issues"]:
|
||||
for i, it in enumerate(data["issues"], start=1):
|
||||
lines.append(f"{i}. [{it['category']}] `{it['fragment']}` → 建议:{it['suggestion']}")
|
||||
lines.append(f" > 所在句:{it['sentence']}")
|
||||
lines.append("")
|
||||
else:
|
||||
lines.append("(无)")
|
||||
lines.append("")
|
||||
if data["long_sentences"]:
|
||||
lines += ["## 过长句子(>35 词,建议拆分)", ""]
|
||||
for s in data["long_sentences"]:
|
||||
lines.append(f"- {s}")
|
||||
lines.append("")
|
||||
lines += ["---", "",
|
||||
"由 polishpro 生成。基于规则的改进建议,最终改法由你定;它不替你重写句子。"]
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
p = argparse.ArgumentParser(prog="polishpro", description="学术英文润色检查器")
|
||||
p.add_argument("--input", type=Path, help="英文草稿文件")
|
||||
p.add_argument("--text", help="直接传入英文文本")
|
||||
p.add_argument("--format", choices=["markdown", "json"], default="markdown")
|
||||
p.add_argument("--output", type=Path)
|
||||
args = p.parse_args(argv)
|
||||
|
||||
if args.input and args.input.exists():
|
||||
text = args.input.read_text(encoding="utf-8-sig", errors="replace")
|
||||
elif args.text:
|
||||
text = args.text
|
||||
else:
|
||||
print("错误:请用 --input 或 --text 提供英文文本。", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
data = polish(text)
|
||||
out = (json.dumps(data, ensure_ascii=False, indent=2) if args.format == "json"
|
||||
else render_markdown(data))
|
||||
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())
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
"""polishpro 单元测试。"""
|
||||
from __future__ import annotations
|
||||
import sys
|
||||
from pathlib import Path
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "scripts"))
|
||||
import pytest
|
||||
from polish import split_sentences, check_sentence, polish, render_markdown
|
||||
|
||||
|
||||
class TestSplit:
|
||||
def test_basic(self):
|
||||
assert len(split_sentences("First one. Second one. Third one.")) == 3
|
||||
|
||||
def test_bom(self):
|
||||
s = split_sentences("\ufeffHello world. Bye.")
|
||||
assert not s[0].startswith("\ufeff")
|
||||
|
||||
|
||||
class TestCheckSentence:
|
||||
def test_wordy(self):
|
||||
issues = check_sentence("In order to test it.")
|
||||
assert any(i["category"] == "冗余短语" and "in order to" in i["fragment"].lower() for i in issues)
|
||||
|
||||
def test_nominalization(self):
|
||||
issues = check_sentence("We make an investigation of the data.")
|
||||
cats = [i["category"] for i in issues]
|
||||
assert "弱动词/名词化" in cats
|
||||
|
||||
def test_chinglish(self):
|
||||
issues = check_sentence("This is a very important result.")
|
||||
assert any(i["category"] == "中式英文/赘词" for i in issues)
|
||||
|
||||
def test_passive(self):
|
||||
issues = check_sentence("The model was trained on the dataset.")
|
||||
assert any(i["category"] == "被动语态" for i in issues)
|
||||
|
||||
def test_clean_sentence(self):
|
||||
issues = check_sentence("We trained the model.")
|
||||
# 主动、简洁,应无或极少问题
|
||||
assert all(i["category"] != "冗余短语" for i in issues)
|
||||
|
||||
|
||||
class TestPolish:
|
||||
def test_counts(self):
|
||||
data = polish("In order to win, we make use of tricks. Due to the fact that it works.")
|
||||
assert data["issue_count"] >= 3
|
||||
assert data["sentence_count"] == 2
|
||||
|
||||
def test_by_category(self):
|
||||
data = polish("In order to improve, due to the fact that data is limited.")
|
||||
assert "冗余短语" in data["by_category"]
|
||||
|
||||
def test_avg_length(self):
|
||||
data = polish("Short one. Another short.")
|
||||
assert data["avg_sentence_length"] > 0
|
||||
|
||||
def test_long_sentence_flagged(self):
|
||||
long = " ".join(["word"] * 40) + "."
|
||||
data = polish(long)
|
||||
assert data["long_sentences"]
|
||||
|
||||
|
||||
class TestRender:
|
||||
def test_render_with_issues(self):
|
||||
md = render_markdown(polish("In order to test, we make use of data."))
|
||||
assert "英文润色检查报告" in md
|
||||
assert "逐条建议" in md
|
||||
|
||||
def test_render_clean(self):
|
||||
md = render_markdown(polish("We trained the model."))
|
||||
assert "英文润色检查报告" in md
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(pytest.main([__file__, "-v"]))
|
||||
Loading…
Reference in New Issue