Skip to content

Commit 4bf87a2

Browse files
rootroot
authored andcommitted
feat: shuffled options, term synonyms, mixed exams (P0.3)
1 parent e287740 commit 4bf87a2

2 files changed

Lines changed: 67 additions & 7 deletions

File tree

app/extended_curriculum.py

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,24 @@
77

88
from __future__ import annotations
99

10+
import random
1011
from typing import Any
1112

13+
from app.lessons_13_25 import LESSONS_13_25
14+
15+
TERM_SYNONYMS = {
16+
"арифметический оператор": ("арифметика", "оператор"),
17+
"оператор %": ("%", "остаток"),
18+
"оператор and": ("and", "логическое и"),
19+
"срез": ("slice", "срез строки"),
20+
"метод replace": ("replace", "замена"),
21+
"метод join": ("join", "соединение строк"),
22+
"f-строка": ("f string", "форматированная строка"),
23+
"кортеж": ("tuple",),
24+
"распаковка": ("unpacking",),
25+
"append": ("метод append", "добавление в список"),
26+
}
27+
1228

1329
def _theory(title: str, text: str, example: str, tip: str = "") -> dict[str, str]:
1430
return {"title": title, "text": text, "example": example, "tip": tip}
@@ -1389,21 +1405,24 @@ def _make_questions(
13891405
) -> list[dict[str, Any]]:
13901406
_, title, subtitle, keyword, example, concept, _ = lesson_spec
13911407
code_task = _code_task(task_kind)
1408+
choice_id = f"{lesson_id}-choice"
1409+
options = [example, "print('Сначала пишем код, потом думаем')", "value = input() + 1"]
1410+
random.Random(f"{lesson_id}:{choice_id}").shuffle(options)
13921411
return [
13931412
{
1394-
"id": f"{lesson_id}-choice",
1413+
"id": choice_id,
13951414
"kind": "choice",
13961415
"prompt": f"Какой пример относится к теме «{title}»?",
1397-
"options": [example, "print('Сначала пишем код, потом думаем')", "value = input() + 1"],
1416+
"options": options,
13981417
"answer": example,
13991418
"explanation": concept,
14001419
},
14011420
{
14021421
"id": f"{lesson_id}-term",
14031422
"kind": "input",
14041423
"prompt": f"Какой ключевой инструмент или термин связывает урок «{title}»?",
1405-
"answers": [keyword],
1406-
"placeholder": "Например: словарь",
1424+
"answers": [keyword, *TERM_SYNONYMS.get(keyword, ())],
1425+
"placeholder": f"Например: {keyword}",
14071426
"explanation": f"Ключевой ориентир урока — «{keyword}». {subtitle}.",
14081427
},
14091428
{"id": f"{lesson_id}-code", "kind": "code", **code_task},
@@ -1429,11 +1448,10 @@ def build_extended_course() -> tuple[
14291448
"icon": unit["icon"],
14301449
}
14311450
)
1432-
question_ids: list[str] = []
1451+
question_ids_by_kind: dict[str, list[str]] = {"choice": [], "input": [], "code": []}
14331452
for lesson_index, spec in enumerate(unit["lessons"]):
14341453
slug, title, subtitle, keyword, example, concept, advice = spec
14351454
lesson_id = f"{module_id}-{slug}"
1436-
question_ids.append(f"{lesson_id}-choice")
14371455
if order >= 26:
14381456
questions = _make_questions(lesson_id, spec, TASK_CYCLES[unit_index][lesson_index])
14391457
lessons.append(
@@ -1462,10 +1480,27 @@ def build_extended_course() -> tuple[
14621480
"questions": questions,
14631481
}
14641482
)
1483+
else:
1484+
questions = next(lesson for lesson in LESSONS_13_25 if lesson["id"] == lesson_id)["questions"]
1485+
for question in questions:
1486+
question_ids_by_kind[question["kind"]].append(question["id"])
14651487
order += 1
1488+
question_ids = [
1489+
random.Random(f"{module_id}:{kind}").choice(ids)
1490+
for kind, ids in question_ids_by_kind.items()
1491+
]
1492+
remaining_ids = [
1493+
question_id
1494+
for ids in question_ids_by_kind.values()
1495+
for question_id in ids
1496+
if question_id not in question_ids
1497+
]
1498+
random.Random(f"{module_id}:exam").shuffle(remaining_ids)
1499+
question_ids.append(remaining_ids[0])
1500+
random.Random(f"{module_id}:exam-order").shuffle(question_ids)
14661501
exams[module_id] = {
14671502
"title": f"Контрольная точка: {unit['title']}",
1468-
"description": f"Четыре коротких вопроса по разделу «{unit['title']}».",
1503+
"description": f"Четыре вопроса разных типов по разделу «{unit['title']}».",
14691504
"question_ids": question_ids,
14701505
}
14711506
return modules, lessons, exams

tests/test_curriculum.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,3 +110,28 @@ def test_lessons_four_to_six_do_not_require_future_topics() -> None:
110110
assert QUESTION_BY_ID["while-code"]["tests"] == [
111111
{"kind": "stdout", "expected": "5\n4\n3\n2\n1\nПуск!"}
112112
]
113+
114+
115+
def test_extended_questions_are_fair_and_exams_are_mixed() -> None:
116+
extended_lessons = [lesson for lesson in LESSONS if lesson["order"] >= 13]
117+
choice_positions = {
118+
question["options"].index(question["answer"])
119+
for lesson in extended_lessons
120+
for question in lesson["questions"]
121+
if question["kind"] == "choice"
122+
}
123+
assert len(choice_positions) > 1
124+
assert (
125+
sum(
126+
len(question["answers"]) > 1
127+
for lesson in extended_lessons
128+
for question in lesson["questions"]
129+
if question["kind"] == "input"
130+
)
131+
>= 10
132+
)
133+
extended_modules = {lesson["module_id"] for lesson in extended_lessons}
134+
for module_id in extended_modules:
135+
assert {
136+
QUESTION_BY_ID[question_id]["kind"] for question_id in EXAMS[module_id]["question_ids"]
137+
} == {"choice", "input", "code"}

0 commit comments

Comments
 (0)