综合素质测评登记表填报
面向高校(典型:湖南师范大学物电院)学年综测表填报:看懂细则 → 算准三个分项 → 严格照示范草表版式重建成 docx → 压到两页 → 清红字 → 出证明材料 → 视觉校验。
第 0 步:先侦察,别急着改(决定后面全部返工量)
拿到用户的「综测」文件夹后,先只读,把这几件事查清楚再动手:
- 实施细则(
*实施细则*.docx)——所有加分依据都在这里,必须读原文行号级摘录,不要凭常识推。 - 示范草表(
附件2*.doc)——版式基准。红色字体 = 学校留的待填提示,黑色是模板固定文字。用户常说"只有红色字体是要改的"。 - 证明材料模板(
附件3*.docx)——⚠️ 大概率不是空白表格,而是一份「范例结构」(内嵌占位图 + 示例条目)。要照它的结构做(大类标题 B1/B3 → 分项说明「2.(四级优秀)加2分」→ 证明图片),不是照抄它的条目。 - 用户给的成绩截图、证书截图——先把图片落地到磁盘再处理(剪贴板截图常被系统临时清理)。
把侦察结论(尤其"哪些项加几分、依据哪一行")先讲给用户确认,再进入生成。
第 1 步:算 F1 / F2 / F3
公式
F = F1×10% + F2×65% + F3×25% # 四舍五入保留 2 位小数
等级:80+ 优;70~79.99 良;60~69.99 合格;<60 不合格。
F1 基本素质
A1~A5 各 20 分,基准 100 分,减分制。一般无扣分即 F1 = 100.00。在表里逐项填 20,并写自评分/核查分。
F2 课程学习成绩(最容易算错,注意范围)
- 只算必修课 + 专业选修课,排除公共选修课(用户常特别点出"XX 那门不计入统计")。
F2 = Σ(学分×成绩) / Σ学分,保留 2 位小数。- 在脚本里用 assert 锁住结果(如
assert F2 == 78.33),后续改动不会静默跑飞。 - 明细要写进表里(12 门左右),空着会被判"内容没写全"。
F3 创新素质与实践能力
F3 = B1 + B2 + B3 + B4 + B5 + B6 + B7 + B8,分项累加。
上限:B1/B2/B3/B5/B6/B7 每项 ≤30;B4 ≤20;B8 ≤10。
⚠️ 关键:很多项本身带"基础分",用户会明确要求"F3 的很多项目是有基础分的,要加上"。基础分必须回到实施细则原文确认,例如:
| 分项 | 基础分 | 典型叠加项 |
|---|---|---|
| B1 职业技能/外语/证书 | +5 | 四级合格 +1(>550 优秀 +2);六级合格 +2 / 优秀 +4;青马网课结业按初级 +1 |
| B2 学科竞赛 | +5 | 参与即得 |
| B3 学术科技活动/讲座 | +2 | 参与即得 |
| B4 征文/投稿 | +5 | 参与即得 |
| B6 社会实践/志愿服务 | +5 | 参与即得 |
不加分项要记住(细则里常单独列一段排除):入党积极分子/发展对象/预备党员培训结业、心理辅导员等。
青马网课这类"看似模糊"的项:回原文找关键字(如"青马网课…凭结业证书按照初级加1分")→ 判定归属(青马网课属 B1)。别自己猜档位。
详细规则摘录见 references/rules-hunnu-physics.md,换成别的学校/学院时按同一方法重新摘录。
第 2 步:生成 docx(必须用 python-docx,不要用编辑器保存)
🚨 本环境最大的坑
不要用本地 Office 编辑器(edsdk / tencent-local-office-edit)保存的 .doc —— 这类文件 Word 打不开(ZIP 里 [Content_Types].xml 排最后 + flag=0x0800 + 含目录条目)。
正确路径:
1. 先用 Word COM 把示范模板另存为干净 docx(SaveAs2(path, 0))当基准模板 → 存到 render/demo_clean.docx。
2. 用 python-docx 打开基准模板改写,尾部再 SaveAs2(path, 0) 导出 .doc 交付。
版式硬约束(A3 横向双栏)
- 页面 A3 横向,双栏;每栏可用高仅 662pt(13240 twips)——这是"两页"的物理上限。
- 主表
tblLayout fixed,所有行cantSplit+trHeight hRule=atLeast。 - 列宽算法:
(pageW - leftMargin - rightMargin - space*(ncol-1)) // ncol(从sectPr/w:cols取num/space)。
🚨 个人总结行(r1)的行高是"两页"的命门
必须保持最小行高 12488 twips(模板原值)。实测:
- 调大到 12840 → 整表溢出到第 3 页
- 调小到 11400 → 「二、学生综合素质测评计分」标题行滑到左栏底部、与表格主体分离(版式错)
- 内容本身超过 12488 也会溢出 → 所以个人总结正文压到 5 段、签名前空段只留 2 个
改版式前先跑 scripts/blocks.py(page.get_text("blocks") 拿文字块坐标)看真实分页位置,比只看页数有用得多。
封面横线对齐(制表位 + 下划线前导符)
不要靠"补空格"画横线(会因字体度量不同而参差)。用 tab stop + WD_TAB_LEADER.LINES:
from docx.shared import Twips
from docx.enum.text import WD_TAB_ALIGNMENT, WD_TAB_LEADER
ts.add_tab_stop(Twips(P1), WD_TAB_ALIGNMENT.LEFT, WD_TAB_LEADER.LINES) # 值起始
ts.add_tab_stop(Twips(P2), WD_TAB_ALIGNMENT.LEFT, WD_TAB_LEADER.LINES) # 横线右端
# 结构:标签(无下划线) + [tab→P1] + 值(带下划线) + [tab→P2]
P1 = 缩进(3163) + 1250,P2 = 列宽 - 120(A3横向双栏 colW=10707)。
红转黑(提交前必须清零)
用户要求"不能存在红色字体"。写一个 kill_red(),覆盖 FF0000 / C00000 / ED1C24 / FF0001 / 002060 以及带 w:themeColor 的 run(主题色有时不体现在 val 上)。
保留在红色占位 run 里填空的原位技巧
表里"最终得分""☑ 合格"等位置原文是红色空格/红色空心字,就地把那个 run 的文字改成值(而不是删了重写),能保住原有的下划线、字号、居中。完成后统一 kill_red。
其他 python-docx 陷阱
section.sectPr不存在 → 用section._sectPrrow._tr在 python-docx 1.2.0 里就是row本身- 跨页时
Row.Range取不到值 → 改用折叠 Rangedoc.Range(s, s) - 单元格文本用
t.rows[i].cells[c]._tc拿 0 基 tc(不要用原始 tc 索引,合并过会错位)
第 3 步:证明材料
照 附件3 的结构生成独立文档:
B1 ← 大类标题,24pt
1.(大学英语四级考试合格)加1分 ← 分项说明,12pt,与图片同页
[证明图片]
2.(参加"青马网课"培训结业,按初级认定)加1分
[证明图片]
- 图片缩放要同时受可用宽和可用高约束:
MAXH = (pageH - topM - botM) * 0.90 - 给"说明文字"段落加
w:keepNext+w:keepLines,否则会被分页拆到图片前一行。
截图裁剪:用 numpy 投影找边界,别手调坐标
证书/成绩单截图常带大片白边或右侧多余文字列。做法:
a = np.array(Image.open(path).convert('L'))
col = (a <= 235).sum(axis=0); row = (a <= 235).sum(axis=1) # 非白像素投影
# 找到连续低于阈值(如 h*0.03) 的空白带 → 取内容边界裁剪
第 4 步:渲染 + 纯视觉校验(必做,且做几遍)
可靠通道:Word COM → PDF → PNG,然后真的去看 PNG。
🚨 Word COM 在沙箱环境下的稳定性(血泪教训)
- 现象:前台跑 COM 脚本,
Documents.Open/SaveAs2中途 WINWORD 被杀(exit 1、日志残缺,每次死点不同),或报0x800706BE 远程过程调用失败。 - 根因:沙箱在工具调用结束时清理整棵进程树,COM 派生的 WINWORD 子进程跟着陪葬。
- 正确做法:把「打开 → SaveAs×N → 关闭 → 逐个校验」全部放进一个后台任务跑完,期间绝不发 Stop-Process / 不再碰 Word 进程。
- 其他坑:
1. PowerShell 工具 stdout 不回显 → 所有输出用[System.IO.File]::WriteAllText/AppendAllText写日志文件,再用读文件工具看。
2..ps1含中文路径/字符串必须 UTF-8 带 BOM;无 BOM 会被 PowerShell 5.1 当 ANSI 解析,直接解析失败(exit 1、零输出)。可用 Python 补 BOM。
3. 沙箱「批量删除保护」拦Remove-Item通配清理(SAFE_DELETE_BULK_GUARD)→ 残留~$锁文件留着别删,无害。
4. 编辑器预览占用的 docx 会让 Word 打开挂起 → 先cp一份副本,对副本渲染。
5. 清理残留 WINWORD 用Get-Process WINWORD | Stop-Process -Force(这个不被拦)。
6..doc(FileFormat=0)转换可能在该环境下挂死(本环境后半夜连续 3 次挂 3 分钟+,而早些时候能正常转,疑似 Word 被强杀数次后状态劣化)。应对:先用ComputeStatistics确认页数(这个一直可靠);.doc转不出来时降级交付——.docx用 python-docx 产物直接cp覆盖(零红字有保证),.doc保留此前 Word 转换成功的版本并如实告知用户差异。
# 流程骨架(后台任务内):
$doc = $word.Documents.Open($src, $false, $false)
$pages = $doc.ComputeStatistics(2) # wdStatisticPages
$doc.SaveAs2($outDoc, 0) # wdFormatDocument97 (.doc)
$doc.SaveAs2($outDocx, 12) # wdFormatXMLDocument (.docx)
$doc.SaveAs2($outPdf, 17) # wdFormatPDF
$doc.Close($false); $word.Quit()
然后用 pymupdf 转 PNG(150dpi),逐页看:
- 页数 = 2(>2 就得回去压内容)
- 跨栏/跨页处有没有断行、标题与主体分离
- 空白单元格是否都填上了
- 封面横线右端是否齐
零红字的真正标准
kill_red 只扫 w:r 会漏掉段落标记的颜色(w:pPr/w:rPr/w:color=FF0000)——它不可见、不影响打印,但 Word「查找→字体颜色=红色」会命中,且用户在该处继续输入会变红。正确做法是遍历所有 w:color 元素(正文 run 与段落标记一网打尽):
for c in root.iter(qn('w:color')): # 而不是 root.iter(qn('w:r'))
...
验证脚本(lxml 区分可见文字与段落标记):
for c in root.iter(W+'color'):
v=(c.get(W+'val') or '').upper()
if v in ('000000','AUTO',''): continue
rpr=c.getparent(); owner=rpr.getparent()
in_pm = (owner is not None and owner.tag==W+'pPr') # 段落标记
配套脚本:
- scripts/verify.py — 页数 + 扫描非黑色 run(必须为 0)+ 转 PNG
- scripts/audit.py — 逐单元格内容核对
- scripts/blocks.py — 文字块坐标定位真实分页
- scripts/build_zongce.py — 综测表生成(模板脚本,改数据区即可复用)
- scripts/build_proof.py — 证明材料生成
第 5 步:命名与交付
- 草表:
草表(学号+姓名),如草表(202400000001张三).doc - 证明材料:
证明材料(学号+姓名),如证明材料(202400000001张三).docx - 学号+姓名不加分隔符;括号用全角
- 交付前对每个文件跑一次 Word COM 校验:能打开 + 页数正确
- 工作区留一份同名副本,桌面(用户目录)放交付副本
红旗检查清单(交付前逐条过)
- [ ] 页数 = 2
- [ ] 非黑色 run = 0(红字提示全清)
- [ ] 封面 6 行横线右端对齐
- [ ] F1/F2/F3 与自评分/核查分四处文字一致
- [ ] ☑ 已勾在正确等级
- [ ] 个人总结有签名 + 日期(日期取当天,脚本里单独一个
datetime.date(...)便于改) - [ ] 证明材料每项"说明 + 图片"同页
- [ ] 三个文件名符合
草表(学号+姓名)/证明材料(学号+姓名)