| import re |
| from .explainer_types import ExplainerResult, ExplainerScaffold |
|
|
|
|
| _PERCENT_PATTERNS = [ |
| r"\bpercent\b", |
| r"\bpercentage\b", |
| r"\bwhat percent\b", |
| r"\bpercent of\b", |
| r"\bpercent greater than\b", |
| r"\bpercent less than\b", |
| r"\bincrease(?:d)? by \d+ ?%\b", |
| r"\bdecrease(?:d)? by \d+ ?%\b", |
| r"\bdiscount\b", |
| r"\bmarkup\b", |
| r"\binterest\b", |
| r"\btax\b", |
| r"\btip\b", |
| r"\bprofit margin\b", |
| r"\bcommission\b", |
| r"\bpart of\b", |
| r"\bof a number\b", |
| ] |
|
|
|
|
| def _looks_like_percent_question(text: str) -> bool: |
| low = (text or "").lower() |
| if "%" in low: |
| return True |
| return any(re.search(p, low) for p in _PERCENT_PATTERNS) |
|
|
|
|
| def _infer_percent_subtype(text: str) -> str: |
| low = (text or "").lower() |
|
|
| if any(k in low for k in ["increase", "decrease", "increased", "decreased", "percent change", "changed by"]): |
| return "percent_change" |
| if any(k in low for k in ["discount", "sale", "off", "markdown"]): |
| return "discount" |
| if any(k in low for k in ["interest", "simple interest", "compound interest"]): |
| return "interest" |
| if any(k in low for k in ["tax", "vat", "tip", "gratuity"]): |
| return "tax_tip" |
| if any(k in low for k in ["what percent", "is what percent of", "as a percent of"]): |
| return "percent_of_percent" |
| if any(k in low for k in ["percent of", "of a number", "% of"]): |
| return "basic_percent" |
| return "generic_percent" |
|
|
|
|
| def explain_percent_question(text: str): |
| if not _looks_like_percent_question(text): |
| return None |
|
|
| subtype = _infer_percent_subtype(text) |
|
|
| result = ExplainerResult( |
| understood=True, |
| topic="percent", |
| 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." |
| ) |
|
|
| scaffold = ExplainerScaffold( |
| concept="Percent problems are built around a base amount, a compared amount, and a rate per hundred.", |
| 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.", |
| target="Translate the wording into the correct percent relationship before doing any arithmetic.", |
| answer_hidden=True, |
| ) |
|
|
| teaching_points = [ |
| "Percent means per hundred, so it should usually be converted to a decimal or fraction before setting up the relationship.", |
| "Most percent questions reduce to one of a small number of templates, so identifying the template matters more than rushing into calculation.", |
| "The biggest source of error is choosing the wrong base quantity." |
| ] |
|
|
| if subtype == "basic_percent": |
| scaffold.setup_actions = [ |
| "Label the known quantities as part, whole, and percent.", |
| "Convert the percent to a decimal or fraction.", |
| "Match the statement to the relationship: part = percent × whole." |
| ] |
| scaffold.intermediate_steps = [ |
| "If the whole is unknown, assign it a variable.", |
| "Substitute the known values into the relationship.", |
| "Solve only after the structure is correct." |
| ] |
| scaffold.first_move = "Start by identifying the whole and the part." |
| scaffold.next_hint = "Once those roles are clear, write one equation connecting part, percent, and whole." |
| scaffold.variables_to_define = [ |
| "Let x represent the unknown quantity if either the part or whole is missing." |
| ] |
| scaffold.equations_to_form = [ |
| "part = percent × whole" |
| ] |
| scaffold.common_traps = [ |
| "Using the wrong quantity as the whole.", |
| "Using 40 instead of 0.40.", |
| "Treating the wording as a percent change problem when it is only a basic percent-of problem." |
| ] |
|
|
| elif subtype == "percent_of_percent": |
| scaffold.setup_actions = [ |
| "Identify the comparison being made.", |
| "Place the compared quantity over the base quantity.", |
| "Convert the resulting fraction to a percent." |
| ] |
| scaffold.intermediate_steps = [ |
| "Make sure the denominator is the reference/base amount.", |
| "Simplify the fraction if helpful.", |
| "Only convert to percent at the end." |
| ] |
| scaffold.first_move = "Ask: percent of what?" |
| scaffold.next_hint = "Use the base quantity as the denominator in the fraction before converting." |
| scaffold.equations_to_form = [ |
| "percent = (part / whole) × 100" |
| ] |
| scaffold.common_traps = [ |
| "Reversing numerator and denominator.", |
| "Using the larger number automatically as the denominator instead of the stated base.", |
| "Forgetting to multiply by 100 at the end if the question asks for a percent." |
| ] |
|
|
| elif subtype == "percent_change": |
| scaffold.setup_actions = [ |
| "Identify the original value and the new value.", |
| "Find the amount of change first.", |
| "Use the original value as the base in the percent-change formula." |
| ] |
| scaffold.intermediate_steps = [ |
| "Compute change = new − original.", |
| "Place that change over the original value.", |
| "Convert the result to a percent." |
| ] |
| scaffold.first_move = "Find the amount of increase or decrease before thinking about the percent." |
| scaffold.next_hint = "After that, divide by the original amount, not the new amount." |
| scaffold.equations_to_form = [ |
| "percent change = (new − original) / original × 100" |
| ] |
| scaffold.common_traps = [ |
| "Using the new amount as the denominator.", |
| "Skipping the change step and comparing the wrong two quantities.", |
| "Losing track of whether the result should be described as an increase or decrease." |
| ] |
|
|
| elif subtype == "discount": |
| scaffold.setup_actions = [ |
| "Identify the original price and the discount rate.", |
| "Find the discount amount from the original price.", |
| "Subtract the discount from the original price if the question asks for the sale price." |
| ] |
| scaffold.intermediate_steps = [ |
| "Treat the listed price as the base.", |
| "Compute the discount amount separately from the final price.", |
| "Check whether the question asks for discount amount or discounted price." |
| ] |
| scaffold.first_move = "Use the original price as the percent base." |
| scaffold.next_hint = "Find the discount amount first, then decide whether to stop or subtract." |
| scaffold.equations_to_form = [ |
| "discount = rate × original price", |
| "sale price = original price − discount" |
| ] |
| scaffold.common_traps = [ |
| "Applying the discount to the wrong quantity.", |
| "Giving the discount amount when the question asks for the final price.", |
| "Treating multiple discounts as one simple combined subtraction without checking the structure." |
| ] |
|
|
| elif subtype == "interest": |
| scaffold.setup_actions = [ |
| "Identify principal, rate, and time.", |
| "Determine whether the question is simple interest or compound interest.", |
| "Set up the matching formula structure." |
| ] |
| scaffold.intermediate_steps = [ |
| "For simple interest, keep the principal fixed.", |
| "For compound interest, recognize that the base changes from period to period.", |
| "Check whether the question asks for interest earned or final amount." |
| ] |
| scaffold.first_move = "Work out whether the interest is simple or compound." |
| scaffold.next_hint = "Then identify which quantity stays fixed and which quantity grows." |
| scaffold.common_traps = [ |
| "Using a simple-interest structure on a compound-interest question.", |
| "Confusing interest earned with total amount.", |
| "Ignoring the time unit matching between rate and period." |
| ] |
|
|
| elif subtype == "tax_tip": |
| scaffold.setup_actions = [ |
| "Identify the pre-tax or pre-tip amount.", |
| "Use that original amount as the base.", |
| "Compute the tax or tip amount before adding if a total is required." |
| ] |
| scaffold.intermediate_steps = [ |
| "Separate the added charge from the final amount.", |
| "Check whether multiple percentages are applied independently or sequentially.", |
| "Make sure the question wants the fee amount or the full total." |
| ] |
| scaffold.first_move = "Use the original listed amount as the base unless the wording clearly says otherwise." |
| scaffold.next_hint = "Find the added percent amount first, then combine only if the question asks for the total." |
| scaffold.equations_to_form = [ |
| "added amount = rate × base amount", |
| "total = base amount + added amount" |
| ] |
| scaffold.common_traps = [ |
| "Using the final total as the percent base.", |
| "Stopping at the tax or tip amount when the question asks for the total bill.", |
| "Blending separate percentages together without checking the wording." |
| ] |
|
|
| else: |
| scaffold.setup_actions = [ |
| "Identify the base quantity.", |
| "Identify the compared quantity or changed amount.", |
| "Match the wording to a standard percent relationship." |
| ] |
| scaffold.intermediate_steps = [ |
| "Convert the percent appropriately.", |
| "Build one clear relationship before solving.", |
| "Check whether the question asks for a value, a rate, or a final amount." |
| ] |
| scaffold.first_move = "Work out what the percent is being taken of." |
| scaffold.next_hint = "Once the base quantity is clear, write the relationship before calculating." |
| scaffold.common_traps = [ |
| "Choosing the wrong base.", |
| "Not converting the percent properly.", |
| "Using the right numbers in the wrong relationship." |
| ] |
|
|
| result.teaching_points = teaching_points |
| result.scaffold = scaffold |
| result.meta = { |
| "intent": "explain_question", |
| "bridge_ready": True, |
| "hint_style": "step_ready", |
| "subtype": subtype, |
| } |
| return result |