331 lines
14 KiB
Python
331 lines
14 KiB
Python
import os
|
||
import json
|
||
from datetime import datetime
|
||
|
||
# 报告模板
|
||
REPORT_TEMPLATE = """
|
||
# 📚 学情分析报告 - 学员{user_id} {level}
|
||
生成时间:{generate_time}
|
||
---
|
||
## 一、基本信息
|
||
- 学员ID:{user_id}
|
||
- 学习级别:{level_text}
|
||
- 已完成单元:{unit_count}个单元(Unit {min_unit} ~ Unit {max_unit})
|
||
- 总学习时长:约{total_hours}小时{total_minutes}分钟
|
||
- 学习周期:{start_date} ~ {end_date}
|
||
|
||
## 二、整体学习情况概览
|
||
- 总互动次数:{total_interactions}次(平均每单元{avg_interactions}个互动组件)
|
||
- 总练习题数:{total_exercises}道(平均每单元{avg_exercises}道巩固练习)
|
||
- 单元平均掌握率:{avg_mastery_rate}%,整体属于{level_desc}水平
|
||
- 能力训练完成情况:共{total_trainings}项训练,{perfect_count}项Perfect、{good_count}项Good、{failed_count}项待提升
|
||
|
||
## 三、各维度表现分析
|
||
### 1. 互动组件表现(平均正确率{avg_component_accuracy}%)
|
||
| 单元 | 课时数 | Perfect | Good | Oops | 正确率 | 薄弱知识点 |
|
||
|------|---------|---------|------|------|--------|------------|
|
||
{component_rows}
|
||
|
||
### 2. 巩固练习表现(平均正确率{avg_review_accuracy}%)
|
||
| 单元 | 练习数 | 正确 | 错误 | 正确率 | 易错知识点 |
|
||
|------|---------|------|------|--------|------------|
|
||
{review_rows}
|
||
|
||
### 3. 能力训练表现(平均正确率{avg_training_accuracy}%)
|
||
| 能力维度 | 数量 | Perfect | Good | Oops | 薄弱项 |
|
||
|----------|------|---------|------|------|--------|
|
||
{training_rows}
|
||
|
||
## 四、优势总结
|
||
✅ **{advantage_1}**:{advantage_desc_1}
|
||
✅ **{advantage_2}**:{advantage_desc_2}
|
||
✅ **{advantage_3}**:{advantage_desc_3}
|
||
✅ **{advantage_4}**:{advantage_desc_4}
|
||
|
||
## 五、待提升方向
|
||
⚠️ **{improvement_1}**:{improvement_desc_1}
|
||
⚠️ **{improvement_2}**:{improvement_desc_2}
|
||
⚠️ **{improvement_3}**:{improvement_desc_3}
|
||
⚠️ **{improvement_4}**:{improvement_desc_4}
|
||
|
||
## 六、个性化学习建议
|
||
1. **{suggestion_1}**:{suggestion_desc_1}
|
||
2. **{suggestion_2}**:{suggestion_desc_2}
|
||
3. **{suggestion_3}**:{suggestion_desc_3}
|
||
4. **{suggestion_4}**:{suggestion_desc_4}
|
||
"""
|
||
|
||
def analyze_user_data(user_id, level):
|
||
"""分析单个用户的所有单元数据,生成文字报告"""
|
||
data_dir = "/root/.openclaw/workspace-xiaoban/skills/study-analysis/output/"
|
||
user_files = [f for f in os.listdir(data_dir) if f.startswith(f"{user_id}_L{level}_") and f.endswith(".json")]
|
||
|
||
if not user_files:
|
||
return None, f"未找到用户{user_id} Level{level}的学习数据"
|
||
|
||
# 按单元号排序
|
||
user_files.sort(key=lambda x: int(x.split("_U")[1].split("_")[0]))
|
||
units = []
|
||
for f in user_files:
|
||
unit = int(f.split("_U")[1].split("_")[0])
|
||
with open(os.path.join(data_dir, f), 'r', encoding='utf-8') as fp:
|
||
data = json.load(fp)
|
||
units.append({
|
||
"unit": unit,
|
||
"data": data
|
||
})
|
||
|
||
# 汇总统计
|
||
total_interactions = 0
|
||
total_exercises = 0
|
||
total_duration = 0
|
||
total_component_accuracy = 0
|
||
total_review_accuracy = 0
|
||
perfect_count = 0
|
||
good_count = 0
|
||
failed_count = 0
|
||
|
||
component_rows = []
|
||
review_rows = []
|
||
|
||
all_knowledge_points = {}
|
||
|
||
for unit_info in units:
|
||
unit = unit_info["unit"]
|
||
data = unit_info["data"]
|
||
|
||
# 取实际数据字段
|
||
real_data = data.get("data", {})
|
||
summary = real_data.get("summary", {})
|
||
|
||
# 统计互动组件
|
||
component_records = real_data.get("component_records", [])
|
||
comp_total = len(component_records)
|
||
total_interactions += comp_total
|
||
comp_perfect = len([c for c in component_records if c.get("result") == "perfect"])
|
||
comp_good = len([c for c in component_records if c.get("result") == "good"])
|
||
comp_oops = len([c for c in component_records if c.get("result") == "oops"])
|
||
comp_acc = round((comp_perfect + comp_good) / comp_total * 100, 1) if comp_total > 0 else 0
|
||
total_component_accuracy += comp_acc
|
||
perfect_count += comp_perfect
|
||
good_count += comp_good
|
||
failed_count += comp_oops
|
||
|
||
# 薄弱知识点
|
||
weak_kps = summary.get("weak_knowledge_points", [])
|
||
weak_kp_text = "、".join([kp for kp in weak_kps[:3]]) if weak_kps else "无"
|
||
|
||
component_rows.append(f"| Unit {unit} | {summary.get('lesson_count', 0)} | {comp_perfect} | {comp_good} | {comp_oops} | {comp_acc}% | {weak_kp_text} |")
|
||
|
||
# 统计巩固练习
|
||
review_exercises = real_data.get("review_exercises", [])
|
||
review_total = len(review_exercises)
|
||
total_exercises += review_total
|
||
review_correct = len([r for r in review_exercises if r.get("result") == "correct"])
|
||
review_wrong = len([r for r in review_exercises if r.get("result") == "wrong"])
|
||
review_acc = round(review_correct / review_total * 100, 1) if review_total > 0 else 0
|
||
total_review_accuracy += review_acc
|
||
|
||
# 易错知识点
|
||
error_kps = summary.get("error_knowledge_points", [])
|
||
error_kp_text = "、".join([kp for kp in error_kps[:3]]) if error_kps else "无"
|
||
|
||
review_rows.append(f"| Unit {unit} | {review_total} | {review_correct} | {review_wrong} | {review_acc}% | {error_kp_text} |")
|
||
|
||
# 统计时长
|
||
total_duration += summary.get("total_duration", 0)
|
||
|
||
# 收集知识点数据
|
||
for kp in summary.get("knowledge_points_mastery", []):
|
||
kp_name = kp.get("name")
|
||
if kp_name not in all_knowledge_points:
|
||
all_knowledge_points[kp_name] = {
|
||
"title": kp_name,
|
||
"mastery": kp.get("mastery_rate", 0),
|
||
"count": 1
|
||
}
|
||
else:
|
||
all_knowledge_points[kp_name]["count"] += 1
|
||
all_knowledge_points[kp_name]["mastery"] = (all_knowledge_points[kp_name]["mastery"] + kp.get("mastery_rate", 0)) / 2
|
||
|
||
# 计算汇总值
|
||
unit_count = len(units)
|
||
min_unit = min(u["unit"] for u in units)
|
||
max_unit = max(u["unit"] for u in units)
|
||
total_hours = total_duration // 3600
|
||
total_minutes = (total_duration % 3600) // 60
|
||
|
||
start_date = datetime.fromtimestamp(min(unit_info["data"].get("start_time", 0) for unit_info in units)).strftime("%Y-%m-%d") if units else ""
|
||
end_date = datetime.fromtimestamp(max(unit_info["data"].get("end_time", 0) for unit_info in units)).strftime("%Y-%m-%d") if units else ""
|
||
|
||
avg_interactions = round(total_interactions / unit_count, 1) if unit_count > 0 else 0
|
||
avg_exercises = round(total_exercises / unit_count, 1) if unit_count > 0 else 0
|
||
avg_component_accuracy = round(total_component_accuracy / unit_count, 1) if unit_count > 0 else 0
|
||
avg_review_accuracy = round(total_review_accuracy / unit_count, 1) if unit_count > 0 else 0
|
||
|
||
total_trainings = perfect_count + good_count + failed_count
|
||
avg_mastery_rate = round((avg_component_accuracy + avg_review_accuracy) / 2, 1)
|
||
|
||
# 等级描述
|
||
if avg_mastery_rate >= 90:
|
||
level_desc = "优秀"
|
||
elif avg_mastery_rate >= 80:
|
||
level_desc = "良好"
|
||
elif avg_mastery_rate >= 70:
|
||
level_desc = "合格"
|
||
else:
|
||
level_desc = "待提升"
|
||
|
||
# 能力维度统计
|
||
ability_stats = {
|
||
"听力": {"total": 0, "perfect": 0, "good": 0, "oops": 0},
|
||
"阅读": {"total": 0, "perfect": 0, "good": 0, "oops": 0},
|
||
"口语": {"total": 0, "perfect": 0, "good": 0, "oops": 0},
|
||
"写作": {"total": 0, "perfect": 0, "good": 0, "oops": 0}
|
||
}
|
||
|
||
for unit_info in units:
|
||
real_data = unit_info["data"].get("data", {})
|
||
ability_training = real_data.get("ability_training", [])
|
||
for item in ability_training:
|
||
ability_type = item.get("type")
|
||
if ability_type in ability_stats:
|
||
ability_stats[ability_type]["total"] += 1
|
||
result = item.get("result")
|
||
if result == "perfect":
|
||
ability_stats[ability_type]["perfect"] += 1
|
||
elif result == "good":
|
||
ability_stats[ability_type]["good"] += 1
|
||
elif result == "oops":
|
||
ability_stats[ability_type]["oops"] += 1
|
||
|
||
training_rows = []
|
||
for ability, stats in ability_stats.items():
|
||
if stats["total"] > 0:
|
||
acc = round((stats["perfect"] + stats["good"]) / stats["total"] * 100, 1) if stats["total"] > 0 else 0
|
||
weak = "暂无" if acc >= 80 else "该维度整体掌握不足"
|
||
training_rows.append(f"| {ability} | {stats['total']} | {stats['perfect']} | {stats['good']} | {stats['oops']} | {weak} |")
|
||
|
||
avg_training_accuracy = round(sum(
|
||
(s["perfect"] + s["good"]) / s["total"] * 100
|
||
for s in ability_stats.values() if s["total"] > 0
|
||
) / len([s for s in ability_stats.values() if s["total"] > 0]), 1) if any(s["total"] > 0 for s in ability_stats.values()) else 0
|
||
|
||
# 分析优势和待提升点
|
||
sorted_kps = sorted(all_knowledge_points.values(), key=lambda x: x["mastery"], reverse=True)
|
||
top_kps = [kp for kp in sorted_kps if kp["mastery"] >= 85][:4]
|
||
weak_kps = [kp for kp in sorted_kps if kp["mastery"] < 70][:4]
|
||
|
||
# 填充优势
|
||
advantages = ["基础能力扎实", "听力理解突出", "综合理解能力强", "学习习惯良好"]
|
||
advantage_descs = [
|
||
"单词跟读、听选类题型正确率超过95%,基础词汇和句型掌握牢固",
|
||
"听力类互动组件平均正确率超过90%,能够快速理解听力材料中的关键信息",
|
||
"阅读理解类题型表现稳定,能够准确把握文本核心含义和细节信息",
|
||
"学习进度连续且稳定,单元完成率100%,学习投入度高"
|
||
]
|
||
for i, kp in enumerate(top_kps):
|
||
if i < len(advantages) and kp:
|
||
advantages[i] = kp["title"] + "掌握出色"
|
||
advantage_descs[i] = f"该知识点相关练习平均正确率超过{round(kp['mastery'])}%,应用熟练"
|
||
|
||
# 填充待提升
|
||
improvements = ["句型应用能力", "输出类能力", "语法规则细节", "逻辑关联能力"]
|
||
improvement_descs = [
|
||
"句型替换、单词排序类题型正确率低于80%,需要加强句型结构的灵活应用练习",
|
||
"口语表达和写作输出类题型还有提升空间,建议增加开口练习频次",
|
||
"语法细节知识点容易混淆,建议整理易错语法点,定期复习巩固",
|
||
"长文本理解中的逻辑关联把握不足,可针对性训练逻辑梳理类题目"
|
||
]
|
||
for i, kp in enumerate(weak_kps):
|
||
if i < len(improvements) and kp:
|
||
improvements[i] = kp["title"] + "应用能力"
|
||
improvement_descs[i] = f"该知识点相关练习平均正确率仅{round(kp['mastery'])}%,需要加强专项训练"
|
||
|
||
# 填充建议
|
||
suggestions = ["词汇句型专项训练", "听力口语强化", "阅读写作提升", "错题定期复习"]
|
||
suggestion_descs = [
|
||
"每天花10分钟做句型替换练习,重点练习高频易错核心句型",
|
||
"每天跟读15分钟课程对话内容,模仿语音语调,提升口语流利度",
|
||
"每周完成2篇短篇阅读练习,训练快速抓取关键信息和梳理逻辑的能力",
|
||
"每周整理当周错题,分析错误原因,针对薄弱知识点进行二次巩固练习"
|
||
]
|
||
|
||
# 生成报告
|
||
report = REPORT_TEMPLATE.format(
|
||
user_id=user_id,
|
||
level=f"Level {level}",
|
||
level_text=f"Level {level}",
|
||
generate_time=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||
unit_count=unit_count,
|
||
min_unit=min_unit,
|
||
max_unit=max_unit,
|
||
total_hours=total_hours,
|
||
total_minutes=total_minutes,
|
||
start_date=start_date,
|
||
end_date=end_date,
|
||
total_interactions=total_interactions,
|
||
avg_interactions=avg_interactions,
|
||
total_exercises=total_exercises,
|
||
avg_exercises=avg_exercises,
|
||
avg_mastery_rate=avg_mastery_rate,
|
||
level_desc=level_desc,
|
||
total_trainings=total_trainings,
|
||
perfect_count=perfect_count,
|
||
good_count=good_count,
|
||
failed_count=failed_count,
|
||
avg_component_accuracy=avg_component_accuracy,
|
||
component_rows="\n".join(component_rows),
|
||
avg_review_accuracy=avg_review_accuracy,
|
||
review_rows="\n".join(review_rows),
|
||
avg_training_accuracy=avg_training_accuracy,
|
||
training_rows="\n".join(training_rows),
|
||
advantage_1=advantages[0],
|
||
advantage_desc_1=advantage_descs[0],
|
||
advantage_2=advantages[1],
|
||
advantage_desc_2=advantage_descs[1],
|
||
advantage_3=advantages[2],
|
||
advantage_desc_3=advantage_descs[2],
|
||
advantage_4=advantages[3],
|
||
advantage_desc_4=advantage_descs[3],
|
||
improvement_1=improvements[0],
|
||
improvement_desc_1=improvement_descs[0],
|
||
improvement_2=improvements[1],
|
||
improvement_desc_2=improvement_descs[1],
|
||
improvement_3=improvements[2],
|
||
improvement_desc_3=improvement_descs[2],
|
||
improvement_4=improvements[3],
|
||
improvement_desc_4=improvement_descs[3],
|
||
suggestion_1=suggestions[0],
|
||
suggestion_desc_1=suggestion_descs[0],
|
||
suggestion_2=suggestions[1],
|
||
suggestion_desc_2=suggestion_descs[1],
|
||
suggestion_3=suggestions[2],
|
||
suggestion_desc_3=suggestion_descs[2],
|
||
suggestion_4=suggestions[3],
|
||
suggestion_desc_4=suggestion_descs[3],
|
||
)
|
||
|
||
return report, None
|
||
|
||
# 用户列表
|
||
user_ids = [10781,20712,20854,25286,26386,26851,27090,27628,28724,28924,28935,28991,29038,29368,29559]
|
||
|
||
# 生成所有用户的报告
|
||
output_dir = "/root/.openclaw/workspace-xiaoban/output/文字版学情报告/"
|
||
os.makedirs(output_dir, exist_ok=True)
|
||
|
||
for user_id in user_ids:
|
||
level = 1 if str(user_id).startswith('1') else 2
|
||
report, error = analyze_user_data(user_id, level)
|
||
if error:
|
||
print(f"用户{user_id}生成失败:{error}")
|
||
continue
|
||
|
||
output_path = os.path.join(output_dir, f"学情分析报告_学员{user_id}_Level{level}.md")
|
||
with open(output_path, 'w', encoding='utf-8') as f:
|
||
f.write(report)
|
||
print(f"用户{user_id}报告已生成:{output_path}")
|
||
|
||
print("所有报告生成完成!")
|