feat: add linux course diagnostics
This commit is contained in:
79
server.py
79
server.py
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user