|
|
|
@@ -310,6 +310,28 @@ def build_response_categorisation(task: TaskInfo) -> dict:
|
|
|
|
|
return {"categorisation": result}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def build_response_writing_challenge(task: TaskInfo) -> dict:
|
|
|
|
|
"""writing-challenge: 写作挑战 — 优先使用expectedResponse,否则提交占位文本"""
|
|
|
|
|
expected = task.expected_response.get("contents", {}).get("writingChallenge", {})
|
|
|
|
|
if expected:
|
|
|
|
|
return {"writingChallenge": expected}
|
|
|
|
|
|
|
|
|
|
# 回退:提交一段较长占位文本(确保满足最低字数要求)
|
|
|
|
|
return {
|
|
|
|
|
"writingChallenge": {
|
|
|
|
|
"userInput": (
|
|
|
|
|
"I think this is a good topic. There are many things to consider "
|
|
|
|
|
"when writing about this subject. First of all, it is important to "
|
|
|
|
|
"understand the main ideas and think carefully about the best way to "
|
|
|
|
|
"express your thoughts. In my opinion, practice is the key to success "
|
|
|
|
|
"and everyone should try their best to improve their skills every day. "
|
|
|
|
|
"This is why I believe that learning English is very useful for "
|
|
|
|
|
"communication with people from different countries and cultures."
|
|
|
|
|
),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 任务类型 → 答题策略映射
|
|
|
|
|
RESPONSE_BUILDERS = {
|
|
|
|
|
"media-with-time-markers": build_response_media_with_time_markers,
|
|
|
|
@@ -322,6 +344,7 @@ RESPONSE_BUILDERS = {
|
|
|
|
|
"multiple-choice": build_response_multiple_choice,
|
|
|
|
|
"text-highlights": build_response_text_highlights,
|
|
|
|
|
"categorisation": build_response_categorisation,
|
|
|
|
|
"writing-challenge": build_response_writing_challenge,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -337,6 +360,9 @@ class EFCourseAutopilot:
|
|
|
|
|
- 步骤②-④ (api.ef.studio域): 需要 Authorization: Bearer JWT token
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
# 类级别:本次运行中已处理过的课程 (course_id:node_id)
|
|
|
|
|
_processed_lessons: set = set()
|
|
|
|
|
|
|
|
|
|
def __init__(
|
|
|
|
|
self,
|
|
|
|
|
token: str = "",
|
|
|
|
@@ -376,6 +402,7 @@ class EFCourseAutopilot:
|
|
|
|
|
self.lesson = LessonSession()
|
|
|
|
|
self.activities: list[ActivityInfo] = []
|
|
|
|
|
self.results: list[SubmitResult] = []
|
|
|
|
|
self.repeat_detected = False
|
|
|
|
|
|
|
|
|
|
def _setup_session(self):
|
|
|
|
|
"""配置HTTP会话的通用headers和SSL设置"""
|
|
|
|
@@ -848,6 +875,57 @@ class EFCourseAutopilot:
|
|
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
def _save_diagnostic_report(self):
|
|
|
|
|
"""当检测到重复课程时,保存诊断报告供开发者分析"""
|
|
|
|
|
import datetime
|
|
|
|
|
|
|
|
|
|
report = {
|
|
|
|
|
"timestamp": datetime.datetime.now().isoformat(),
|
|
|
|
|
"course_id": self.ctx.course_id,
|
|
|
|
|
"node_id": self.ctx.node_id,
|
|
|
|
|
"unit_id": self.ctx.unit_id,
|
|
|
|
|
"level_id": self.ctx.level_id,
|
|
|
|
|
"lesson_id": self.lesson.lesson_id,
|
|
|
|
|
"session_id": self.lesson.session_id,
|
|
|
|
|
"activities": [],
|
|
|
|
|
"results": [],
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for act in self.activities:
|
|
|
|
|
act_info = {
|
|
|
|
|
"activity_id": act.activity_id,
|
|
|
|
|
"tasks": [
|
|
|
|
|
{
|
|
|
|
|
"task_id": t.task_id,
|
|
|
|
|
"task_type": t.task_type,
|
|
|
|
|
"task_detail": t.task_detail,
|
|
|
|
|
"expected_response": t.expected_response,
|
|
|
|
|
}
|
|
|
|
|
for t in act.tasks
|
|
|
|
|
],
|
|
|
|
|
}
|
|
|
|
|
report["activities"].append(act_info)
|
|
|
|
|
|
|
|
|
|
for r in self.results:
|
|
|
|
|
report["results"].append({
|
|
|
|
|
"task_id": r.task_id,
|
|
|
|
|
"task_type": r.task_type,
|
|
|
|
|
"success": r.success,
|
|
|
|
|
"skipped": r.skipped,
|
|
|
|
|
"error": r.error,
|
|
|
|
|
"new_version": r.new_version,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
safe_cid = self.ctx.course_id[:8] if self.ctx.course_id else "unknown"
|
|
|
|
|
safe_nid = self.ctx.node_id[:8] if self.ctx.node_id else "unknown"
|
|
|
|
|
filename = f"diagnostic_{safe_cid}_{safe_nid}_{int(time.time())}.json"
|
|
|
|
|
|
|
|
|
|
with open(filename, "w") as f:
|
|
|
|
|
json.dump(report, f, ensure_ascii=False, indent=2)
|
|
|
|
|
|
|
|
|
|
self._log(f"\n 诊断报告已保存至: {filename}")
|
|
|
|
|
self._log(f" 请将此文件提供给开发者以优化对新课程的支持")
|
|
|
|
|
|
|
|
|
|
# --------------------------------------------------------
|
|
|
|
|
# 主流程
|
|
|
|
|
# --------------------------------------------------------
|
|
|
|
@@ -887,6 +965,16 @@ class EFCourseAutopilot:
|
|
|
|
|
else:
|
|
|
|
|
self.step_get_focus()
|
|
|
|
|
|
|
|
|
|
# 检测是否重复处理同一课程
|
|
|
|
|
lesson_key = f"{self.ctx.course_id}:{self.ctx.node_id}"
|
|
|
|
|
if lesson_key in self._processed_lessons:
|
|
|
|
|
self.repeat_detected = True
|
|
|
|
|
self._log("\n⚠️ 检测到重复课程!该课程已处理过但未能晋级。")
|
|
|
|
|
self._log(" 请手动登录平台检查课程状态,或联系开发者分析。")
|
|
|
|
|
self._save_diagnostic_report()
|
|
|
|
|
self._log(f" 诊断文件已保存,请提供给开发者")
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
# 步骤②: 注册课程会话
|
|
|
|
|
self.step_open_lesson_enrollment()
|
|
|
|
|
|
|
|
|
@@ -953,6 +1041,9 @@ class EFCourseAutopilot:
|
|
|
|
|
# 步骤⑤: 检查并激活下一单元
|
|
|
|
|
self.step_activate_next_unit()
|
|
|
|
|
|
|
|
|
|
# 标记该课程已处理(避免下次循环重复提交)
|
|
|
|
|
self._processed_lessons.add(f"{self.ctx.course_id}:{self.ctx.node_id}")
|
|
|
|
|
|
|
|
|
|
return all(r.success for r in self.results)
|
|
|
|
|
|
|
|
|
|
except requests.exceptions.HTTPError as e:
|
|
|
|
|