j-js commited on
Commit
9e67f64
·
verified ·
1 Parent(s): a5d5985

Update explainers/explainer_ratio.py

Browse files
Files changed (1) hide show
  1. explainers/explainer_ratio.py +175 -45
explainers/explainer_ratio.py CHANGED
@@ -1,61 +1,191 @@
1
  import re
2
- from models import ExplainerResult
3
 
4
 
5
- def explain_ratio_question(text: str) -> ExplainerResult | None:
6
- t = text.lower()
 
 
 
 
 
 
 
 
 
7
 
8
- # STRONG detection
9
- has_ratio_word = "ratio" in t or "proportion" in t
10
- has_ratio_form = bool(re.search(r"\b\d+\s*:\s*\d+\b", t))
11
 
12
- if not has_ratio_word and not has_ratio_form:
13
- return None
14
-
15
- givens = []
16
- relationships = []
17
- constraints = []
18
- trap_notes = []
19
 
20
- ratio_matches = re.findall(r"\b\d+\s*:\s*\d+\b", t)
 
 
 
 
 
21
 
22
- if ratio_matches:
23
- givens.append(f"A ratio is given: {', '.join(ratio_matches[:3])}")
24
- else:
25
- givens.append("A proportional relationship is described")
26
 
27
- if "total" in t or "class" in t:
28
- givens.append("A total amount may be relevant")
29
 
30
- relationships.append("This is a fixed proportional relationship between quantities")
 
31
 
32
- if "fraction" in t or "what fraction" in t:
33
- relationships.append("You may need to convert a ratio into a fraction of the whole")
 
 
 
 
 
 
 
34
 
35
- if "boys to girls" in t or "men to women" in t:
36
- relationships.append("This compares two groups within a total")
37
 
38
- asks_for = "a missing part, total, or fraction from the ratio"
 
 
39
 
40
- trap_notes.extend([
41
- "Do not confuse part-to-part ratios with part-to-whole fractions",
42
- "Keep the ratio order consistent",
43
- "If a total is given, first find the total number of ratio parts",
44
- ])
45
 
