j-js commited on
Commit
2e002b9
·
verified ·
1 Parent(s): 2b74f91

Update conversation_logic.py

Browse files
Files changed (1) hide show
  1. conversation_logic.py +187 -41
conversation_logic.py CHANGED
@@ -1,8 +1,10 @@
1
  from __future__ import annotations
2
 
 
 
3
  from typing import Any, Dict, List, Optional
4
 
5
- from context_parser import mentions_choice_letter, user_is_referring_to_existing_question
6
  from formatting import format_reply
7
  from models import SolverResult
8
  from quant_solver import extract_choices, is_quant_question, solve_quant
@@ -28,16 +30,24 @@ class ResponseContext:
28
  @property
29
  def combined_question_block(self) -> str:
30
  parts: List[str] = []
31
-
32
  if self.question_text:
33
  parts.append(self.question_text)
34
-
35
  if self.options_text:
36
  parts.append(self.options_text)
37
-
38
  return "\n".join(parts).strip()
39
 
40
 
 
 
 
 
 
 
 
 
 
 
 
41
  def build_choice_explanation(
42
  chosen_letter: str,
43
  result: SolverResult,
@@ -72,26 +82,158 @@ def build_choice_explanation(
72
  return f"Yes — it’s {chosen_letter}."
73
 
74
 
75
- def get_last_assistant_message(chat_history: List[Dict[str, Any]]) -> str:
76
- for item in reversed(chat_history or []):
77
- if str(item.get("role", "")).strip().lower() == "assistant":
78
- return str(item.get("text", "")).strip()
79
- return ""
 
 
 
 
 
 
80
 
81
 
82
- def handle_conversational_followup(ctx: ResponseContext, help_mode: str) -> Optional[SolverResult]:
83
- user_text = ctx.visible_user_text
84
- lower = user_text.lower().strip()
85
- question_block = ctx.combined_question_block
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
  if not question_block:
88
  return None
89
 
90
- if not user_is_referring_to_existing_question(user_text, ctx.question_text):
 
 
91
  return None
92
 
93
  solved = solve_quant(question_block, "answer")
94
- asked_letter = mentions_choice_letter(user_text)
95
  choices = extract_choices(question_block)
96
 
97
  if asked_letter and solved.answer_letter and asked_letter == solved.answer_letter:
@@ -107,7 +249,6 @@ def handle_conversational_followup(ctx: ResponseContext, help_mode: str) -> Opti
107
 
108
  if asked_letter and solved.answer_letter and asked_letter != solved.answer_letter:
109
  correct_choice_text = choices.get(solved.answer_letter, "").strip()
110
-
111
  if help_mode == "hint":
112
  reply = f"Check the calculation again and compare your result with choice {solved.answer_letter}, not {asked_letter}."
113
  elif help_mode == "walkthrough":
@@ -128,25 +269,31 @@ def handle_conversational_followup(ctx: ResponseContext, help_mode: str) -> Opti
128
  answer_value=solved.answer_value,
129
  )
130
 
131
- if "what is the question asking" in lower or "what is this asking" in lower:
132
- category = ctx.question_category.lower()
133
- question_text = ctx.question_text
134
 
135
- if "variability" in question_text.lower() or "data" in category:
136
- reply = (
137
- "It is asking you to compare how spread out each dataset is and decide which one varies the most."
138
- )
139
- else:
140
- reply = (
141
- "It is asking you to identify the main task, extract the relevant numbers or relationships, and then choose the matching option."
142
- )
143
 
144
- return SolverResult(
145
- reply=reply,
146
- domain="quant",
147
- solved=False,
148
- help_mode="hint",
149
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
150
 
151
  if lower in {"help", "can you help", "help me", "i dont get it", "i don't get it"}:
152
  if help_mode == "hint":
@@ -163,26 +310,20 @@ def handle_conversational_followup(ctx: ResponseContext, help_mode: str) -> Opti
163
 
164
  if "why" in lower or "explain" in lower:
165
  walkthrough_result = solve_quant(question_block, "walkthrough")
166
- if solved.answer_letter and "why is it" not in lower:
167
- choice_text = choices.get(solved.answer_letter, "").strip()
168
- if choice_text:
169
- walkthrough_result.reply += f"\n\nSo the correct choice is {solved.answer_letter} ({choice_text})."
170
- else:
171
- walkthrough_result.reply += f"\n\nSo the correct choice is {solved.answer_letter}."
172
  return walkthrough_result
173
 
174
  if "answer" in lower or "which one" in lower or "what answer" in lower or "are you sure" in lower:
175
  return solve_quant(question_block, "answer")
176
 
177
  last_ai = get_last_assistant_message(ctx.chat_history)
178
- if last_ai and ("that" in lower or "it" in lower or "why" in lower):
179
  return solve_quant(question_block, "walkthrough")
180
 
181
  return None
182
 
183
 
184
  def solve_verbal_or_general(user_text: str, help_mode: str) -> SolverResult:
185
- lower = user_text.lower()
186
 
187
  if any(
188
  k in lower
@@ -261,6 +402,11 @@ def generate_response(
261
  followup.reply = format_reply(followup.reply, tone, verbosity, transparency, followup.help_mode)
262
  return followup
263
 
 
 
 
 
 
264
  if question_block and is_quant_question(question_block):
265
  result = solve_quant(question_block, help_mode)
266
  result.reply = format_reply(result.reply, tone, verbosity, transparency, help_mode)
 
1
  from __future__ import annotations
2
 
3
+ import re
4
+ from fractions import Fraction
5
  from typing import Any, Dict, List, Optional
6
 
7
+ from context_parser import mentions_choice_letter
8
  from formatting import format_reply
9
  from models import SolverResult
10
  from quant_solver import extract_choices, is_quant_question, solve_quant
 
30
  @property
31
  def combined_question_block(self) -> str:
32
  parts: List[str] = []
 
33
  if self.question_text:
34
  parts.append(self.question_text)
 
35
  if self.options_text:
36
  parts.append(self.options_text)
 
37
  return "\n".join(parts).strip()
38
 
39
 
40
+ def normalize_text(s: str) -> str:
41
+ return (s or "").strip().lower()
42
+
43
+
44
+ def get_last_assistant_message(chat_history: List[Dict[str, Any]]) -> str:
45
+ for item in reversed(chat_history or []):
46
+ if str(item.get("role", "")).strip().lower() == "assistant":
47
+ return str(item.get("text", "")).strip()
48
+ return ""
49
+
50
+
51
  def build_choice_explanation(
52
  chosen_letter: str,
53
  result: SolverResult,
 
82
  return f"Yes — it’s {chosen_letter}."
83
 
84
 
85
+ def parse_percent_value(text: str) -> Optional[int]:
86
+ m = re.search(r"(\d+(?:\.\d+)?)\s*%", text or "")
87
+ if not m:
88
+ return None
89
+ try:
90
+ value = float(m.group(1))
91
+ if value.is_integer():
92
+ return int(value)
93
+ return None
94
+ except Exception:
95
+ return None
96
 
97
 
98
+ def percent_to_fraction_text(percent_value: int) -> str:
99
+ frac = Fraction(percent_value, 100)
100
+ return f"{frac.numerator}/{frac.denominator}"
101
+
102
+
103
+ def find_matching_choice_for_value(options_text: str, target_value: str) -> Optional[str]:
104
+ for line in (options_text or "").splitlines():
105
+ m = re.match(r"\s*([A-E])\)\s*(.+?)\s*$", line.strip(), re.IGNORECASE)
106
+ if not m:
107
+ continue
108
+ letter = m.group(1).upper()
109
+ value = m.group(2).strip()
110
+ if value == target_value:
111
+ return letter
112
+ return None
113
+
114
+
115
+ def handle_percent_fraction_question(ctx: ResponseContext, help_mode: str) -> Optional[SolverResult]:
116
+ q = ctx.question_text
117
+ u = normalize_text(ctx.visible_user_text)
118
+
119
+ combined = f"{q}\n{ctx.options_text}".lower()
120
+
121
+ mentions_fraction_task = (
122
+ "this equals" in combined
123
+ or "as a fraction" in combined
124
+ or "fraction" in combined
125
+ or "pie chart" in combined
126
+ or "%" in combined
127
+ )
128
+
129
+ if not mentions_fraction_task:
130
+ return None
131
+
132
+ percent_value = parse_percent_value(q) or parse_percent_value(ctx.visible_user_text)
133
+ if percent_value is None:
134
+ return None
135
+
136
+ fraction_text = percent_to_fraction_text(percent_value)
137
+ answer_letter = find_matching_choice_for_value(ctx.options_text, fraction_text)
138
+
139
+ asking_task = (
140
+ "what is the question asking" in u
141
+ or "what is this asking" in u
142
+ or "what do i need to do" in u
143
+ )
144
+
145
+ asking_hint = (
146
+ "hint" in u
147
+ or "how do i start" in u
148
+ or "first step" in u
149
+ or "what do i do first" in u
150
+ or "can you help" in u
151
+ or u in {"help", "help me", "i dont get it", "i don't get it"}
152
+ )
153
+
154
+ asking_answer = (
155
+ "answer" in u
156
+ or "which one" in u
157
+ or "what answer" in u
158
+ or "what is 25% as a fraction" in u
159
+ or "are you sure" in u
160
+ )
161
+
162
+ if asking_task:
163
+ reply = "It is asking you to convert 25% into an equivalent fraction and then match it to the correct option."
164
+ return SolverResult(
165
+ reply=reply,
166
+ domain="quant",
167
+ solved=False,
168
+ help_mode="hint",
169
+ answer_letter=answer_letter,
170
+ answer_value=fraction_text,
171
+ )
172
+
173
+ if asking_hint or help_mode == "hint":
174
+ reply = (
175
+ "Think of percent as 'out of 100'.\n"
176
+ f"So {percent_value}% means {percent_value}/100.\n"
177
+ "Then simplify that fraction and compare it with the options."
178
+ )
179
+ return SolverResult(
180
+ reply=reply,
181
+ domain="quant",
182
+ solved=True,
183
+ help_mode="hint",
184
+ answer_letter=answer_letter,
185
+ answer_value=fraction_text,
186
+ )
187
+
188
+ if asking_answer or help_mode == "answer":
189
+ if answer_letter:
190
+ reply = f"{percent_value}% = {percent_value}/100 = {fraction_text}, so the correct answer is {answer_letter}."
191
+ else:
192
+ reply = f"{percent_value}% = {percent_value}/100 = {fraction_text}."
193
+ return SolverResult(
194
+ reply=reply,
195
+ domain="quant",
196
+ solved=True,
197
+ help_mode="answer",
198
+ answer_letter=answer_letter,
199
+ answer_value=fraction_text,
200
+ )
201
 
202
+ if help_mode == "walkthrough":
203
+ if answer_letter:
204
+ reply = (
205
+ f"Percent means 'per 100', so {percent_value}% = {percent_value}/100.\n"
206
+ f"Simplify {percent_value}/100 to {fraction_text}.\n"
207
+ f"Now compare that with the options: {fraction_text} matches choice {answer_letter}."
208
+ )
209
+ else:
210
+ reply = (
211
+ f"Percent means 'per 100', so {percent_value}% = {percent_value}/100.\n"
212
+ f"Simplify {percent_value}/100 to {fraction_text}."
213
+ )
214
+ return SolverResult(
215
+ reply=reply,
216
+ domain="quant",
217
+ solved=True,
218
+ help_mode="walkthrough",
219
+ answer_letter=answer_letter,
220
+ answer_value=fraction_text,
221
+ )
222
+
223
+ return None
224
+
225
+
226
+ def handle_choice_letter_followup(ctx: ResponseContext, help_mode: str) -> Optional[SolverResult]:
227
+ question_block = ctx.combined_question_block
228
  if not question_block:
229
  return None
230
 
231
+ user_text = ctx.visible_user_text
232
+ asked_letter = mentions_choice_letter(user_text)
233
+ if not asked_letter:
234
  return None
235
 
236
  solved = solve_quant(question_block, "answer")
 
237
  choices = extract_choices(question_block)
238
 
239
  if asked_letter and solved.answer_letter and asked_letter == solved.answer_letter:
 
249
 
250
  if asked_letter and solved.answer_letter and asked_letter != solved.answer_letter:
251
  correct_choice_text = choices.get(solved.answer_letter, "").strip()
 
252
  if help_mode == "hint":
253
  reply = f"Check the calculation again and compare your result with choice {solved.answer_letter}, not {asked_letter}."
254
  elif help_mode == "walkthrough":
 
269
  answer_value=solved.answer_value,
270
  )
271
 
272
+ return None
 
 
273
 
 
 
 
 
 
 
 
 
274
 
275
+ def handle_conversational_followup(ctx: ResponseContext, help_mode: str) -> Optional[SolverResult]:
276
+ user_text = ctx.visible_user_text
277
+ lower = normalize_text(user_text)
278
+ question_block = ctx.combined_question_block
279
+
280
+ if not question_block:
281
+ return None
282
+
283
+ percent_fraction = handle_percent_fraction_question(ctx, help_mode)
284
+ if percent_fraction is not None:
285
+ return percent_fraction
286
+
287
+ choice_followup = handle_choice_letter_followup(ctx, help_mode)
288
+ if choice_followup is not None:
289
+ return choice_followup
290
+
291
+ if "what is the question asking" in lower or "what is this asking" in lower:
292
+ if "variability" in ctx.question_text.lower() or "spread" in ctx.question_text.lower():
293
+ reply = "It is asking you to compare how spread out the answer choices are and identify which dataset varies the most."
294
+ else:
295
+ reply = "It is asking you to identify the mathematical task and then match the result to one of the options."
296
+ return SolverResult(reply=reply, domain="quant", solved=False, help_mode="hint")
297
 
298
  if lower in {"help", "can you help", "help me", "i dont get it", "i don't get it"}:
299
  if help_mode == "hint":
 
310
 
311
  if "why" in lower or "explain" in lower:
312
  walkthrough_result = solve_quant(question_block, "walkthrough")
 
 
 
 
 
 
313
  return walkthrough_result
314
 
315
  if "answer" in lower or "which one" in lower or "what answer" in lower or "are you sure" in lower:
316
  return solve_quant(question_block, "answer")
317
 
318
  last_ai = get_last_assistant_message(ctx.chat_history)
319
+ if last_ai and ("that" in lower or "it" in lower):
320
  return solve_quant(question_block, "walkthrough")
321
 
322
  return None
323
 
324
 
325
  def solve_verbal_or_general(user_text: str, help_mode: str) -> SolverResult:
326
+ lower = normalize_text(user_text)
327
 
328
  if any(
329
  k in lower
 
402
  followup.reply = format_reply(followup.reply, tone, verbosity, transparency, followup.help_mode)
403
  return followup
404
 
405
+ percent_fraction = handle_percent_fraction_question(ctx, help_mode)
406
+ if percent_fraction is not None:
407
+ percent_fraction.reply = format_reply(percent_fraction.reply, tone, verbosity, transparency, percent_fraction.help_mode)
408
+ return percent_fraction
409
+
410
  if question_block and is_quant_question(question_block):
411
  result = solve_quant(question_block, help_mode)
412
  result.reply = format_reply(result.reply, tone, verbosity, transparency, help_mode)