Download mathly2/lessons.py from wzender/mathly: direct link, hf CLI and curl.
- Browser
- Download file 5.11 kB
-
https://huggingface.co/spaces/wzender/mathly/resolve/main/mathly2/lessons.py
- Command line
-
hf download hf://spaces/wzender/mathly/mathly2/lessons.py
-
curl -L -o lessons.py https://huggingface.co/spaces/wzender/mathly/resolve/main/mathly2/lessons.py
5.11 kB
| """Lesson documents kept in the database (add-subjects-and-worksheets): giving their parts ids, and the checks before publishing. | |
| The document's shape is described in bank.py. The editor, the JSON load and the AI's draft may hand in skills and mistakes without | |
| ids, or with temporary ones; `assign_ids` gives each a server-made id `w<lesson>_<k>` / `w<lesson>_m<k>` from counters kept in | |
| the document that only grow, so an id is never reused after a delete and a reorder changes none. | |
| """ | |
| from __future__ import annotations | |
| import copy | |
| import re | |
| from .bank import lesson_unit_id | |
| def _counter(document: dict, key: str, used: list[int]) -> int: | |
| return max([int(document.get(key) or 1)] + [n + 1 for n in used]) | |
| def assign_ids(document: dict, lesson_id: int) -> dict: | |
| """A copy of the document in which every skill and mistake has an id of this lesson, choices point at the renamed mistakes, | |
| and the counters are past every id used.""" | |
| doc = copy.deepcopy(document) | |
| prefix = lesson_unit_id(lesson_id) | |
| skill_id, mistake_id = re.compile(rf"^{prefix}_(\d+)$"), re.compile(rf"^{prefix}_m(\d+)$") | |
| skills = doc.get("skills") if isinstance(doc.get("skills"), list) else [] | |
| mistakes = doc.get("mistakes") if isinstance(doc.get("mistakes"), dict) else {} | |
| next_skill = _counter(doc, "next_skill", [int(m.group(1)) for s in skills if isinstance(s, dict) for m in [skill_id.match(str(s.get("id", "")))] if m]) | |
| for skill in skills: | |
| if isinstance(skill, dict) and not skill_id.match(str(skill.get("id", ""))): | |
| skill["id"] = f"{prefix}_{next_skill}" | |
| next_skill += 1 | |
| next_mistake = _counter(doc, "next_mistake", [int(m.group(1)) for k in mistakes for m in [mistake_id.match(k)] if m]) | |
| renamed: dict[str, str] = {} | |
| for key in list(mistakes): | |
| if not mistake_id.match(key): | |
| renamed[key] = f"{prefix}_m{next_mistake}" | |
| next_mistake += 1 | |
| doc["mistakes"] = {renamed.get(k, k): v for k, v in mistakes.items()} | |
| for skill in skills: | |
| for question in (skill.get("questions") or []) if isinstance(skill, dict) else []: | |
| for choice in question.get("choices") or [] if isinstance(question, dict) else []: | |
| if isinstance(choice, dict) and choice.get("mistake") in renamed: | |
| choice["mistake"] = renamed[choice["mistake"]] | |
| doc["skills"], doc["next_skill"], doc["next_mistake"] = skills, next_skill, next_mistake | |
| return doc | |
| MIN_QUESTIONS = 5 # questions a skill needs before its lesson can be published | |
| def publish_problems(document: dict, lesson_id: int) -> list[str]: | |
| """Every reason the draft cannot be published yet, in words and with where it is: a name, at least one skill, at least | |
| MIN_QUESTIONS questions per skill, exactly one right choice per question, and for every wrong choice a mistake with a name | |
| and a tip. Then the bank's own checks (bank.lesson_problems), so a published lesson always loads.""" | |
| from .bank import lesson_problems | |
| problems: list[str] = [] | |
| if not str(document.get("name") or "").strip(): | |
| problems.append("诇砖讬注讜专 讗讬谉 砖诐") | |
| skills, mistakes = document.get("skills") or [], document.get("mistakes") or {} | |
| if not skills: | |
| problems.append("讗讬谉 讘砖讬注讜专 讗祝 诪讬讜诪谞讜转") | |
| for si, skill in enumerate(skills, 1): | |
| where = f"诪讬讜诪谞讜转 {si}" | |
| if not str(skill.get("name") or "").strip(): | |
| problems.append(f"{where}: 讗讬谉 砖诐") | |
| questions = skill.get("questions") or [] | |
| if len(questions) < MIN_QUESTIONS: | |
| problems.append(f"{where}: 讬砖 {len(questions)} 砖讗诇讜转, 爪专讬讱 诇驻讞讜转 {MIN_QUESTIONS}") | |
| for qi, question in enumerate(questions, 1): | |
| at = f"{where}, 砖讗诇讛 {qi}" | |
| if not str(question.get("question") or "").strip(): | |
| problems.append(f"{at}: 讗讬谉 谞讜住讞") | |
| choices = question.get("choices") or [] | |
| right = sum(1 for c in choices if c.get("correct")) | |
| if right != 1: | |
| problems.append(f"{at}: 爪专讬讻讛 转砖讜讘讛 谞讻讜谞讛 讗讞转 讘讚讬讜拽 (讬砖 {right})") | |
| if len(choices) < 2: | |
| problems.append(f"{at}: 爪专讬讱 诇驻讞讜转 砖转讬 转砖讜讘讜转") | |
| for ci, choice in enumerate(choices, 1): | |
| if not str(choice.get("text") or "").strip(): | |
| problems.append(f"{at}, 转砖讜讘讛 {ci}: 讗讬谉 讟拽住讟") | |
| if choice.get("correct"): | |
| continue | |
| mistake = mistakes.get(choice.get("mistake") or "") | |
| if not mistake: | |
| problems.append(f"{at}, 转砖讜讘讛 {ci}: 诇转砖讜讘讛 讛砖讙讜讬讛 讗讬谉 讟注讜转") | |
| elif not str(mistake.get("name") or "").strip() or not str(mistake.get("tip") or "").strip(): | |
| problems.append(f"{at}, 转砖讜讘讛 {ci}: 诇讟注讜转 砖讛讬讗 诪专讗讛 讞住专 砖诐 讗讜 讟讬驻") | |
| if not problems: | |
| problems += lesson_problems(document, lesson_id) | |
| return problems | |