Skip to content

Commit fc1df8c

Browse files
rootroot
authored andcommitted
fix: neutral placeholder, exam edge cases, stronger invariants (review)
1 parent 4bf87a2 commit fc1df8c

2 files changed

Lines changed: 64 additions & 11 deletions

File tree

app/extended_curriculum.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from app.lessons_13_25 import LESSONS_13_25
1414

1515
TERM_SYNONYMS = {
16+
"sorted": ("сортировка",),
1617
"арифметический оператор": ("арифметика", "оператор"),
1718
"оператор %": ("%", "остаток"),
1819
"оператор and": ("and", "логическое и"),
@@ -1422,7 +1423,7 @@ def _make_questions(
14221423
"kind": "input",
14231424
"prompt": f"Какой ключевой инструмент или термин связывает урок «{title}»?",
14241425
"answers": [keyword, *TERM_SYNONYMS.get(keyword, ())],
1425-
"placeholder": f"Например: {keyword}",
1426+
"placeholder": "Введите термин",
14261427
"explanation": f"Ключевой ориентир урока — «{keyword}». {subtitle}.",
14271428
},
14281429
{"id": f"{lesson_id}-code", "kind": "code", **code_task},
@@ -1481,13 +1482,16 @@ def build_extended_course() -> tuple[
14811482
}
14821483
)
14831484
else:
1484-
questions = next(lesson for lesson in LESSONS_13_25 if lesson["id"] == lesson_id)["questions"]
1485+
questions = next(lesson for lesson in LESSONS_13_25 if lesson["id"] == lesson_id)[
1486+
"questions"
1487+
]
14851488
for question in questions:
14861489
question_ids_by_kind[question["kind"]].append(question["id"])
14871490
order += 1
14881491
question_ids = [
14891492
random.Random(f"{module_id}:{kind}").choice(ids)
14901493
for kind, ids in question_ids_by_kind.items()
1494+
if ids
14911495
]
14921496
remaining_ids = [
14931497
question_id
@@ -1496,7 +1500,8 @@ def build_extended_course() -> tuple[
14961500
if question_id not in question_ids
14971501
]
14981502
random.Random(f"{module_id}:exam").shuffle(remaining_ids)
1499-
question_ids.append(remaining_ids[0])
1503+
if remaining_ids:
1504+
question_ids.append(remaining_ids[0])
15001505
random.Random(f"{module_id}:exam-order").shuffle(question_ids)
15011506
exams[module_id] = {
15021507
"title": f"Контрольная точка: {unit['title']}",

tests/test_curriculum.py

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import re
22
from pathlib import Path
33

4+
from app import extended_curriculum
45
from app.content import EXAMS, LESSONS, MODULES, QUESTION_BY_ID
5-
from app.evaluator import evaluate
6-
from app.extended_curriculum import EXTRA_LESSONS
6+
from app.evaluator import evaluate, normalize
7+
from app.extended_curriculum import EXTRA_LESSONS, build_extended_course
78
from app.lessons_13_25 import LESSONS_13_25
89

910
LESSONS_13_25_IDENTITY = [
@@ -114,13 +115,14 @@ def test_lessons_four_to_six_do_not_require_future_topics() -> None:
114115

115116
def test_extended_questions_are_fair_and_exams_are_mixed() -> None:
116117
extended_lessons = [lesson for lesson in LESSONS if lesson["order"] >= 13]
117-
choice_positions = {
118+
choice_positions = [
118119
question["options"].index(question["answer"])
119120
for lesson in extended_lessons
120121
for question in lesson["questions"]
121122
if question["kind"] == "choice"
122-
}
123-
assert len(choice_positions) > 1
123+
]
124+
assert len(set(choice_positions)) > 1
125+
assert all(0 <= position < 3 for position in choice_positions)
124126
assert (
125127
sum(
126128
len(question["answers"]) > 1
@@ -132,6 +134,52 @@ def test_extended_questions_are_fair_and_exams_are_mixed() -> None:
132134
)
133135
extended_modules = {lesson["module_id"] for lesson in extended_lessons}
134136
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"}
137+
question_ids = EXAMS[module_id]["question_ids"]
138+
assert len(question_ids) == len(set(question_ids)) == 4
139+
assert {QUESTION_BY_ID[question_id]["kind"] for question_id in question_ids} == {
140+
"choice",
141+
"input",
142+
"code",
143+
}
144+
145+
term_question = next(
146+
question
147+
for lesson in extended_lessons
148+
for question in lesson["questions"]
149+
if question["kind"] == "input" and len(question["answers"]) > 1
150+
)
151+
assert all(
152+
normalize(answer) not in normalize(term_question["placeholder"])
153+
for answer in term_question["answers"]
154+
)
155+
156+
keyword, synonyms = "sorted", ("сортировка",)
157+
answers = next(
158+
question["answers"]
159+
for lesson in extended_lessons
160+
for question in lesson["questions"]
161+
if question["kind"] == "input" and keyword in question["answers"]
162+
)
163+
assert normalize(f" {synonyms[0].upper()} ") in {normalize(answer) for answer in answers}
164+
165+
166+
def test_extended_generation_is_deterministic_and_handles_short_modules(monkeypatch) -> None:
167+
first = build_extended_course()
168+
second = build_extended_course()
169+
assert first == second
170+
171+
monkeypatch.setattr(
172+
extended_curriculum,
173+
"COURSE_UNITS",
174+
[
175+
{
176+
**extended_curriculum.COURSE_UNITS[0],
177+
"lessons": extended_curriculum.COURSE_UNITS[0]["lessons"][:1],
178+
}
179+
],
180+
)
181+
monkeypatch.setattr(
182+
extended_curriculum, "TASK_CYCLES", [extended_curriculum.TASK_CYCLES[0][:1]]
183+
)
184+
_, _, exams = build_extended_course()
185+
assert len(next(iter(exams.values()))["question_ids"]) == 3

0 commit comments

Comments
 (0)