feat: add linux course diagnostics

This commit is contained in:
Codex
2026-03-19 14:58:56 +08:00
parent 83831b8622
commit f61376aa8a
3 changed files with 170 additions and 1 deletions

View File

@@ -28,6 +28,7 @@ PUBLIC_GET_PATHS = {
"/privacy.html",
"/api/course",
"/api/course/search",
"/api/diagnostics",
"/api/health",
"/api/lesson",
"/api/overview",
@@ -243,16 +244,80 @@ def build_lesson_preview(module: dict[str, Any], lesson: dict[str, Any]) -> dict
def build_module_preview(module: dict[str, Any]) -> dict[str, Any]:
lessons = [build_lesson_preview(module, lesson) for lesson in module.get("lessons", [])]
exercises = [exercise for lesson in module.get("lessons", []) for exercise in lesson.get("exercises", [])]
operations = [exercise for exercise in exercises if exercise.get("type") == "operation"]
reflections = [exercise for exercise in exercises if exercise.get("type") != "operation"]
commands = sorted(
{
command
for lesson in module.get("lessons", [])
for command in split_commands(lesson.get("command"), lesson.get("id"))
if command
}
)
return {
"id": module.get("id"),
"display_title": module_title(module),
"display_summary": module_summary(module),
"lesson_count": len(module.get("lessons", [])),
"exercise_count": sum(len(lesson.get("exercises", [])) for lesson in module.get("lessons", [])),
"exercise_count": len(exercises),
"operation_count": len(operations),
"reflection_count": len(reflections),
"command_count": len(commands),
"command_tokens": commands,
"lessons": lessons,
}
def extract_solution_commands() -> list[str]:
commands: list[str] = []
for module in COURSE.get("modules", []):
for lesson in module.get("lessons", []):
for exercise in lesson.get("exercises", []):
for solution in exercise.get("solution", []):
for segment in str(solution).split("|"):
head = segment.strip().split(" ")[0].strip()
if head:
commands.append(head)
return commands
def build_diagnostics() -> dict[str, Any]:
lessons = flatten_lessons()
course_commands = sorted(
{
command
for lesson in lessons
for command in split_commands(lesson.get("command"), lesson.get("id"))
if command
}
)
solution_commands = sorted(set(extract_solution_commands()))
required_commands = sorted(set(course_commands) | set(solution_commands))
supported_commands = sorted(SANDBOX.supported_commands())
unsupported_commands = [command for command in required_commands if command not in SANDBOX.supported_commands()]
total_exercises = sum(len(lesson.get("exercises", [])) for lesson in lessons)
operation_exercises = sum(
1
for lesson in lessons
for exercise in lesson.get("exercises", [])
if exercise.get("type") == "operation"
)
reflection_exercises = total_exercises - operation_exercises
return {
"supported_command_count": len(supported_commands),
"required_command_count": len(required_commands),
"covered_command_count": len(required_commands) - len(unsupported_commands),
"coverage_rate": 100 if not required_commands else round(((len(required_commands) - len(unsupported_commands)) / len(required_commands)) * 100)),
"unsupported_commands": unsupported_commands,
"course_commands": required_commands,
"operation_exercises": operation_exercises,
"reflection_exercises": reflection_exercises,
"module_checks": [build_module_preview(module) for module in COURSE.get("modules", [])],
}
def build_exercise(exercise: dict[str, Any], lesson_stub: dict[str, Any]) -> dict[str, Any]:
commands = split_commands(lesson_stub.get("command"), lesson_stub.get("id"))
label = command_label(commands)
@@ -343,6 +408,7 @@ def build_overview() -> dict[str, Any]:
)
exercise_count = sum(len(lesson.get("exercises", [])) for lesson in lessons)
meta = COURSE.get("meta", {})
diagnostics = build_diagnostics()
return {
"meta": {
@@ -362,6 +428,12 @@ def build_overview() -> dict[str, Any]:
"cwd": SANDBOX.cwd,
"user": SANDBOX.user,
},
"diagnostics": {
"coverage_rate": diagnostics["coverage_rate"],
"unsupported_count": len(diagnostics["unsupported_commands"]),
"operation_exercises": diagnostics["operation_exercises"],
"reflection_exercises": diagnostics["reflection_exercises"],
},
"modules": modules,
"commands": commands[:24],
}
@@ -531,6 +603,7 @@ class LinuxLearningHandler(http.server.BaseHTTPRequestHandler):
"module_count": overview["meta"]["module_count"],
"lesson_count": overview["meta"]["lesson_count"],
"exercise_count": overview["meta"]["exercise_count"],
"coverage_rate": overview["diagnostics"]["coverage_rate"],
}
)
return
@@ -543,6 +616,10 @@ class LinuxLearningHandler(http.server.BaseHTTPRequestHandler):
self.send_json(build_overview())
return
if path == "/api/diagnostics":
self.send_json(build_diagnostics())
return
if path == "/api/lesson":
lesson_id = urllib.parse.parse_qs(parsed.query).get("id", [""])[0]
if not lesson_id: