j-js commited on
Commit
01591e3
·
verified ·
1 Parent(s): ee987af

Update conversation_logic.py

Browse files
Files changed (1) hide show
  1. conversation_logic.py +151 -27
conversation_logic.py CHANGED
@@ -222,32 +222,12 @@ def _specific_topic_from_question(question_text: str, fallback_topic: str, class
222
  return "median"
223
  if "range" in q:
224
  return "range"
225
-
226
- ratio_like = any(
227
- phrase in q
228
- for phrase in [
229
- "ratio",
230
- "proportion",
231
- "proportional",
232
- "part to whole",
233
- "out of",
234
- "for every",
235
- "in the same ratio",
236
- "in proportion",
237
- "directly proportional",
238
- "inversely proportional",
239
- ]
240
- )
241
- ratio_like = ratio_like or bool(re.search(r"\b[a-z]\s*/\s*[a-z]\b", q))
242
- ratio_like = ratio_like or bool(re.search(r"\b\d+\s*/\s*\d+\b", q))
243
- ratio_like = ratio_like or bool(re.search(r"\b[a-z]\s*:\s*[a-z]\b", q))
244
- ratio_like = ratio_like or bool(re.search(r"\b\d+\s*:\s*\d+\b", q))
245
- ratio_like = ratio_like or bool(re.search(r"\b[a-z]+\s+to\s+[a-z]+\b", q))
246
- ratio_like = ratio_like or bool(re.search(r"\b\d+\s+to\s+\d+\b", q))
247
-
248
- if ratio_like:
249
  return "ratio"
250
-
251
  if topic == "data" and any(k in q for k in ["dataset", "table", "chart", "graph"]):
252
  return "statistics"
253
  return topic
@@ -580,6 +560,115 @@ def _solver_has_useful_steps(result: Optional[SolverResult]) -> bool:
580
  return bool(result is not None and _get_result_steps(result))
581
 
582
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
583
  def _answer_path_from_steps(steps: List[str], verbosity: float) -> str:
584
  safe_steps = _safe_steps(steps)
585
  if not safe_steps:
@@ -770,6 +859,14 @@ class ConversationEngine:
770
  hint_stage=hint_stage,
771
  verbosity=verbosity,
772
  )
 
 
 
 
 
 
 
 
773
 
774
  # Merge solver result into base result only if it agrees with the classified topic.
775
  if solver_result is not None:
@@ -877,8 +974,15 @@ class ConversationEngine:
877
 
878
  if fallback_pack and fallback_pack.get("topic") == "statistics":
879
  qlow = (solver_input or "").lower()
 
880
  if any(k in qlow for k in ["variability", "spread", "standard deviation"]):
