ovos-skill-math-practice 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. ovos_skill_math_practice/__init__.py +249 -0
  2. ovos_skill_math_practice/locale/da-dk/count_to.intent +1 -0
  3. ovos_skill_math_practice/locale/da-dk/count_too_high.dialog +1 -0
  4. ovos_skill_math_practice/locale/da-dk/counting.dialog +1 -0
  5. ovos_skill_math_practice/locale/da-dk/number_not_understood.dialog +1 -0
  6. ovos_skill_math_practice/locale/da-dk/operation_aliases.json +12 -0
  7. ovos_skill_math_practice/locale/da-dk/operation_not_understood.dialog +1 -0
  8. ovos_skill_math_practice/locale/da-dk/quiz_correct.dialog +2 -0
  9. ovos_skill_math_practice/locale/da-dk/quiz_finished.dialog +1 -0
  10. ovos_skill_math_practice/locale/da-dk/quiz_general.intent +3 -0
  11. ovos_skill_math_practice/locale/da-dk/quiz_incorrect.dialog +1 -0
  12. ovos_skill_math_practice/locale/da-dk/quiz_no_answer.dialog +1 -0
  13. ovos_skill_math_practice/locale/da-dk/quiz_operation.intent +2 -0
  14. ovos_skill_math_practice/locale/da-dk/quiz_question_add.dialog +1 -0
  15. ovos_skill_math_practice/locale/da-dk/quiz_question_divide.dialog +1 -0
  16. ovos_skill_math_practice/locale/da-dk/quiz_question_multiply.dialog +1 -0
  17. ovos_skill_math_practice/locale/da-dk/quiz_question_subtract.dialog +1 -0
  18. ovos_skill_math_practice/locale/da-dk/quiz_table.intent +2 -0
  19. ovos_skill_math_practice/locale/da-dk/recite_table.intent +2 -0
  20. ovos_skill_math_practice/locale/da-dk/skill.json +23 -0
  21. ovos_skill_math_practice/locale/da-dk/table_row.dialog +1 -0
  22. ovos_skill_math_practice/locale/en-us/count_to.intent +1 -0
  23. ovos_skill_math_practice/locale/en-us/count_too_high.dialog +1 -0
  24. ovos_skill_math_practice/locale/en-us/counting.dialog +1 -0
  25. ovos_skill_math_practice/locale/en-us/number_not_understood.dialog +1 -0
  26. ovos_skill_math_practice/locale/en-us/operation_aliases.json +9 -0
  27. ovos_skill_math_practice/locale/en-us/operation_not_understood.dialog +1 -0
  28. ovos_skill_math_practice/locale/en-us/quiz_correct.dialog +2 -0
  29. ovos_skill_math_practice/locale/en-us/quiz_finished.dialog +1 -0
  30. ovos_skill_math_practice/locale/en-us/quiz_general.intent +3 -0
  31. ovos_skill_math_practice/locale/en-us/quiz_incorrect.dialog +1 -0
  32. ovos_skill_math_practice/locale/en-us/quiz_no_answer.dialog +1 -0
  33. ovos_skill_math_practice/locale/en-us/quiz_operation.intent +2 -0
  34. ovos_skill_math_practice/locale/en-us/quiz_question_add.dialog +1 -0
  35. ovos_skill_math_practice/locale/en-us/quiz_question_divide.dialog +1 -0
  36. ovos_skill_math_practice/locale/en-us/quiz_question_multiply.dialog +1 -0
  37. ovos_skill_math_practice/locale/en-us/quiz_question_subtract.dialog +1 -0
  38. ovos_skill_math_practice/locale/en-us/quiz_table.intent +2 -0
  39. ovos_skill_math_practice/locale/en-us/recite_table.intent +2 -0
  40. ovos_skill_math_practice/locale/en-us/skill.json +23 -0
  41. ovos_skill_math_practice/locale/en-us/table_row.dialog +1 -0
  42. ovos_skill_math_practice/version.py +5 -0
  43. ovos_skill_math_practice-0.0.1.dist-info/METADATA +104 -0
  44. ovos_skill_math_practice-0.0.1.dist-info/RECORD +48 -0
  45. ovos_skill_math_practice-0.0.1.dist-info/WHEEL +5 -0
  46. ovos_skill_math_practice-0.0.1.dist-info/entry_points.txt +2 -0
  47. ovos_skill_math_practice-0.0.1.dist-info/licenses/LICENSE +674 -0
  48. ovos_skill_math_practice-0.0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,249 @@