46
- return ExplainerResult(
47
  understood=True,
48
  topic="ratio",
49
- asks_for=asks_for,
50
- givens=givens,
51
- constraints=constraints,
52
- relationships=relationships,
53
- needed_concepts=[
54
- "ratio structure",
55
- "proportional scaling",
56
- "part-to-whole conversion",
57
- ],
58
- trap_notes=trap_notes,
59
- strategy_hint="Work out how many total parts the ratio represents, then map that to the quantity you need.",
60
- plain_english="The question is describing a ratio between quantities and asking you to find a missing part or convert it into a fraction of the whole.",
61
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import re
2
+ from explainer_types import ExplainerResult, ExplainerScaffold
3
 
4
 
5
+ _RATIO_PATTERNS = [
6
+ r"\bratio\b",
7
+ r"\bproportion\b",
8
+ r"\bfor every\b",
9
+ r"\bto\b",
10
+ r":",
11
+ r"\bout of\b",
12
+ r"\brespectively\b",
13
+ r"\bpart to part\b",
14
+ r"\bpart to whole\b",
15
+ ]
16
 
 
 
 
17
 
18
+ def _looks_like_ratio_question(text: str) -> bool:
19
+ low = (text or "").lower()
 
 
 
 
 
20
 
21
+ if re.search(r"\b\d+\s*:\s*\d+\b", low):
22
+ return True
23
+ if "for every" in low:
24
+ return True
25
+ if "ratio" in low or "proportion" in low:
26
+ return True
27
 
28
+ return False
 
 
 
29
 
 
 
30
 
31
+ def _infer_ratio_subtype(text: str) -> str:
32
+ low = (text or "").lower()
33
 
34
+ if any(k in low for k in ["for every", "ratio of", "respectively"]):
35
+ return "ratio_parts"
36
+ if any(k in low for k in ["total", "sum", "combined"]):
37
+ return "part_to_total"
38
+ if any(k in low for k in ["proportion", "directly proportional", "inversely proportional"]):
39
+ return "proportion"
40
+ if any(k in low for k in ["mixture", "men and women", "boys and girls", "red and blue", "apples and oranges"]):
41
+ return "group_ratio"
42
+ return "generic_ratio"
43
 
 
 
44
 
45
+ def explain_ratio(text: str):
46
+ if not _looks_like_ratio_question(text):
47
+ return None
48
 
49
+ subtype = _infer_ratio_subtype(text)
 
 
 
 
50
 
51
+ result = ExplainerResult(
52
  understood=True,
53
  topic="ratio",
54
+ summary="This is a ratio problem. The main job is to preserve the order of the ratio and convert ratio parts into actual quantities using one shared multiplier."
55
+ )
56
+
57
+ scaffold = ExplainerScaffold(
58
+ concept="A ratio compares quantities by relative size, not by actual amount.",
59
+ ask="Decide what each side of the ratio refers to, keep the order exact, and determine whether the question wants one part, a total, a difference, or a scaled version.",
60
+ target="Translate the ratio into variable-based quantities that can be linked to the condition in the question.",
61
+ answer_hidden=True,
62
+ )
63
+
64
+ teaching_points = [
65
+ "A ratio does not give actual quantities until a common scale factor is applied.",
66
+ "Most ratio questions become simple algebra once each part is written in terms of the same multiplier.",
67
+ "The order matters. Reversing the ratio changes the meaning of the whole setup."
68
+ ]
69
+
70
+ if subtype == "ratio_parts":
71
+ scaffold.setup_actions = [
72
+ "Write each ratio part using a common multiplier such as ak and bk.",
73
+ "Keep the terms in the same order as the original ratio statement.",
74
+ "Use the condition in the question to connect those expressions to actual values."
75
+ ]
76
+ scaffold.intermediate_steps = [
77
+ "If one part is known, solve for the multiplier first.",
78
+ "If a total is given, add the ratio expressions.",
79
+ "If a difference is given, subtract the relevant ratio expressions."
80
+ ]
81
+ scaffold.first_move = "Rewrite each ratio term as a multiple of the same variable."
82
+ scaffold.next_hint = "Then connect those expressions to the value or condition given in the question."
83
+ scaffold.variables_to_define = [
84
+ "Let the common multiplier be k."
85
+ ]
86
+ scaffold.equations_to_form = [
87
+ "amounts = ratio parts × common multiplier"
88
+ ]
89
+ scaffold.common_traps = [
90
+ "Reversing the order of the ratio.",
91
+ "Treating the ratio numbers as final quantities instead of scaled parts.",
92
+ "Forgetting that different parts must all use the same multiplier."
93
+ ]
94
+
95
+ elif subtype == "part_to_total":
96
+ scaffold.setup_actions = [
97
+ "Represent each part with a shared multiplier.",
98
+ "Add the ratio parts to represent the total.",
99
+ "Match the part or total expression to the given condition."
100
+ ]
101
+ scaffold.intermediate_steps = [
102
+ "Translate the whole ratio into algebraic amounts first.",
103
+ "Use the sum of all parts for the total.",
104
+ "Check whether the question asks for one component or the overall total."
105
+ ]
106
+ scaffold.first_move = "Turn the ratio into variable-based amounts and add them to get the total structure."
107
+ scaffold.next_hint = "Use the given total to solve for the shared multiplier."
108
+ scaffold.variables_to_define = [
109
+ "Let the common multiplier be k."
110
+ ]
111
+ scaffold.equations_to_form = [
112
+ "total = sum of all ratio parts × k"
113
+ ]
114
+ scaffold.common_traps = [
115
+ "Using only one part instead of the full sum when a total is given.",
116
+ "Dropping one category from the total.",
117
+ "Solving for the multiplier and forgetting to return to the quantity actually asked for."
118
+ ]
119
+
120
+ elif subtype == "proportion":
121
+ scaffold.setup_actions = [
122
+ "Identify which two ratios or rates are being set equal.",
123
+ "Preserve matching positions carefully.",
124
+ "Use cross-multiplication only after the correspondence is correct."
125
+ ]
126
+ scaffold.intermediate_steps = [
127
+ "Line up like-with-like before building the proportion.",
128
+ "Check units or roles so the comparison makes sense.",
129
+ "Then simplify the resulting equation."
130
+ ]
131
+ scaffold.first_move = "Match the corresponding quantities in the two ratios."
132
+ scaffold.next_hint = "Once the matching is correct, form the equation between the two ratios."
133
+ scaffold.equations_to_form = [
134
+ "first ratio = second ratio"
135
+ ]
136
+ scaffold.common_traps = [
137
+ "Matching the wrong terms across the two ratios.",
138
+ "Cross-multiplying before the setup is correct.",
139
+ "Ignoring whether the problem is direct or inverse proportion."
140
+ ]
141
+
142
+ elif subtype == "group_ratio":
143
+ scaffold.setup_actions = [
144
+ "Assign each group its ratio-based expression.",
145
+ "Use the stated total, difference, or known subgroup size to create an equation.",
146
+ "Solve for the common multiplier before finding the requested quantity."
147
+ ]
148
+ scaffold.intermediate_steps = [
149
+ "Make sure each category is represented exactly once.",
150
+ "Check whether the condition is about the whole group or one subgroup.",
151
+ "Return to the requested category at the end."
152
+ ]
153
+ scaffold.first_move = "Represent each group using the same scaling variable."
154
+ scaffold.next_hint = "Then use the condition involving the total or one group to solve for that variable."
155
+ scaffold.variables_to_define = [
156
+ "Let the common multiplier be k."
157
+ ]
158
+ scaffold.common_traps = [
159
+ "Using separate multipliers for parts of the same ratio.",
160
+ "Answering with the multiplier instead of the requested group amount.",
161
+ "Losing the original ratio order when translating categories."
162
+ ]
163
+
164
+ else:
165
+ scaffold.setup_actions = [
166
+ "Identify what each term in the ratio represents.",
167
+ "Translate the ratio into algebraic quantities with a common scale factor.",
168
+ "Use the stated condition to solve for the scale factor."
169
+ ]
170
+ scaffold.intermediate_steps = [
171
+ "Use the sum if a total is involved.",
172
+ "Use subtraction if a difference is involved.",
173
+ "Check which final quantity the question wants."
174
+ ]
175
+ scaffold.first_move = "Start by assigning a shared multiplier to the ratio parts."
176
+ scaffold.next_hint = "Then use the given condition to turn the ratio setup into an equation."
177
+ scaffold.common_traps = [
178
+ "Reversing the order of the ratio.",
179
+ "Not using one shared multiplier.",
180
+ "Stopping at the multiplier instead of the requested quantity."
181
+ ]
182
+
183
+ result.teaching_points = teaching_points
184
+ result.scaffold = scaffold
185
+ result.meta = {
186
+ "intent": "explain_question",
187
+ "bridge_ready": True,
188
+ "hint_style": "step_ready",
189
+ "subtype": subtype,
190
+ }
191
+ return result