881
- if resolved_help_mode == "answer":
 
 
 
 
 
 
882
  fallback_reply_core = (
883
  "- Notice this is asking about variability, which means spread, not the mean.\n"
884
  "- Compare how far the smallest and largest values sit from the middle value in each dataset.\n"
@@ -968,6 +1072,26 @@ class ConversationEngine:
968
  hint_stage=hint_stage,
969
  topic=result.topic,
970
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
971
  elif fallback_reply_core:
972
  reply_core = fallback_reply_core
973
  result.meta["response_source"] = "fallback"
@@ -1041,4 +1165,4 @@ class ConversationEngine:
1041
  result.meta["session_state"] = state
1042
  result.meta["used_retrieval"] = False
1043
  result.meta["used_generator"] = False
1044
- return result
 
222
  return "median"
223
  if "range" in q:
224
  return "range"
225
+ if (
226
+ "ratio" in q
227
+ or re.search(r"\b[a-z]\s*/\s*[a-z]\b", q)
228
+ or re.search(r"\b\d+\s*/\s*\d+\b", q)
229
+ ):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
230
  return "ratio"
 
231
  if topic == "data" and any(k in q for k in ["dataset", "table", "chart", "graph"]):
232
  return "statistics"
233
  return topic
 
560
  return bool(result is not None and _get_result_steps(result))
561
 
562
 
563
+ def _parse_numeric_option_set(option: str) -> Optional[List[float]]:
564
+ raw = _clean_text(option)
565
+ if not raw:
566
+ return None
567
+ try:
568
+ parts = [float(x.strip()) for x in raw.split(",") if x.strip()]
569
+ except Exception:
570
+ return None
571
+ return parts if len(parts) >= 2 else None
572
+
573
+
574
+ def _question_specific_ratio_reply(question_text: str) -> str:
575
+ q = _clean_text(question_text)
576
+ low = q.lower()
577
+ if re.search(r"\b[a-z]\s*/\s*[a-z]\s*=\s*\d+\s*/\s*\d+", low) and re.search(r"what is\s*\(", low):
578
+ return (
579
+ "- Treat the ratio as matching parts: if a/b = 3/4, you can set a = 3k and b = 4k.\n"
580
+ "- Substitute those part-values into the expression the question asks for instead of solving for specific numbers.\n"
581
+ "- After substitution, simplify the expression by cancelling the common factor k."
582
+ )
583
+ return (
584
+ "- Rewrite the ratio using matching parts, such as 3k and 4k, before touching the target expression.\n"
585
+ "- Build the requested expression from those parts, then simplify only at the end."
586
+ )
587
+
588
+
589
+ def _question_specific_variability_reply(options_text: Optional[List[str]]) -> str:
590
+ parsed = [_parse_numeric_option_set(opt) for opt in (options_text or [])]
591
+ valid = [p for p in parsed if p]
592
+ if valid and all(len(p) == 3 for p in valid):
593
+ return (
594
+ "- This is asking about variability, so compare spread rather than average.\n"
595
+ "- For each three-number set, use the middle value as the centre and compare how far the outer numbers sit from it.\n"
596
+ "- The dataset whose values stretch furthest away from the centre is the one with the greatest variability."
597
+ )
598
+ return (
599
+ "- This is asking about variability, so focus on spread rather than the average.\n"
600
+ "- Compare how tightly clustered or widely spaced the values are in each answer choice.\n"
601
+ "- The choice with the widest spread is the strongest candidate."
602
+ )
603
+
604
+
605
+ def _question_specific_percent_reply(question_text: str) -> str:
606
+ low = _clean_text(question_text).lower()
607
+ if "increased by" in low and "decreased by" in low:
608
+ return (
609
+ "- For back-to-back percent changes, turn the changes into multipliers instead of trying to combine the percentages directly.\n"
610
+ "- Apply the increase multiplier first, then the decrease multiplier to that new amount.\n"
611
+ "- Compare the final multiplier with 1 to decide whether the result is above or below the original."
612
+ )
613
+ return (
614
+ "- Identify the base quantity first, because percent relationships only make sense relative to a base.\n"
615
+ "- Translate the wording into either a multiplier or a percent equation before simplifying."
616
+ )
617
+
618
+
619
+ def _build_question_specific_reply(
620
+ *,
621
+ question_text: str,
622
+ options_text: Optional[List[str]],
623
+ classified_topic: str,
624
+ help_mode: str,
625
+ input_type: str,
626
+ user_text: str,
627
+ ) -> str:
628
+ q = _clean_text(question_text)
629
+ low = q.lower()
630
+ topic = (classified_topic or "general").lower()
631
+ user_low = _clean_text(user_text).lower()
632
+
633
+ if not q:
634
+ return ""
635
+
636
+ if any(k in low for k in ["variability", "spread", "standard deviation"]):
637
+ return _question_specific_variability_reply(options_text)
638
+
639
+ if topic in {"ratio", "algebra"}:
640
+ if re.search(r"\b[a-z]\s*/\s*[a-z]\s*=\s*\d+\s*/\s*\d+", low):
641
+ return _question_specific_ratio_reply(q)
642
+ if "what is" in low and "(" in low and ")" in low and any(sym in low for sym in ["a+b", "x+y", "a-b", "x-y"]):
643
+ return (
644
+ "- Start by rewriting one variable in terms of the other using the relationship you were given.\n"
645
+ "- Then substitute into the exact expression in parentheses, rather than trying to solve for actual numbers.\n"
646
+ "- Simplify only after the whole target expression has been rewritten in one variable or in matching parts."
647
+ )
648
+
649
+ if topic == "percent" or "%" in low or "percent" in low:
650
+ return _question_specific_percent_reply(q)
651
+
652
+ if topic == "statistics" and any(k in low for k in ["dataset", "table", "chart", "graph"]):
653
+ return (
654
+ "- Read the question stem first, then decide which statistic matters before comparing answer choices.\n"
655
+ "- Use the structure of the choices to compare them efficiently instead of computing unnecessary extra values."
656
+ )
657
+
658
+ if topic == "algebra":
659
+ return (
660
+ "- Turn the wording into one clean relationship first.\n"
661
+ "- Then focus on the exact expression the question asks for, rather than solving more than you need to."
662
+ )
663
+
664
+ if input_type in {"hint", "next_hint", "confusion"} or any(
665
+ phrase in user_low for phrase in ["how do i solve", "what do i do first", "what should i do first", "how should i start"]
666
+ ):
667
+ return "- Start by identifying the main relationship in the question, then rewrite the target expression using that relationship before simplifying."
668
+
669
+ return ""
670
+
671
+
672
  def _answer_path_from_steps(steps: List[str], verbosity: float) -> str:
673
  safe_steps = _safe_steps(steps)
674
  if not safe_steps:
 
859
  hint_stage=hint_stage,
860
  verbosity=verbosity,
861
  )
