j-js commited on
Commit
2660a67
·
verified ·
1 Parent(s): b3ac5d7

Update explainers/explainer_percent.py

Browse files
Files changed (1) hide show
  1. explainers/explainer_percent.py +188 -151
explainers/explainer_percent.py CHANGED
@@ -2,246 +2,283 @@ import re
2
  from .explainer_types import ExplainerResult, ExplainerScaffold
3
 
4
 
5
- _ALGEBRA_PATTERNS = [
6
- r"=",
7
- r"\bsolve\b",
8
- r"\bequation\b",
9
- r"\bexpression\b",
10
- r"\bvalue of\b",
11
- r"\bwhat is x\b",
12
- r"\bwhat is y\b",
13
- r"\bvariable\b",
 
 
 
 
 
 
 
 
 
14
  ]
15
 
16
 
17
- def _looks_like_algebra_question(text: str) -> bool:
18
  low = (text or "").lower()
19
-
20
- if re.search(r"\b[xyzab]\b", low) and "=" in low:
21
- return True
22
- if any(re.search(p, low) for p in _ALGEBRA_PATTERNS):
23
  return True
24
- return False
25
 
26
 
27
- def _infer_algebra_subtype(text: str) -> str:
28
  low = (text or "").lower()
29
 
30
- if any(k in low for k in ["system", "simultaneous", "x and y", "two equations"]):
31
- return "system"
32
- if any(k in low for k in ["inequality", "<", ">", "at least", "at most", "no more than"]):
33
- return "inequality"
34
- if any(k in low for k in ["quadratic", "squared", "^2", "x2", "root", "factor"]):
35
- return "quadratic"
36
- if any(k in low for k in ["expression", "value of 2x", "value of x +", "in terms of"]):
37
- return "expression_evaluation"
38
- if "=" in low:
39
- return "linear_equation"
40
- return "generic_algebra"
41
-
42
-
43
- def explain_algebra_question(text: str):
44
- if not _looks_like_algebra_question(text):
 
 
45
  return None
46
 
47
- subtype = _infer_algebra_subtype(text)
48
- low = (text or "").lower()
49
 
50
  result = ExplainerResult(
51
  understood=True,
52
- topic="algebra",
53
- summary="This is an algebra problem. The main goal is to translate the wording into a clean symbolic relationship and isolate the requested quantity step by step.",
54
- asks_for="the value of the variable or the requested expression built from it",
55
- plain_english="Algebra questions usually become easier once you write one clean equation and then reverse the operations in order.",
56
  )
57
 
58
  scaffold = ExplainerScaffold(
59
- concept="Algebra represents unknown quantities symbolically, then uses valid transformations to isolate or compare them.",
60
- ask="Identify the unknown, identify the governing relationship, and check whether the question wants the variable itself or an expression built from it.",
61
- target="Set up the simplest correct equation or relation before manipulating it.",
62
  answer_hidden=True,
63
  solution_path_type=subtype,
64
  )
65
 
 
 
 
 
 
66
  result.givens = [
67
- "A symbolic relationship or equation is implied by the wording.",
68
  ]
69
- if "=" in low:
70
- result.givens.append("An equals sign or equation structure is present.")
71
-
72
  result.relationships = [
73
- "Both sides of an equation must stay balanced.",
74
- "Each algebra step should simplify or isolate the target quantity.",
75
  ]
76
  result.needed_concepts = [
77
- "equation balancing",
78
- "inverse operations",
79
- "checking what the question actually asks for",
80
  ]
81
  result.trap_notes = [
82
- "Moving terms across the equals sign incorrectly.",
83
- "Trying to isolate the variable before simplifying.",
84
- "Finding x and forgetting the question asks for something like 2x or x + 3.",
85
- ]
86
- result.strategy_hint = "Rewrite the relationship as one clean equation before doing any manipulation."
87
-
88
- result.teaching_points = [
89
- "Most algebra errors happen before the solving starts: either the variable is misdefined, or the equation is set up incorrectly.",
90
- "A clean equation makes the solving steps much easier.",
91
- "You should always check whether the question asks for x itself or for something derived from x.",
92
  ]
 
93
 
94
- if subtype == "linear_equation":
95
  scaffold.setup_actions = [
96
- "Identify the unknown and write the equation cleanly.",
97
- "Simplify each side if needed.",
98
- "Undo operations in a logical order to isolate the variable.",
99
  ]
100
  scaffold.intermediate_steps = [
101
- "Combine like terms first when possible.",
102
- "Move variable terms and constant terms carefully.",
103
- "Check whether the final result should be the variable or a substituted expression.",
104
  ]
