feat: note scoring 60/6 with rating award + Minecraft theme

- Score max changed from 100 to 60, rating max from 10 to 6
- Note scoring now awards actual rating points (delta-based)
- Re-scoring only awards/deducts the difference
- Rating history shows note_score entries with problem link
- LLM prompt includes problem statement context for better evaluation
- LLM scoring dimensions: 题意理解/思路算法/代码记录/踩坑反思 (15 each)
- Minecraft-themed UI: 矿石鉴定, 探索笔记, 存入宝典, etc.
- Fallback scoring adjusted for 60-point scale
- Handle LLM markdown code fence wrapping in response

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
这个提交包含在:
cryptocommuniums-afk
2026-02-16 18:32:23 +08:00
父节点 7dd10bef2d
当前提交 9772ea6764
修改 7 个文件,包含 142 行新增46 行删除

查看文件

@@ -1,5 +1,5 @@
#!/usr/bin/env python3
"""Score a learning note (0-100) and map to rating (1-10) via LLM with fallback."""
"""Score a learning note (0-60) and map to rating (0-6) via LLM with fallback."""
from __future__ import annotations
@@ -27,28 +27,28 @@ def load_input(path: str) -> Dict[str, Any]:
def fallback(note: str) -> Dict[str, Any]:
n = note.strip()
score = 40
if len(n) >= 300:
score += 15
if len(n) >= 800:
score = 20
if len(n) >= 200:
score += 10
if len(n) >= 500:
score += 5
if "```" in n:
score += 15
score += 10
if "踩坑" in n or "错误" in n or "debug" in n.lower():
score += 10
score += 8
if "总结" in n or "注意" in n:
score += 10
score = min(100, score)
rating = max(1, min(10, round(score / 10)))
score += 7
score = min(60, score)
rating = max(0, min(6, round(score / 10)))
feedback_md = (
"### 笔记评分(规则兜底)\n"
f"- 评分**{score}/100**,评级**{rating}/10**\n"
"\n### 你做得好的地方\n"
"- 记录了学习过程(检测到一定的笔记内容)。\n"
"\n### 建议补充\n"
"- 写清:本节课**学习目标**、**关键概念**、**代码模板**。\n"
"- 至少 1 个你遇到的坑(如输入输出格式、编译报错)以及解决方案。\n"
"- 最后用 3-5 行做总结,方便复习\n"
"### ⛏️ 矿石鉴定报告(规则兜底)\n"
f"- 品质**{score}/60** ⚡ 经验值**+{rating}**\n"
"\n### 🏆 已获成就\n"
"- 记录了探索过程(检测到一定的笔记内容)。\n"
"\n### 📜 升级指南\n"
"- 写清本次**探索目标**、**核心知识点**、**代码配方**。\n"
"- 至少记录 1 个你遇到的陷阱(如格式、编译报错)以及修复方案。\n"
"- 最后用 3-5 行做总结,铸造你的知识宝典\n"
)
return {"score": score, "rating": rating, "feedback_md": feedback_md, "model_name": "fallback-rules"}
@@ -60,23 +60,35 @@ def call_llm(payload: Dict[str, Any]) -> Dict[str, Any]:
if not api_url:
raise RuntimeError("missing OI_LLM_API_URL")
problem_title = payload.get("problem_title", "")
problem_statement = payload.get("problem_statement", "")
# Truncate long statements to save tokens
if len(problem_statement) > 2000:
problem_statement = problem_statement[:2000] + "\n...(truncated)"
system = (
"你是一位面向小学生的C++竞赛教练,请对学习笔记打分。"
"评分满分100分,并给出10分制评级rating=round(score/10),范围1-10"
"评分维度:覆盖度30、正确性30、可操作性20、反思总结20。"
"输出必须是JSON,不要输出其他任何文字。"
"你是一位 Minecraft 风格的C++竞赛教练(矿石鉴定大师),请结合题目内容对学习笔记打分。\n"
"评分满分60分,经验值(rating)=round(score/10),范围0-6。\n"
"评分维度:\n"
"- 题意理解 15分是否正确理解题目要求\n"
"- 思路与算法 15分解题思路是否清晰、算法是否正确\n"
"- 代码记录 15分是否有代码片段/模板/关键实现\n"
"- 踩坑反思 15分是否记录了坑点、调试过程、经验教训\n"
"请用 Minecraft 游戏风格的语言给出反馈,使用⛏️🏆📜💎⚡等图标。\n"
"输出必须是纯JSON不要markdown代码块,不要输出其他任何文字。"
)
user = {
"task": "对学习笔记评分并给出改进建议",
"task": "结合题目对学习笔记评分并给出改进建议",
"problem": {
"id": payload.get("problem_id"),
"title": payload.get("problem_title"),
"title": problem_title,
"statement": problem_statement,
},
"note": payload.get("note", ""),
"output_json_schema": {
"score": "integer 0-100",
"rating": "integer 1-10",
"feedback_md": "markdown string",
"score": "integer 0-60",
"rating": "integer 0-6",
"feedback_md": "markdown string, Minecraft style",
"model_name": "string",
},
}
@@ -105,14 +117,21 @@ def call_llm(payload: Dict[str, Any]) -> Dict[str, Any]:
raise RuntimeError(f"HTTP {resp.status_code}")
data = resp.json()
content = data["choices"][0]["message"]["content"]
parsed = json.loads(content)
# Strip markdown code fence if present
c = content.strip()
if c.startswith("```"):
c = c.split("\n", 1)[-1]
if c.endswith("```"):
c = c[:-3]
c = c.strip()
parsed = json.loads(c)
if not isinstance(parsed, dict):
raise ValueError("model output not object")
score = int(parsed.get("score", 0))
score = max(0, min(100, score))
score = max(0, min(60, score))
rating = int(parsed.get("rating", round(score / 10)))
rating = max(1, min(10, rating))
feedback_md = str(parsed.get("feedback_md", "")).strip() or "### 笔记评分\n- 请补充更多内容(学习目标/代码/总结)。"
rating = max(0, min(6, rating))
feedback_md = str(parsed.get("feedback_md", "")).strip() or "### ⛏️ 矿石鉴定报告\n- 请补充更多内容(探索目标/代码配方/总结)。"
model_name = str(parsed.get("model_name", model)).strip() or model
return {"score": score, "rating": rating, "feedback_md": feedback_md, "model_name": model_name}
except Exception as e: # noqa: BLE001
@@ -132,7 +151,7 @@ def main() -> int:
if not note.strip():
print(
json.dumps(
{"score": 0, "rating": 1, "feedback_md": "### 笔记为空\n请先写笔记再评分", "model_name": "validator"},
{"score": 0, "rating": 0, "feedback_md": "### ⛏️ 空白卷轴\n请先写下你的探索笔记再进行鉴定", "model_name": "validator"},
ensure_ascii=False,
)
)