862
+ question_specific_reply_core = _build_question_specific_reply(
863
+ question_text=solver_input,
864
+ options_text=options_text,
865
+ classified_topic=question_topic,
866
+ help_mode=resolved_help_mode,
867
+ input_type=input_type,
868
+ user_text=user_text,
869
+ )
870
 
871
  # Merge solver result into base result only if it agrees with the classified topic.
872
  if solver_result is not None:
 
974
 
975
  if fallback_pack and fallback_pack.get("topic") == "statistics":
976
  qlow = (solver_input or "").lower()
977
+ wants_topic = input_type == "topic_query"
978
  if any(k in qlow for k in ["variability", "spread", "standard deviation"]):
979
+ if wants_topic:
980
+ fallback_reply_core = (
981
+ "- This is a statistics / data insight question about variability (spread).\n"
982
+ "- Focus on how spread out each dataset is rather than the average.\n"
983
+ "- Compare how far the outer values sit from the middle value in each set."
984
+ )
985
+ elif resolved_help_mode == "answer":
986
  fallback_reply_core = (
987
  "- Notice this is asking about variability, which means spread, not the mean.\n"
988
  "- Compare how far the smallest and largest values sit from the middle value in each dataset.\n"
 
1072
  hint_stage=hint_stage,
1073
  topic=result.topic,
1074
  )
1075
+ elif question_specific_reply_core and (
1076
+ _is_help_first_mode(resolved_help_mode)
1077
+ or input_type in {"other", "confusion", "hint", "next_hint"}
1078
+ or not direct_solve_request
1079
+ or not fallback_reply_core
1080
+ ):
1081
+ reply_core = question_specific_reply_core
1082
+ result.meta["response_source"] = "question_specific"
1083
+ result.meta["question_support_used"] = bool(fallback_pack)
1084
+ result.meta["question_support_source"] = fallback_pack.get("support_source") if fallback_pack else None
1085
+ result.meta["question_support_topic"] = fallback_pack.get("topic") if fallback_pack else None
1086
+ reply = format_reply(
1087
+ reply_core,
1088
+ tone=tone,
1089
+ verbosity=verbosity,
1090
+ transparency=transparency,
1091
+ help_mode=resolved_help_mode,
1092
+ hint_stage=hint_stage,
1093
+ topic=result.topic,
1094
+ )
1095
  elif fallback_reply_core:
1096
  reply_core = fallback_reply_core
1097
  result.meta["response_source"] = "fallback"
 
1165
  result.meta["session_state"] = state
1166
  result.meta["used_retrieval"] = False
1167
  result.meta["used_generator"] = False
1168
+ return result