GameAI / explainers /explainer_ratio.py
j-js's picture
Update explainers/explainer_ratio.py
e978573 verified
import re
from .explainer_types import ExplainerResult, ExplainerScaffold
def _looks_like_ratio_question(text: str) -> bool:
low = (text or "").lower().strip()
if re.search(r"\b\d+\s*:\s*\d+\b", low):
return True
ratio_signals = [
"ratio",
"proportion",
"for every",
"respectively",
"boys to girls",
"girls to boys",
"men to women",
"women to men",
"red to blue",
"blue to red",
"part to whole",
"part-to-whole",
"out of",
]
return any(signal in low for signal in ratio_signals)
def _infer_ratio_subtype(text: str) -> str:
low = (text or "").lower()
if any(k in low for k in ["directly proportional", "inversely proportional", "proportion"]):
return "proportion"
if any(k in low for k in ["total", "sum", "combined", "altogether", "in all"]):
return "part_to_total"
if any(
k in low
for k in [
"boys and girls",
"girls and boys",
"men and women",
"women and men",
"red and blue",
"blue and red",
"apples and oranges",
"cats and dogs",
"students and teachers",
]
):
return "group_ratio"
if any(k in low for k in ["for every", "ratio of", "respectively"]) or re.search(r"\b\d+\s*:\s*\d+\b", low):
return "ratio_parts"
return "generic_ratio"
def explain_ratio_question(text: str):
if not _looks_like_ratio_question(text):
return None
subtype = _infer_ratio_subtype(text)
result = ExplainerResult(
understood=True,
topic="ratio",
summary="This is a ratio problem. The main job is to preserve the order of the comparison and translate ratio parts into usable quantities with one shared scale factor.",
asks_for="the missing part, the total, or the quantity linked to the ratio",
plain_english="A ratio compares relative sizes, not actual amounts, so you usually need one shared multiplier before you can connect it to real quantities.",
)
scaffold = ExplainerScaffold(
concept="A ratio compares quantities by relative size, not by actual amount.",
ask="Identify what each side of the ratio represents, keep the order exact, and decide whether the question wants a part, a total, a difference, or a scaled amount.",
target="Translate the ratio into variable-based quantities that connect to the condition in the question.",
answer_hidden=True,
solution_path_type=subtype,
)
result.teaching_points = [
"A ratio does not give actual quantities until a common scale factor is applied.",
"Most ratio questions become straightforward once each part is written using the same multiplier.",
"The order matters. Reversing the ratio changes the meaning of the setup.",
]
result.givens = [
"A comparison between two or more quantities.",
"An order that must be preserved exactly.",
]
result.relationships = [
"actual quantity = ratio part × common multiplier",
]
result.needed_concepts = [
"ratio order",
"shared multiplier",
"part-to-part versus part-to-whole structure",
]
result.trap_notes = [
"Reversing the order of the ratio.",
"Treating ratio numbers as final quantities instead of scaled parts.",
"Finding the multiplier but not returning to the quantity actually asked for.",
]
result.strategy_hint = "Start by assigning the same multiplier to every part of the ratio."
if subtype == "ratio_parts":
scaffold.setup_actions = [
"Write each ratio part using a common multiplier, such as ak and bk.",
"Keep the parts in the same order as the original ratio statement.",
"Use the condition in the question to connect those expressions to actual values.",
]
scaffold.intermediate_steps = [
"If one part is known, use it to find the common multiplier.",
"If a total is given, add the ratio expressions.",
"If a difference is given, subtract the relevant ratio expressions.",
]
scaffold.first_move = "Rewrite each ratio term as a multiple of the same variable."
scaffold.next_hint = "Then connect those expressions to the value or condition given in the question."
scaffold.variables_to_define = [
"Let the common multiplier be k.",
]
scaffold.equations_to_form = [
"amount = ratio part × k",
]
scaffold.common_traps = [
"Reversing the order of the ratio.",
"Treating the ratio parts as actual amounts immediately.",
"Using different multipliers for parts of the same ratio.",
]
scaffold.key_operations = [
"preserve order",
"introduce a shared multiplier",
"connect the ratio to the given condition",
]
scaffold.hint_ladder = [
"What does each side of the ratio represent?",
"Can you write both parts using the same multiplier?",
"What condition links those expressions to real values?",
]
elif subtype == "part_to_total":
scaffold.setup_actions = [
"Represent each part using the same multiplier.",
"Add the ratio parts to build the total.",
"Match the part or total expression to the stated condition.",
]
scaffold.intermediate_steps = [
"Translate the whole ratio into algebraic amounts first.",
"Use the sum of all parts when the question gives a total.",
"Check whether the final answer should be one part or the whole amount.",
]
scaffold.first_move = "Turn the ratio into variable-based parts and combine them to form the total."
scaffold.next_hint = "Use the given total to solve for the shared multiplier."
scaffold.variables_to_define = [
"Let the common multiplier be k.",
]
scaffold.equations_to_form = [
"total = (sum of ratio parts) × k",
]
scaffold.common_traps = [
"Using only one part when the condition refers to the whole total.",
"Leaving out one category when building the total.",
"Stopping after finding the multiplier instead of the requested amount.",
]
scaffold.key_operations = [
"write each part in terms of k",
"sum the parts",
"solve from the total condition",
]
scaffold.hint_ladder = [
"How many parts are there altogether?",
"Can you express the total in terms of k?",
"After finding k, which quantity does the question actually want?",
]
elif subtype == "proportion":
scaffold.setup_actions = [
"Identify which two ratios or rates are being set equal.",
"Match corresponding positions carefully.",
"Only form the equation once the correspondence is correct.",
]
scaffold.intermediate_steps = [
"Line up like-with-like before writing the proportion.",
"Check whether the quantities and units match properly.",
"Then simplify the resulting equation step by step.",
]
scaffold.first_move = "Match the corresponding quantities in the two ratios."
scaffold.next_hint = "Once the matching is correct, write the equality between the two ratios."
scaffold.equations_to_form = [
"first ratio = second ratio",
]
scaffold.common_traps = [
"Matching the wrong terms across the two ratios.",
"Using cross-multiplication before the setup is correct.",
"Ignoring whether the wording implies direct or inverse proportion.",
]
scaffold.key_operations = [
"match corresponding quantities",
"form the proportion",
"solve the resulting equation",
]
scaffold.hint_ladder = [
"Which terms correspond to each other?",
"Can you write the two ratios in the same order?",
"Only then should you solve the equation that results.",
]
result.relationships = [
"equivalent ratios represent the same multiplicative relationship",
"matching positions must stay consistent across the proportion",
]
result.needed_concepts = [
"equivalent ratios",
"corresponding terms",
"proportional structure",
]
elif subtype == "group_ratio":
scaffold.setup_actions = [
"Assign each group its ratio-based expression.",
"Use the stated total, difference, or known subgroup size to build an equation.",
"Solve for the common multiplier before finding the requested quantity.",
]
scaffold.intermediate_steps = [
"Make sure each category is represented exactly once.",
"Check whether the condition refers to one group or the whole set.",
"Return to the requested group after finding the multiplier.",
]
scaffold.first_move = "Represent each group using the same scaling variable."
scaffold.next_hint = "Then use the condition involving the total or one subgroup to solve for that variable."
scaffold.variables_to_define = [
"Let the common multiplier be k.",
]
scaffold.equations_to_form = [
"group amount = ratio part × k",
]
scaffold.common_traps = [
"Using separate multipliers for groups in the same ratio.",
"Answering with the multiplier instead of the group requested.",
"Losing track of the original ratio order when translating categories.",
]
scaffold.key_operations = [
"assign each group an expression",
"use the total or subgroup condition",
"return to the requested category",
]
scaffold.hint_ladder = [
"What does each group correspond to in the ratio?",
"Can you write each group in terms of k?",
"Which condition lets you solve for k?",
]
else:
scaffold.setup_actions = [
"Identify what each part of the ratio refers to.",
"Translate the ratio into algebraic quantities using one shared scale factor.",
"Use the stated condition to solve for that scale factor.",
]
scaffold.intermediate_steps = [
"Use addition if a total is involved.",
"Use subtraction if a difference is involved.",
"Check carefully which final quantity the question wants.",
]
scaffold.first_move = "Start by assigning a shared multiplier to the ratio parts."
scaffold.next_hint = "Then use the given condition to turn the ratio setup into an equation."
scaffold.variables_to_define = [
"Let the common multiplier be k.",
]
scaffold.equations_to_form = [
"actual quantity = ratio part × k",
]
scaffold.common_traps = [
"Reversing the order of the ratio.",
"Not using one shared multiplier.",
"Stopping at the multiplier instead of the requested quantity.",
]
scaffold.key_operations = [
"identify the parts",
"use one shared multiplier",
"build the equation from the given condition",
]
scaffold.hint_ladder = [
"What exactly is being compared?",
"Can you express the ratio parts using k?",
"What given condition turns that setup into an equation?",
]
result.scaffold = scaffold
result.meta = {
"intent": "explain_question",
"bridge_ready": True,
"hint_style": "step_ready",
"subtype": subtype,
}
return result