1
+ """
2
+ skill OVOS Math Practice
3
+ Copyright (C) 2026 Andreas Lorensen
4
+
5
+ This program is free software: you can redistribute it and/or modify
6
+ it under the terms of the GNU General Public License as published by
7
+ the Free Software Foundation, either version 3 of the License, or
8
+ (at your option) any later version.
9
+
10
+ This program is distributed in the hope that it will be useful,
11
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ GNU General Public License for more details.
14
+
15
+ You should have received a copy of the GNU General Public License
16
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+
18
+ ---
19
+
20
+ Math practice for kids (and anyone else) - three distinct modes,
21
+ deliberately kept separate rather than folded into one "do math"
22
+ intent:
23
+
24
+ 1. COUNTING - "count to ten" - pure recitation, no interaction.
25
+ 2. TABLE RECITATION - "say the 5 times table" - pure recitation of
26
+ 1x5 through 10x5, no interaction. Multiplication only - addition/
27
+ subtraction/division don't have a traditional "table" concept the
28
+ same way, so this mode is deliberately scoped to what "tabeller"
29
+ (the original request) actually means in everyday usage.
30
+ 3. QUIZ - "quiz me on the 3 times table" / "quiz me on addition" -
31
+ genuinely interactive: asks NUM_QUIZ_QUESTIONS questions one at a
32
+ time via get_response(), checks each spoken answer, and reports a
33
+ final score. Covers all four basic operations, not just
34
+ multiplication - this was an explicit scope correction from the
35
+ original "times tables" framing.
36
+
37
+ ARCHITECTURE NOTE: get_response(), NOT A BACKGROUND THREAD
38
+ -----------------------------------------------------------------
39
+ Unlike ovos-skill-metronome/ovos-skill-rhythm-box/ovos-skill-white-
40
+ noise, quiz mode needs no background thread - it's a sequential,
41
+ blocking conversation (ask, wait for STT to transcribe an answer,
42
+ check it, ask the next one) using OVOSSkill.get_response(), which
43
+ already handles the listen-and-transcribe round-trip. This is
44
+ architecturally simpler than the audio-loop skills, but introduces a
45
+ different kind of failure mode: a timed-out or unparseable spoken
46
+ answer, handled explicitly per question rather than crashing the
47
+ whole quiz.
48
+
49
+ PROBLEM GENERATION DEFAULTS (state your assumptions, then build)
50
+ -----------------------------------------------------------------
51
+ - NUM_QUIZ_QUESTIONS = 5 per quiz round.
52
+ - Times tables: the traditional 1-12 range.
53
+ - Addition/subtraction: operands 1-20, subtraction always kept
54
+ non-negative (larger number first) - a negative answer isn't
55
+ wrong, but wasn't the kind of practice this was built for.
56
+ - Division: always divides evenly (dividend is constructed as
57
+ divisor x quotient, both 1-10) - no fractional answers, since
58
+ spoken fraction-checking is a much harder problem than this skill
59
+ attempts to solve.
60
+ """
61
+
62
+ import json
63
+ import random
64
+ from pathlib import Path
65
+
66
+ from ovos_workshop.skills import OVOSSkill
67
+ from ovos_workshop.decorators import intent_handler
68
+ from ovos_number_parser import extract_number
69
+
70
+ NUM_QUIZ_QUESTIONS = 5
71
+ COUNT_MAX = 100 # sanity cap for "count to X" - not a hard product limit, just avoids absurdly long recitations
72
+ TABLE_MIN, TABLE_MAX = 1, 12
73
+ ADD_SUB_MIN, ADD_SUB_MAX = 1, 20
74
+ DIVIDE_FACTOR_MIN, DIVIDE_FACTOR_MAX = 1, 10
75
+
76
+ OPERATIONS = ["add", "subtract", "multiply", "divide"]
77
+
78
+
79
+ def generate_problem(operation, table=None):
80
+ """Returns (a, b, correct_answer) for the given operation.
81
+ `table` restricts multiplication to a specific times table (the
82
+ fixed factor); ignored for other operations."""
83
+ if operation == "multiply":
84
+ a = table if table else random.randint(2, TABLE_MAX)
85
+ b = random.randint(1, 10)
86
+ return a, b, a * b
87
+ elif operation == "add":
88
+ a = random.randint(ADD_SUB_MIN, ADD_SUB_MAX)
89
+ b = random.randint(ADD_SUB_MIN, ADD_SUB_MAX)
90
+ return a, b, a + b
91
+ elif operation == "subtract":
92
+ a = random.randint(ADD_SUB_MIN, ADD_SUB_MAX)
93
+ b = random.randint(ADD_SUB_MIN, ADD_SUB_MAX)
94
+ if b > a:
95
+ a, b = b, a # keep the result non-negative, see module docstring
96
+ return a, b, a - b
97
+ elif operation == "divide":
98
+ divisor = random.randint(DIVIDE_FACTOR_MIN, DIVIDE_FACTOR_MAX)
99
+ quotient = random.randint(DIVIDE_FACTOR_MIN, DIVIDE_FACTOR_MAX)
100
+ dividend = divisor * quotient
101
+ return dividend, divisor, quotient
102
+ raise ValueError(f"unknown operation: {operation!r}")
103
+
104
+
105
+ def multiplication_table(n, up_to=10):
106
+ """Returns [(1, n, 1*n), (2, n, 2*n), ..., (up_to, n, up_to*n)] -
107
+ the actual (factor, table_number, product) rows of a times table,
108
+ for both reciting and quizzing."""
109
+ return [(i, n, i * n) for i in range(1, up_to + 1)]
110
+
111
+
112
+ SKILL_ROOT = Path(__file__).resolve().parent
113
+ LOCALE_DIR = SKILL_ROOT / "locale"
114
+
115
+
116
+ def _load_operation_aliases_from_disk():
117
+ """locale/<lang>/operation_aliases.json - {spoken operation name:
118
+ key in OPERATIONS}. Same JSON-in-locale convention as the rest of
119
+ this project family."""
120
+ merged = {}
121
+ if not LOCALE_DIR.is_dir():
122
+ return merged
123
+ for lang_dir in sorted(LOCALE_DIR.iterdir()):
124
+ if not lang_dir.is_dir():
125
+ continue
126
+ alias_file = lang_dir / "operation_aliases.json"
127
+ if not alias_file.exists():
128
+ continue
129
+ with open(alias_file, encoding="utf-8") as f:
130
+ aliases = json.load(f)
131
+ lang = lang_dir.name.lower()
132
+ merged[lang] = {k: v for k, v in aliases.items() if not k.startswith("_")}
133
+ return merged
134
+
135
+
136
+ OPERATION_ALIASES = _load_operation_aliases_from_disk()
137
+
138
+
139
+ class MathPractice(OVOSSkill):
140
+
141
+ def _speak_number_not_understood(self):
142
+ self.speak_dialog("number_not_understood")
143
+
144
+ def _operation_aliases_for(self, lang):
145
+ lang = lang.lower()
146
+ return OPERATION_ALIASES.get(lang) or OPERATION_ALIASES.get("en-us", {})
147
+
148
+ def _resolve_operation(self, raw, lang):
149
+ """Exact match only, no fuzzy matching - same reasoning as
150
+ every other alias resolver in this project family: quizzing
151
+ someone on the wrong operation is a more confusing wrong
152
+ answer than a slightly mis-parsed number."""
153
+ if not raw:
154
+ return None
155
+ return self._operation_aliases_for(lang).get(raw.strip().lower())
156
+
157
+ # ------------------------------------------------------------------
158
+ # Counting
159
+ # ------------------------------------------------------------------
160
+
161
+ @intent_handler("count_to.intent")
162
+ def handle_count_to(self, message):
163
+ n_raw = message.data.get("number")
164
+ n = extract_number(n_raw, lang=self.lang) if n_raw else None
165
+ if n is False or n is None or n < 1:
166
+ self._speak_number_not_understood()
167
+ return
168
+ n = int(n)
169
+ if n > COUNT_MAX:
170
+ self.speak_dialog("count_too_high", {"max": COUNT_MAX})
171
+ return
172
+ numbers = ", ".join(str(i) for i in range(1, n + 1))
173
+ self.speak_dialog("counting", {"numbers": numbers})
174
+
175
+ # ------------------------------------------------------------------
176
+ # Table recitation
177
+ # ------------------------------------------------------------------
178
+
179
+ def _render_table_recitation(self, n):
180
+ """Builds the full 'one times N is ..., two times N is ...'
181
+ recitation by rendering table_row.dialog once per row (with
182
+ that row's own data) rather than speaking each row as a
183
+ separate utterance - keeps the per-row phrasing localized in
184
+ table_row.dialog while still producing one continuous spoken
185
+ response."""
186
+ lines = []
187
+ for i, table_n, product in multiplication_table(n):
188
+ rendered = self.resources.load_dialog_file(
189
+ "table_row", {"i": i, "n": table_n, "product": product})
190
+ lines.append(rendered[0])
191
+ return ". ".join(lines)
192
+
193
+ @intent_handler("recite_table.intent")
194
+ def handle_recite_table(self, message):
195
+ n_raw = message.data.get("number")
196
+ n = extract_number(n_raw, lang=self.lang) if n_raw else None
197
+ if n is False or n is None or n < 1:
198
+ self._speak_number_not_understood()
199
+ return
200
+ n = int(n)
201
+ self.speak(self._render_table_recitation(n))
202
+
203
+ # ------------------------------------------------------------------
204
+ # Quiz
205
+ # ------------------------------------------------------------------
206
+
207
+ def _ask_question(self, operation, a, b):
208
+ """Speaks the question and listens for a spoken answer -
209
+ returns the transcribed text, or None on timeout/no response."""
210
+ return self.get_response(dialog=f"quiz_question_{operation}", data={"a": a, "b": b})
211
+
212
+ def _run_quiz(self, operation, table=None):
213
+ correct_count = 0
214
+ for _ in range(NUM_QUIZ_QUESTIONS):
215
+ a, b, answer = generate_problem(operation, table)
216
+ response_text = self._ask_question(operation, a, b)
217
+ if response_text is None:
218
+ self.speak_dialog("quiz_no_answer")
219
+ continue
220
+ user_value = extract_number(response_text, lang=self.lang)
221
+ if user_value is not False and user_value == answer:
222
+ correct_count += 1
223
+ self.speak_dialog("quiz_correct")
224
+ else:
225
+ self.speak_dialog("quiz_incorrect", {"answer": answer})
226
+ self.speak_dialog("quiz_finished", {"correct": correct_count, "total": NUM_QUIZ_QUESTIONS})
227
+
228
+ @intent_handler("quiz_table.intent")
229
+ def handle_quiz_table(self, message):
230
+ n_raw = message.data.get("number")
231
+ n = extract_number(n_raw, lang=self.lang) if n_raw else None
232
+ if n is False or n is None or n < 1:
233
+ self._speak_number_not_understood()
234
+ return
235
+ self._run_quiz("multiply", table=int(n))
236
+
237
+ @intent_handler("quiz_operation.intent")
238
+ def handle_quiz_operation(self, message):
239
+ operation_raw = message.data.get("operation")
240
+ operation = self._resolve_operation(operation_raw, self.lang) if operation_raw else None
241
+ if operation_raw and operation is None:
242
+ self.speak_dialog("operation_not_understood", {"operation": operation_raw})
243
+ return
244
+ operation = operation or random.choice(OPERATIONS)
245
+ self._run_quiz(operation)
246
+
247
+ @intent_handler("quiz_general.intent")
248
+ def handle_quiz_general(self, message):
249
+ self._run_quiz(random.choice(OPERATIONS))
@@ -0,0 +1 @@
1
+ tæl til {number}
@@ -0,0 +1 @@
1
+ det er mange tal, lad os holde os til {max} eller derunder
@@ -0,0 +1 @@
1
+ {numbers}
@@ -0,0 +1 @@
1
+ jeg fangede ikke det tal
@@ -0,0 +1,12 @@
1
+ {
2
+ "_notes": [
3
+ "Standard danske matematiske fagtermer, høj tillid - ikke specialiseret jargon der kan staves forkert på samme måde som rytmeboks' genre-navne."
4
+ ],
5
+ "addition": "add",
6
+ "plus": "add",
7
+ "subtraktion": "subtract",
8
+ "minus": "subtract",
9
+ "multiplikation": "multiply",
10
+ "gange": "multiply",
11
+ "division": "divide"
12
+ }
@@ -0,0 +1 @@
1
+ jeg har ikke en quiz til {operation}, jeg kender addition, subtraktion, multiplikation og division
@@ -0,0 +1,2 @@
1
+ rigtigt
2
+ det er korrekt
@@ -0,0 +1 @@
1
+ du fik {correct} ud af {total} rigtige
@@ -0,0 +1,3 @@
1
+ giv mig en regnequiz
2
+ quiz mig i matematik
3
+ giv mig et regnestykke
@@ -0,0 +1 @@
1
+ ikke helt, svaret er {answer}
@@ -0,0 +1 @@
1
+ jeg hørte ikke et svar, lad os gå videre
@@ -0,0 +1,2 @@
1
+ quiz mig i {operation}
2
+ test mig i {operation}
@@ -0,0 +1 @@
1
+ hvad er {a} plus {b}
@@ -0,0 +1 @@
1
+ hvad er {a} divideret med {b}
@@ -0,0 +1 @@
1
+ hvad er {a} gange {b}
@@ -0,0 +1 @@
1
+ hvad er {a} minus {b}
@@ -0,0 +1,2 @@
1
+ quiz mig i {number} tabellen
2
+ test mig i {number} tabellen
@@ -0,0 +1,2 @@
1
+ sig {number} tabellen
2
+ recitér {number} tabellen
@@ -0,0 +1,23 @@
1
+ {
2
+ "skill_id": "ovos-skill-math-practice.andlo",
3
+ "source": "https://github.com/andlo/ovos-skill-math-practice",
4
+ "package_name": "ovos-skill-math-practice",
5
+ "pip_spec": "ovos-skill-math-practice",
6
+ "license": "GPL-3.0-or-later",
7
+ "author": "andlo",
8
+ "name": "Regnetræning",
9
+ "description": "Regnetræning til børn - tælle, tabel-recitation, og interaktive quizzer i plus, minus, gange og division. Fuldstændig offline.",
10
+ "examples": [
11
+ "tæl til ti",
12
+ "sig 5 tabellen",
13
+ "quiz mig i 3 tabellen"
14
+ ],
15
+ "tags": [
16
+ "matematik",
17
+ "undervisning",
18
+ "tabeller",
19
+ "quiz",
20
+ "børn"
21
+ ],
22
+ "icon": "https://raw.githubusercontent.com/andlo/ovos-skill-math-practice/main/icon.png"
23
+ }
@@ -0,0 +1 @@
1
+ {i} gange {n} er {product}
@@ -0,0 +1 @@
1
+ count to {number}
@@ -0,0 +1 @@
1
+ that's a lot of counting, let's keep it to {max} or under
@@ -0,0 +1 @@
1
+ {numbers}
@@ -0,0 +1 @@
1
+ I didn't catch that number
@@ -0,0 +1,9 @@
1
+ {
2
+ "_notes": [
3
+ "Keys are the spoken operation name, values are the key in OPERATIONS (__init__.py)."
4
+ ],
5
+ "addition": "add",
6
+ "subtraction": "subtract",
7
+ "multiplication": "multiply",
8
+ "division": "divide"
9
+ }
@@ -0,0 +1 @@
1
+ I don't have a quiz for {operation}, I know addition, subtraction, multiplication, and division
@@ -0,0 +1,2 @@
1
+ correct
2
+ that's right
@@ -0,0 +1 @@
1
+ you got {correct} out of {total} right
@@ -0,0 +1,3 @@
1
+ give me a math quiz
2
+ quiz me on math
3
+ give me a math problem
@@ -0,0 +1 @@
1
+ not quite, the answer is {answer}
@@ -0,0 +1 @@
1
+ I didn't hear an answer, let's move on
@@ -0,0 +1,2 @@
1
+ quiz me on {operation}
2
+ test me on {operation}
@@ -0,0 +1 @@
1
+ what is {a} plus {b}
@@ -0,0 +1 @@
1
+ what is {a} divided by {b}
@@ -0,0 +1 @@
1
+ what is {a} times {b}
@@ -0,0 +1 @@
1
+ what is {a} minus {b}
@@ -0,0 +1,2 @@
1
+ quiz me on the {number} times table
2
+ test me on the {number} times table
@@ -0,0 +1,2 @@
1
+ say the {number} times table
2
+ recite the {number} times table
@@ -0,0 +1,23 @@
1
+ {
2
+ "skill_id": "ovos-skill-math-practice.andlo",
3
+ "source": "https://github.com/andlo/ovos-skill-math-practice",
4
+ "package_name": "ovos-skill-math-practice",
5
+ "pip_spec": "ovos-skill-math-practice",
6
+ "license": "GPL-3.0-or-later",
7
+ "author": "andlo",
8
+ "name": "Math Practice",
9
+ "description": "Math practice for kids - counting, times table recitation, and interactive quizzes across addition, subtraction, multiplication, and division. Fully offline.",
10
+ "examples": [
11
+ "count to ten",
12
+ "say the 5 times table",
13
+ "quiz me on the 3 times table"
14
+ ],
15
+ "tags": [
16
+ "math",
17
+ "education",
18
+ "times tables",
19
+ "quiz",
20
+ "kids"
21
+ ],
22
+ "icon": "https://raw.githubusercontent.com/andlo/ovos-skill-math-practice/main/icon.png"
23
+ }
@@ -0,0 +1 @@
1
+ {i} times {n} is {product}
@@ -0,0 +1,5 @@
1
+ VERSION_MAJOR = 0
2
+ VERSION_MINOR = 0
3
+ VERSION_BUILD = 1
4
+ VERSION_ALPHA = 0
5
+ # END_VERSION_BLOCK
@@ -0,0 +1,104 @@
1
+ Metadata-Version: 2.4
2
+ Name: ovos-skill-math-practice
3
+ Version: 0.0.1
4
+ Summary: Math practice for OVOS - counting, times tables, and arithmetic quizzes across add/subtract/multiply/divide, fully offline
5
+ Home-page: https://github.com/andlo/ovos-skill-math-practice
6
+ Author: Andreas Lorensen
7
+ Author-email: andlo@outlook.dk
8
+ License: GPL-3.0-or-later
9
+ Project-URL: Source, https://github.com/andlo/ovos-skill-math-practice
10
+ Project-URL: Bug Tracker, https://github.com/andlo/ovos-skill-math-practice/issues
11
+ Keywords: ovos skill voice assistant math education times tables quiz kids
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: End Users/Desktop
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
16
+ Requires-Python: >=3.9
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Requires-Dist: ovos-number-parser
20
+ Dynamic: author
21
+ Dynamic: author-email
22
+ Dynamic: classifier
23
+ Dynamic: description
24
+ Dynamic: description-content-type
25
+ Dynamic: home-page
26
+ Dynamic: keywords
27
+ Dynamic: license
28
+ Dynamic: license-file
29
+ Dynamic: project-url
30
+ Dynamic: requires-dist
31
+ Dynamic: requires-python
32
+ Dynamic: summary
33
+
34
+ # <img src='icon.png' card_color='#DB4062' width='50' height='50' style='vertical-align:bottom'/> Math Practice
35
+
36
+ Math practice for kids (and anyone else) - counting, times table
37
+ recitation, and interactive arithmetic quizzes across all four basic
38
+ operations. Fully offline.
39
+
40
+ [![Tests](https://github.com/andlo/ovos-skill-math-practice/actions/workflows/test.yml/badge.svg)](https://github.com/andlo/ovos-skill-math-practice/actions/workflows/test.yml)
41
+ [![PyPI version](https://img.shields.io/pypi/v/ovos-skill-math-practice.svg)](https://pypi.org/project/ovos-skill-math-practice/)
42
+
43
+ ## Three separate modes, deliberately not one "do math" intent
44
+
45
+ 1. **Counting** - `"count to ten"` - pure recitation, no interaction.
46
+ 2. **Table recitation** - `"say the 5 times table"` - pure recitation
47
+ of 1×5 through 10×5, no interaction. Multiplication only -
48
+ addition/subtraction/division don't have a traditional "table"
49
+ concept the same way, so this stays scoped to what "times table"
50
+ actually means.
51
+ 3. **Quiz** - `"quiz me on the 3 times table"` / `"quiz me on
52
+ addition"` - genuinely interactive: asks 5 questions one at a
53
+ time, listens for a spoken answer, and reports a final score.
54
+ Covers all four basic operations, not just multiplication - this
55
+ was an explicit scope correction from the original "times tables"
56
+ framing during design.
57
+
58
+ ## Usage
59
+ ```
60
+ "count to ten"
61
+ "say the 5 times table"
62
+ "quiz me on the 3 times table"
63
+ "quiz me on addition"
64
+ "give me a math quiz"
65
+ "tæl til ti" (Danish)
66
+ "sig 5 tabellen" (Danish)
67
+ "quiz mig i 3 tabellen" (Danish)
68
+ ```
69
+
70
+ ## How the quiz works
71
+
72
+ Unlike [ovos-skill-metronome](https://github.com/andlo/ovos-skill-metronome)/
73
+ [ovos-skill-rhythm-box](https://github.com/andlo/ovos-skill-rhythm-box)/
74
+ [ovos-skill-white-noise](https://github.com/andlo/ovos-skill-white-noise),
75
+ quiz mode needs no background thread - it's a sequential, blocking
76
+ conversation using `OVOSSkill.get_response()`, which already handles
77
+ the listen-and-transcribe round-trip. Each of the 5 questions is
78
+ asked, answered (or timed out), and scored in turn; a timed-out or
79
+ unparseable answer counts as incorrect rather than crashing the quiz.
80
+
81
+ ## Problem generation defaults
82
+
83
+ - 5 questions per quiz round.
84
+ - Times tables: the traditional 1-12 range.
85
+ - Addition/subtraction: operands 1-20, subtraction always kept
86
+ non-negative (larger number first).
87
+ - Division: always divides evenly (built as divisor × quotient, both
88
+ 1-10) - no fractional answers, since spoken fraction-checking is a
89
+ much harder problem than this skill attempts to solve.
90
+
91
+ ## Install
92
+ ```bash
93
+ pip install ovos-skill-math-practice
94
+ ```
95
+
96
+ ## Development
97
+
98
+ See [DEVELOPMENT.md](DEVELOPMENT.md).
99
+
100
+ ## Category
101
+ **Education**
102
+
103
+ ## Tags
104
+ #math #education #times-tables #quiz #kids
@@ -0,0 +1,48 @@
1
+ ovos_skill_math_practice/__init__.py,sha256=zOI8mAfA7uhoyuPXhfKPTVAvNGaZ_w5rftS5XouNQd4,10483
2
+ ovos_skill_math_practice/version.py,sha256=M2FhGAK7lylNEcv-WWrBVqshZbkWwWxoIBOifaVfFYM,92
3
+ ovos_skill_math_practice/locale/da-dk/count_to.intent,sha256=lYVXNZ6L11rsuOsb-k6BwTwr2UUg3TgprF7g3V2mM0U,18
4
+ ovos_skill_math_practice/locale/da-dk/count_too_high.dialog,sha256=q2x0P2osG1-EHzepqvkid9PGrIGbmASEXTvXyvbNyJo,59
5
+ ovos_skill_math_practice/locale/da-dk/counting.dialog,sha256=gfLI4XGSLTQGplsJ_ojKeytdBccPWDMel6UdMtdfv5k,10
6
+ ovos_skill_math_practice/locale/da-dk/number_not_understood.dialog,sha256=hV9qlFmc9DF_REaiHtpEnTTT4yRyJkMmfzZsluaUE0M,25
7
+ ovos_skill_math_practice/locale/da-dk/operation_aliases.json,sha256=KX21WpB4ApXgKBjbzrI8CaKDGePfsV4boQCkaKfDdI8,343
8
+ ovos_skill_math_practice/locale/da-dk/operation_not_understood.dialog,sha256=q0-WAshgJcwR1Qlt4UDtp_nAsPdN1FmfQr79LZdzkh8,99
9
+ ovos_skill_math_practice/locale/da-dk/quiz_correct.dialog,sha256=Jg4vnhRmKhDXyYKLarojqRBUw5IE0JjNZ1PwAiZPHPU,23
10
+ ovos_skill_math_practice/locale/da-dk/quiz_finished.dialog,sha256=dklMETDlLv-NyXMvc7v27e8JTz7dl_IHhNvv-4ZgHG8,39
11
+ ovos_skill_math_practice/locale/da-dk/quiz_general.intent,sha256=LMqTvaO6dpaf3nodB-deNoGyHpTMIjuZLW47ssRkCX4,65
12
+ ovos_skill_math_practice/locale/da-dk/quiz_incorrect.dialog,sha256=D_4ru91Ct8jdAflbHaDwBMJQdr3p_ElvInckpRBVRow,30
13
+ ovos_skill_math_practice/locale/da-dk/quiz_no_answer.dialog,sha256=W9zOsqqWzXxs3yMcJ-lwFi7X-UAgNAS-6-YcPFIsUwA,43
14
+ ovos_skill_math_practice/locale/da-dk/quiz_operation.intent,sha256=pH6bUQhfgHLMyM6zxxaG6edTRj07wDE6dszlt4M-kxk,46
15
+ ovos_skill_math_practice/locale/da-dk/quiz_question_add.dialog,sha256=3w4JPwFfxCWXpyqMhM5lZVgc05JWNHYfGziHA5KtDUc,21
16
+ ovos_skill_math_practice/locale/da-dk/quiz_question_divide.dialog,sha256=KRfzbhHng3CLZVz2dVPh3XSS3xTeZCQ6h7hiVhZQYU4,30
17
+ ovos_skill_math_practice/locale/da-dk/quiz_question_multiply.dialog,sha256=A8sios0UeNwm7c7Hqczf0YuaRQigQd8H6H9udYYARng,22
18
+ ovos_skill_math_practice/locale/da-dk/quiz_question_subtract.dialog,sha256=8-n_M-6JrGJvmXX2w5Tw_3kHgevrEkXnVXTLjpJbAqA,22
19
+ ovos_skill_math_practice/locale/da-dk/quiz_table.intent,sha256=myeqogwCOLwwtLzvQFYgWrHYGVKCzhX3HKMqoLla20w,58
20
+ ovos_skill_math_practice/locale/da-dk/recite_table.intent,sha256=svOjHqNWpjdgEwbcPqWalwc28m4pUk30YrIFUceclrk,49
21
+ ovos_skill_math_practice/locale/da-dk/skill.json,sha256=QMH-6pYeRsGBG-xtqJ1FQoh9Fkq3mTBhwNGbkc1blec,712
22
+ ovos_skill_math_practice/locale/da-dk/table_row.dialog,sha256=Xj-vAbD2njlm4IOPDG7NcRNNVSYXYDNu-k5lRneD8v0,27
23
+ ovos_skill_math_practice/locale/en-us/count_to.intent,sha256=pqMZSTfUErRz60H0f7M0lCkhmglFDOgZ1aTwpZYG2yU,18
24
+ ovos_skill_math_practice/locale/en-us/count_too_high.dialog,sha256=JWfk0Uq3S_GxR9uUZFv5jy989OrtciGPHIOlaYtw-wg,58
25
+ ovos_skill_math_practice/locale/en-us/counting.dialog,sha256=gfLI4XGSLTQGplsJ_ojKeytdBccPWDMel6UdMtdfv5k,10
26
+ ovos_skill_math_practice/locale/en-us/number_not_understood.dialog,sha256=XakJMgZYy5uJPC_iTLAbSMGpzWIxVl_1hPPRUuoNv_g,27
27
+ ovos_skill_math_practice/locale/en-us/operation_aliases.json,sha256=x8pUdqdlb6IakeQzO_8eXimfs3dzy_-buQHot37Xp-Y,218
28
+ ovos_skill_math_practice/locale/en-us/operation_not_understood.dialog,sha256=dKhQriK27fhaGB0oFF9aaA5Vv5uW9-t0lJ9PnhHXcmc,96
29
+ ovos_skill_math_practice/locale/en-us/quiz_correct.dialog,sha256=DXo8P7llSivV0cC_KF43EFFOmuD_h85wedymaWFUwNs,21
30
+ ovos_skill_math_practice/locale/en-us/quiz_finished.dialog,sha256=07xE1N1wpJ8a2qMNecZjDPraNpu2m4a1YOqNae8gPaU,39
31
+ ovos_skill_math_practice/locale/en-us/quiz_general.intent,sha256=VT58otFMT0dZ4LwKvH8RMhloGeR_U7v9K6IDMV4cwvA,59
32
+ ovos_skill_math_practice/locale/en-us/quiz_incorrect.dialog,sha256=nirA2mWALuejTNEtZ-MS-e1VA3IYdnYsnkVzzLbPdDQ,34
33
+ ovos_skill_math_practice/locale/en-us/quiz_no_answer.dialog,sha256=zsQHQBkJ1fyI1wJ_F2WTDCaX2pRndWmPF714oec_5dU,39
34
+ ovos_skill_math_practice/locale/en-us/quiz_operation.intent,sha256=dDAfx2tqZUsCmlwD7OCMY-yLOqMOMZIrLQJa2U9Tkj0,46
35
+ ovos_skill_math_practice/locale/en-us/quiz_question_add.dialog,sha256=u9f2mn858sVdm4qsFx3sMviOd0hkp6_NSa9i1ONX4f8,21
36
+ ovos_skill_math_practice/locale/en-us/quiz_question_divide.dialog,sha256=PKwuAGo_Z_2zEqrBR5QK7a302tth_BIADtHfP7L354s,27
37
+ ovos_skill_math_practice/locale/en-us/quiz_question_multiply.dialog,sha256=-eZWX1Y60SQKTGQX2TovNrBgGmHzOb0mzy_7mcm8MBg,22
38
+ ovos_skill_math_practice/locale/en-us/quiz_question_subtract.dialog,sha256=7X9lpuiUU3Iu7buTRVZjFlmxnyKG9OJyX1PyMo8nGts,22
39
+ ovos_skill_math_practice/locale/en-us/quiz_table.intent,sha256=_Lsgq88JK7SinsoJx57vkoyMmXC9yXh5goMBH48Vjqo,72
40
+ ovos_skill_math_practice/locale/en-us/recite_table.intent,sha256=LA6NkN5Dva18jnkycfW3Vp46FesoocaNMLeySA8YxLQ,61
41
+ ovos_skill_math_practice/locale/en-us/skill.json,sha256=kkewsKJ3OsiKIPy4G8SzgSMQdrgAwMfMhtLCNX97Kqs,749
42
+ ovos_skill_math_practice/locale/en-us/table_row.dialog,sha256=d-sEoj-mLIoIY0-NzYNZSUtyP5iPdJZ39yNS2MwMlwQ,27
43
+ ovos_skill_math_practice-0.0.1.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
44
+ ovos_skill_math_practice-0.0.1.dist-info/METADATA,sha256=IyXodZHmYgl9DXfDo_jydoeMIgEqU2Dlx-5xsmxnt9g,3969
45
+ ovos_skill_math_practice-0.0.1.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
46
+ ovos_skill_math_practice-0.0.1.dist-info/entry_points.txt,sha256=3gEKvQCddojMhK91ibbJOwSdBAv2EyKHyjHEwYAuNOA,91
47
+ ovos_skill_math_practice-0.0.1.dist-info/top_level.txt,sha256=1wRuCqOnY3r4yst3hHZr3nxd5SB5na_0HmezfMbQ2wQ,25
48
+ ovos_skill_math_practice-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [ovos.plugin.skill]
2
+ ovos-skill-math-practice.andlo = ovos_skill_math_practice:MathPractice