105
- scaffold.first_move = "Rewrite the relationship as one clean equation if it is not already in that form."
106
- scaffold.next_hint = "Simplify both sides before isolating the variable."
107
  scaffold.variables_to_define = [
108
- "Let the unknown quantity be x if the question has not already named it.",
 
 
 
109
  ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
  scaffold.equations_to_form = [
111
- "Build one equation from the stated relationship.",
112
  ]
113
  scaffold.key_operations = [
114
- "Simplify",
115
- "undo addition/subtraction",
116
- "undo multiplication/division",
117
  ]
118
  scaffold.hint_ladder = [
119
- "What operation is attached to the variable?",
120
- "What inverse operation would undo it?",
121
- "Apply that same operation to both sides.",
122
  ]
123
 
124
- elif subtype == "system":
125
  scaffold.setup_actions = [
126
- "Identify the separate equations and unknowns.",
127
- "Decide whether substitution or elimination is the cleaner method.",
128
- "Reduce the system to one variable before solving completely.",
129
  ]
130
  scaffold.intermediate_steps = [
131
- "Make one variable easy to substitute, or align coefficients for elimination.",
132
- "After finding one variable, substitute back carefully.",
133
- "Check whether the question asks for one variable, both variables, or a combination of them.",
134
  ]
135
- scaffold.first_move = "Choose one variable to eliminate or substitute."
136
- scaffold.next_hint = "Turn the system into a single-variable equation before solving."
137
  scaffold.equations_to_form = [
138
- "Use the two given equations together to reduce to one unknown.",
139
  ]
140
  scaffold.key_operations = [
141
- "substitute",
142
- "eliminate",
143
- "back-substitute",
144
  ]
145
  scaffold.hint_ladder = [
146
- "Which variable looks easier to isolate?",
147
- "Can you align coefficients for elimination?",
148
- "After finding one variable, plug it back into the other equation.",
149
  ]
150
 
151
- elif subtype == "inequality":
152
  scaffold.setup_actions = [
153
- "Translate the condition into an inequality.",
154
- "Manipulate it like an equation, but track the inequality direction carefully.",
155
- "Reverse the sign only if multiplying or dividing by a negative number.",
156
  ]
157
  scaffold.intermediate_steps = [
158
- "Simplify both sides first if possible.",
159
- "Isolate the variable systematically.",
160
- "Interpret the final solution set in the form the question wants.",
 
 
 
 
 
 
161
  ]
162
- scaffold.first_move = "Set up the inequality carefully from the wording."
163
- scaffold.next_hint = "Solve it step by step, watching for any operation that would reverse the sign."
164
  scaffold.key_operations = [
165
- "translate wording to an inequality",
166
- "simplify",
167
- "watch for sign reversal with negatives",
168
  ]
169
  scaffold.hint_ladder = [
170
- "What phrase tells you the direction of the inequality?",
171
- "Can you simplify both sides first?",
172
- "Did you divide or multiply by a negative at any point?",
173
  ]
174
 
175
- elif subtype == "quadratic":
176
  scaffold.setup_actions = [
177
- "Rewrite the equation so one side is zero if needed.",
178
- "Look for factoring, structure, or another simplifying method.",
179
- "Treat each factor or case carefully once the equation is structured properly.",
180
  ]
181
  scaffold.intermediate_steps = [
182
- "Factor if the form allows it.",
183
- "Otherwise identify another clean solving route.",
184
- "Check whether all resulting values are allowed in the original context.",
185
  ]
186
- scaffold.first_move = "Put the expression into a standard structured form before solving."
187
- scaffold.next_hint = "Then look for a factorable pattern or another clean route."
188
  scaffold.key_operations = [
189
- "standardize the expression",
190
- "factor or use structure",
191
- "check all roots back in context",
192
  ]
193
  scaffold.hint_ladder = [
194
- "Can you move everything to one side first?",
195
- "Does the expression factor neatly?",
196
- "Do all candidate solutions actually fit the original question?",
197
  ]
198
 
199
- elif subtype == "expression_evaluation":
200
  scaffold.setup_actions = [
201
- "Find the variable or relationship first.",
202
- "Only then substitute into the requested expression.",
203
- "Simplify the final expression carefully.",
204
  ]
205
  scaffold.intermediate_steps = [
206
- "Do not stop when you find the variable unless that is exactly what the question asks.",
207
- "Preserve parentheses during substitution.",
208
- "Check whether there is a shortcut using the given relationship directly.",
 
 
 
 
 
 
209
  ]
210
- scaffold.first_move = "Work out whether you need to solve for the variable first or can rewrite the target expression directly."
211
- scaffold.next_hint = "Once the relationship is clear, substitute only into the exact expression the question asks for."
212
  scaffold.key_operations = [
213
- "solve or rewrite the relationship",
214
- "substitute carefully",
215
- "simplify the requested expression",
216
  ]
217
  scaffold.hint_ladder = [
218
- "What expression does the question actually want?",
219
- "Do you already have enough information to rewrite that expression?",
220
- "Only substitute after the target expression is clear.",
221
  ]
222
 
223
  else:
224
  scaffold.setup_actions = [
225
- "Define the unknown clearly.",
226
- "Translate the wording into a symbolic relationship.",
227
- "Manipulate the relationship only after the setup is clean.",
228
  ]
229
  scaffold.intermediate_steps = [
230
- "Simplify before isolating.",
231
- "Keep track of what the question actually asks for.",
232
- "Check the final quantity against the prompt.",
233
  ]
234
- scaffold.first_move = "Start by translating the words into one clean symbolic statement."
235
- scaffold.next_hint = "Then simplify the structure before solving."
236
  scaffold.key_operations = [
237
- "translate",
238
- "simplify",
239
- "isolate",
240
  ]
241
  scaffold.hint_ladder = [
242
- "What is the unknown?",
243
- "What relationship connects the quantities?",
244
- "What is the cleanest first algebra step?",
245
  ]
246
 
247
  result.scaffold = scaffold
 
2
  from .explainer_types import ExplainerResult, ExplainerScaffold
3
 
4
 
5
+ _PERCENT_PATTERNS = [
6
+ r"\bpercent\b",
7
+ r"\bpercentage\b",
8
+ r"\bwhat percent\b",
9
+ r"\bpercent of\b",
10
+ r"\bpercent greater than\b",
11
+ r"\bpercent less than\b",
12
+ r"\bincrease(?:d)? by \d+ ?%\b",
13
+ r"\bdecrease(?:d)? by \d+ ?%\b",
14
+ r"\bdiscount\b",
15
+ r"\bmarkup\b",
16
+ r"\binterest\b",
17
+ r"\btax\b",
18
+ r"\btip\b",
19
+ r"\bprofit margin\b",
20
+ r"\bcommission\b",
21
+ r"\bpart of\b",
22
+ r"\bof a number\b",
23
  ]
24
 
25
 
26
+ def _looks_like_percent_question(text: str) -> bool:
27
  low = (text or "").lower()
28
+ if "%" in low:
 
 
 
29
  return True
30
+ return any(re.search(p, low) for p in _PERCENT_PATTERNS)
31
 
32
 
33
+ def _infer_percent_subtype(text: str) -> str:
34
  low = (text or "").lower()
35
 
36
+ if any(k in low for k in ["increase", "decrease", "increased", "decreased", "percent change", "changed by"]):
37
+ return "percent_change"
38
+ if any(k in low for k in ["discount", "sale", "off", "markdown"]):
39
+ return "discount"
40
+ if any(k in low for k in ["interest", "simple interest", "compound interest"]):
41
+ return "interest"
42
+ if any(k in low for k in ["tax", "vat", "tip", "gratuity"]):
43
+ return "tax_tip"
44
+ if any(k in low for k in ["what percent", "is what percent of", "as a percent of"]):
45
+ return "percent_of_percent"
46
+ if any(k in low for k in ["percent of", "of a number", "% of"]):
47
+ return "basic_percent"
48
+ return "generic_percent"
49
+
50
+
51
+ def explain_percent_question(text: str):
52
+ if not _looks_like_percent_question(text):
53
  return None
54
 
55
+ subtype = _infer_percent_subtype(text)
 
56
 
57
  result = ExplainerResult(
58
  understood=True,
59
+ topic="percent",
60
+ summary="This is a percent problem. The key is to identify which quantity is the base, which is the compared amount, and what role the percent is playing.",
61
+ asks_for="the missing value, the percent itself, or the final changed total",
62
+ plain_english="Percent means per hundred, so the safest route is to identify the base first and then match the wording to the correct percent template.",
63
  )
64
 
65
  scaffold = ExplainerScaffold(
66
+ concept="Percent problems are built around a base amount, a compared amount, and a rate per hundred.",
67
+ ask="Decide which quantity is the whole/base, which quantity is the part or changed amount, and whether the question is asking for a percent, a value, or a new total.",
68
+ target="Translate the wording into the correct percent relationship before doing any arithmetic.",
69
  answer_hidden=True,
70
  solution_path_type=subtype,
71
  )
72
 
73
+ result.teaching_points = [
74
+ "Percent means per hundred, so it should usually be converted to a decimal or fraction before setting up the relationship.",
75
+ "Most percent questions reduce to one of a small number of templates, so identifying the template matters more than rushing into calculation.",
76
+ "The biggest source of error is choosing the wrong base quantity.",
77
+ ]
78
  result.givens = [
79
+ "A percent, base, part, or changed amount is implied by the wording.",
80
  ]
 
 
 
81
  result.relationships = [
82
+ "Percent relationships depend on correctly matching part, whole, and rate.",
 
83
  ]
84
  result.needed_concepts = [
85
+ "base vs part",
86
+ "percent as a decimal or fraction",
87
+ "choosing the correct percent template",
88
  ]
89
  result.trap_notes = [
90
+ "Choosing the wrong base quantity.",
91
+ "Using 40 instead of 0.40.",
92
+ "Confusing a basic percent-of problem with a percent-change problem.",
 
 
 
 
 
 
 
93
  ]
94
+ result.strategy_hint = "Start by asking: percent of what?"
95
 
96
+ if subtype == "basic_percent":
97
  scaffold.setup_actions = [
98
+ "Label the known quantities as part, whole, and percent.",
99
+ "Convert the percent to a decimal or fraction.",
100
+ "Match the statement to the relationship: part = percent × whole.",
101
  ]
102
  scaffold.intermediate_steps = [
103
+ "If the whole is unknown, assign it a variable.",
104
+ "Substitute the known values into the relationship.",
105
+ "Solve only after the structure is correct.",
106
  ]
107
+ scaffold.first_move = "Start by identifying the whole and the part."
108
+ scaffold.next_hint = "Once those roles are clear, write one equation connecting part, percent, and whole."
109
  scaffold.variables_to_define = [
110
+ "Let x represent the unknown quantity if either the part or whole is missing.",
111
+ ]
112
+ scaffold.equations_to_form = [
113
+ "part = percent × whole",
114
  ]
115
+ scaffold.key_operations = [
116
+ "identify the base",
117
+ "convert percent to decimal",
118
+ "build one equation",
119
+ ]
120
+ scaffold.hint_ladder = [
121
+ "Which quantity is the whole?",
122
+ "What is the percent as a decimal?",
123
+ "Can you write part = percent × whole?",
124
+ ]
125
+
126
+ elif subtype == "percent_of_percent":
127
+ scaffold.setup_actions = [
128
+ "Identify the comparison being made.",
129
+ "Place the compared quantity over the base quantity.",
130
+ "Convert the resulting fraction to a percent.",
131
+ ]
132
+ scaffold.intermediate_steps = [
133
+ "Make sure the denominator is the reference/base amount.",
134
+ "Simplify the fraction if helpful.",
135
+ "Only convert to percent at the end.",
136
+ ]
137
+ scaffold.first_move = "Ask: percent of what?"
138
+ scaffold.next_hint = "Use the base quantity as the denominator in the fraction before converting."
139
  scaffold.equations_to_form = [
140
+ "percent = (part / whole) × 100",
141
  ]
142
  scaffold.key_operations = [
143
+ "form the fraction",
144
+ "use the base as denominator",
145
+ "convert to percent",
146
  ]
147
  scaffold.hint_ladder = [
148
+ "What is the reference amount?",
149
+ "Which number belongs in the denominator?",
150
+ "Multiply by 100 only after the fraction is correct.",
151
  ]
152
 
153
+ elif subtype == "percent_change":
154
  scaffold.setup_actions = [
155
+ "Identify the original value and the new value.",
156
+ "Find the amount of change first.",
157
+ "Use the original value as the base in the percent-change formula.",
158
  ]
159
  scaffold.intermediate_steps = [
160
+ "Compute change = new original.",
161
+ "Place that change over the original value.",
162
+ "Convert the result to a percent.",
163
  ]
164
+ scaffold.first_move = "Find the amount of increase or decrease before thinking about the percent."
165
+ scaffold.next_hint = "After that, divide by the original amount, not the new amount."
166
  scaffold.equations_to_form = [
167
+ "percent change = (new original) / original × 100",
168
  ]
169
  scaffold.key_operations = [
170
+ "find the change",
171
+ "divide by the original",
172
+ "convert to percent",
173
  ]
174
  scaffold.hint_ladder = [
175
+ "Which value is original?",
176
+ "What is the amount of change?",
177
+ "Which quantity belongs in the denominator?",
178
  ]
179
 
180
+ elif subtype == "discount":
181
  scaffold.setup_actions = [
182
+ "Identify the original price and the discount rate.",
183
+ "Find the discount amount from the original price.",
184
+ "Subtract the discount from the original price if the question asks for the sale price.",
185
  ]
186
  scaffold.intermediate_steps = [
187
+ "Treat the listed price as the base.",
188
+ "Compute the discount amount separately from the final price.",
189
+ "Check whether the question asks for discount amount or discounted price.",
190
+ ]
191
+ scaffold.first_move = "Use the original price as the percent base."
192
+ scaffold.next_hint = "Find the discount amount first, then decide whether to stop or subtract."
193
+ scaffold.equations_to_form = [
194
+ "discount = rate × original price",
195
+ "sale price = original price − discount",
196
  ]
 
 
197
  scaffold.key_operations = [
198
+ "identify the original price",
199
+ "find discount amount",
200
+ "subtract if needed",
201
  ]
202
  scaffold.hint_ladder = [
203
+ "What is the original price?",
204
+ "What percent of that price is the discount?",
205
+ "Does the question want the discount or the final price?",
206
  ]
207
 
208
+ elif subtype == "interest":
209
  scaffold.setup_actions = [
210
+ "Identify principal, rate, and time.",
211
+ "Determine whether the question is simple interest or compound interest.",
212
+ "Set up the matching formula structure.",
213
  ]
214
  scaffold.intermediate_steps = [
215
+ "For simple interest, keep the principal fixed.",
216
+ "For compound interest, recognize that the base changes from period to period.",
217
+ "Check whether the question asks for interest earned or final amount.",
218
  ]
219
+ scaffold.first_move = "Work out whether the interest is simple or compound."
220
+ scaffold.next_hint = "Then identify which quantity stays fixed and which quantity grows."
221
  scaffold.key_operations = [
222
+ "identify principal, rate, and time",
223
+ "match simple vs compound",
224
+ "check whether the target is interest or total amount",
225
  ]
226
  scaffold.hint_ladder = [
227
+ "Is the base changing each period?",
228
+ "Which quantity is the principal?",
229
+ "Does the question want interest earned or the total balance?",
230
  ]
231
 
232
+ elif subtype == "tax_tip":
233
  scaffold.setup_actions = [
234
+ "Identify the pre-tax or pre-tip amount.",
235
+ "Use that original amount as the base.",
236
+ "Compute the tax or tip amount before adding if a total is required.",
237
  ]
238
  scaffold.intermediate_steps = [
239
+ "Separate the added charge from the final amount.",
240
+ "Check whether multiple percentages are applied independently or sequentially.",
241
+ "Make sure the question wants the fee amount or the full total.",
242
+ ]
243
+ scaffold.first_move = "Use the original listed amount as the base unless the wording clearly says otherwise."
244
+ scaffold.next_hint = "Find the added percent amount first, then combine only if the question asks for the total."
245
+ scaffold.equations_to_form = [
246
+ "added amount = rate × base amount",
247
+ "total = base amount + added amount",
248
  ]
 
 
249
  scaffold.key_operations = [
250
+ "identify the original amount",
251
+ "find the added charge",
252
+ "add only if the question asks for the total",
253
  ]
254
  scaffold.hint_ladder = [
255
+ "What is the amount before tax or tip?",
256
+ "How much is the added percent of that base?",
257
+ "Does the question stop at the fee or ask for the total bill?",
258
  ]
259
 
260
  else:
261
  scaffold.setup_actions = [
262
+ "Identify the base quantity.",
263
+ "Identify the compared quantity or changed amount.",
264
+ "Match the wording to a standard percent relationship.",
265
  ]
266
  scaffold.intermediate_steps = [
267
+ "Convert the percent appropriately.",
268
+ "Build one clear relationship before solving.",
269
+ "Check whether the question asks for a value, a rate, or a final amount.",
270
  ]
271
+ scaffold.first_move = "Work out what the percent is being taken of."
272
+ scaffold.next_hint = "Once the base quantity is clear, write the relationship before calculating."
273
  scaffold.key_operations = [
274
+ "identify the base",
275
+ "convert the percent",
276
+ "match the correct template",
277
  ]
278
  scaffold.hint_ladder = [
279
+ "What quantity is the base?",
280
+ "What role does the percent play here?",
281
+ "Which standard percent relationship matches this wording?",
282
  ]
283
 
284
  result.scaffold = scaffold