File size: 10,337 Bytes
affe63a
 
 
020d9c6
affe63a
 
020d9c6
 
 
 
 
 
 
 
affe63a
 
020d9c6
 
 
 
 
ae81756
020d9c6
 
 
 
ae81756
020d9c6
 
 
 
ae81756
020d9c6
 
 
 
ae81756
 
020d9c6
 
ae81756
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
020d9c6
 
 
ae81756
020d9c6
 
ae81756
 
affe63a
ae81756
020d9c6
ae81756
 
 
 
020d9c6
ae81756
 
020d9c6
 
ae81756
020d9c6
ae81756
020d9c6
ae81756
020d9c6
ae81756
020d9c6
ae81756
020d9c6
ae81756
020d9c6
ae81756
 
020d9c6
 
 
 
 
 
ae81756
020d9c6
ae81756
020d9c6
ae81756
 
 
020d9c6
 
 
 
ae81756
020d9c6
 
 
 
ae81756
020d9c6
ae81756
020d9c6
ae81756
020d9c6
ae81756
020d9c6
 
47fe089
ae81756
020d9c6
ae81756
020d9c6
ae81756
020d9c6
ae81756
020d9c6
ae81756
020d9c6
ae81756
 
020d9c6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ae81756
020d9c6
 
ae81756
020d9c6
 
ae81756
 
47fe089
ae81756
020d9c6
 
ae81756
020d9c6
ae81756
020d9c6
 
 
 
 
 
ae81756
020d9c6
ae81756
020d9c6
 
 
 
 
 
ae81756
020d9c6
ae81756
020d9c6
47fe089
020d9c6
 
ae81756
 
020d9c6
ae81756
020d9c6
ae81756
020d9c6
 
 
 
ae81756
020d9c6
ae81756
020d9c6
47fe089
8ff3d0f
 
020d9c6
8ff3d0f
 
 
 
 
 
 
020d9c6
 
 
 
 
 
 
 
 
 
 
 
 
8ff3d0f
020d9c6
8ff3d0f
 
 
 
 
 
ae81756
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
from __future__ import annotations

import re
from typing import Any, List, Optional


def style_prefix(tone: float) -> str:
    if tone < 0.2:
        return ""
    if tone < 0.45:
        return "Ok —"
    if tone < 0.75:
        return "Let’s work through it."
    return "You’ve got this — let’s work through it step by step."


def _clean_lines(core: str) -> List[str]:
    lines: List[str] = []
    for line in (core or "").splitlines():
        cleaned = line.strip()
        if cleaned:
            lines.append(cleaned.lstrip("- ").strip())
    return lines


def _normalize_key(text: str) -> str:
    return re.sub(r"\s+", " ", (text or "").strip().lower())


def _dedupe_lines(lines: List[str]) -> List[str]:
    seen = set()
    out: List[str] = []
    for line in lines:
        key = _normalize_key(line)
        if key and key not in seen:
            seen.add(key)
            out.append(line.strip())
    return out


def _extract_topic_from_text(core: str, topic: Optional[str]) -> str:
    if topic:
        return str(topic).strip().lower()
    text = (core or "").lower()
    if "probability" in text or "favorable" in text or "sample space" in text:
        return "probability"
    if "percent" in text or "%" in text:
        return "percent"
    if "ratio" in text or "multiplier" in text:
        return "ratio"
    if "variable" in text or "equation" in text:
        return "algebra"
    if "variability" in text or "standard deviation" in text or "spread" in text:
        return "statistics"
    if "rectangle" in text or "perimeter" in text or "area" in text:
        return "geometry"
    return "general"


def _normalize_display_lines(lines: List[str]) -> List[str]:
    return [re.sub(r"\s+", " ", (line or "").strip()) for line in lines if str(line).strip()]


def _limit_steps(lines: List[str], verbosity: float, minimum: int = 1) -> List[str]:
    if not lines:
        return []
    if verbosity < 0.22:
        limit = minimum
    elif verbosity < 0.55:
        limit = max(minimum, min(2, len(lines)))
    elif verbosity < 0.82:
        limit = max(minimum, min(4, len(lines)))
    else:
        limit = len(lines)
    return lines[:limit]


def _why_line(topic: str) -> str:
    if topic == "algebra":
        return "Why this helps: reversing operations in the right order keeps the equation equivalent while you isolate the variable."
    if topic == "percent":
        return "Why this helps: percent questions usually break when the base quantity is chosen incorrectly."
    if topic == "ratio":
        return "Why this helps: ratio numbers are usually parts, not the final quantities themselves."
    if topic == "probability":
        return "Why this helps: the numerator and denominator must be counted under the same rules."
    if topic == "statistics":
        return "Why this helps: statistics questions depend on choosing the right measure before calculating."
    if topic == "geometry":
        return "Why this helps: matching the right formula to the shape simplifies the rest of the work."
    return "Why this helps: getting the structure right first makes the next step clearer."


def _tone_rewrite(line: str, tone: float, position: int = 0) -> str:
    text = (line or "").strip()
    if not text:
        return text
    if tone < 0.25:
        return text
    if tone < 0.55:
        return f"Start here: {text[0].lower() + text[1:] if len(text) > 1 else text.lower()}" if position == 0 else text
    if tone < 0.8:
        return f"A good place to start is this: {text[0].lower() + text[1:] if len(text) > 1 else text.lower()}" if position == 0 else text
    return f"You can start with this: {text[0].lower() + text[1:] if len(text) > 1 else text.lower()}" if position == 0 else text


def _transparency_expansion(line: str, topic: str, transparency: float, position: int = 0) -> str:
    text = (line or "").strip()
    if not text or transparency < 0.35:
        return text
    if transparency < 0.7:
        if position == 0:
            if topic == "algebra":
                return f"{text} This keeps the equation balanced while you isolate the variable."
            if topic == "percent":
                return f"{text} This keeps the percent relationship tied to the correct base quantity."
            if topic == "ratio":
                return f"{text} This turns the ratio into usable quantities instead of labels."
            if topic == "probability":
                return f"{text} This separates successful outcomes from total outcomes."
        return text
    if position == 0:
        if topic == "algebra":
            return f"{text} In algebra, each step should preserve an equivalent equation so the solution does not change while the variable is isolated."
        if topic == "percent":
            return f"{text} Percent problems become clearer once the base quantity is fixed, because every percentage must refer back to some amount."
        if topic == "ratio":
            return f"{text} Ratio numbers usually describe relative parts, so turning them into multiples of one common quantity is what makes the setup usable."
        if topic == "probability":
            return f"{text} Probability depends on a consistent sample space, so the numerator and denominator must be counted under the same rules."
        if topic == "statistics":
            return f"{text} Statistics questions often hinge on choosing the right measure first, because different measures capture different features of the data."
        if topic == "geometry":
            return f"{text} Geometry problems often become routine once the correct formula is chosen, because the rest is usually substitution and algebra."
        return f"{text} This makes the underlying structure explicit before you calculate."
    return text


def _styled_lines(lines: List[str], tone: float, transparency: float, topic: str) -> List[str]:
    output: List[str] = []
    for i, line in enumerate(lines):
        rewritten = _tone_rewrite(line, tone, i)
        rewritten = _transparency_expansion(rewritten, topic, transparency, i)
        output.append(rewritten)
    return output


def format_reply(
    core: str,
    tone: float,
    verbosity: float,
    transparency: float,
    help_mode: str,
    hint_stage: int = 0,
    topic: Optional[str] = None,
) -> str:
    prefix = style_prefix(tone)
    core = (core or "").strip()
    if not core:
        return prefix or "Start with the structure of the problem."

    lines = _dedupe_lines(_clean_lines(core))
    if not lines:
        return prefix or "Start with the structure of the problem."

    resolved_topic = _extract_topic_from_text(core, topic)
    normalized_lines = _normalize_display_lines(lines)
    output: List[str] = []
    if prefix:
        output.extend([prefix, ""])

    if help_mode == "hint":
        idx = max(0, min(int(hint_stage or 1) - 1, len(normalized_lines) - 1))
        if verbosity < 0.25:
            shown = [normalized_lines[idx]]
        elif verbosity < 0.62:
            shown = normalized_lines[idx: idx + 2] or [normalized_lines[idx]]
        else:
            shown = normalized_lines[: min(4, len(normalized_lines))]
        shown = _styled_lines(shown, tone, transparency, resolved_topic)
        output.append("Hint:")
        output.extend(f"- {line}" for line in shown)
        if transparency >= 0.75:
            output.extend(["", _why_line(resolved_topic)])
        return "\n".join(output).strip()

    if help_mode in {"walkthrough", "instruction", "step_by_step"}:
        shown = _limit_steps(normalized_lines, verbosity, minimum=2 if help_mode == "walkthrough" else 1)
        shown = _styled_lines(shown, tone, transparency, resolved_topic)
        output.append("Walkthrough:" if help_mode == "walkthrough" else "Step-by-step path:")
        output.extend(f"- {line}" for line in shown)
        if transparency >= 0.7:
            output.extend(["", _why_line(resolved_topic)])
        return "\n".join(output).strip()

    if help_mode in {"method", "explain", "concept", "definition"}:
        shown = _limit_steps(normalized_lines, verbosity, minimum=1)
        shown = _styled_lines(shown, tone, transparency, resolved_topic)
        output.append("Explanation:")
        output.extend(f"- {line}" for line in shown)
        if transparency >= 0.6:
            output.extend(["", _why_line(resolved_topic)])
        return "\n".join(output).strip()

    if help_mode == "answer":
        shown = _limit_steps(normalized_lines, verbosity, minimum=2)
        answer_transparency = transparency if verbosity >= 0.45 else min(transparency, 0.45)
        shown = _styled_lines(shown, tone, answer_transparency, resolved_topic)
        output.append("Answer path:")
        output.extend(f"- {line}" for line in shown)
        if transparency >= 0.75:
            output.extend(["", _why_line(resolved_topic)])
        return "\n".join(output).strip()

    shown = _limit_steps(normalized_lines, verbosity, minimum=1)
    shown = _styled_lines(shown, tone, transparency, resolved_topic)
    output.extend(f"- {line}" for line in shown)
    if transparency >= 0.8:
        output.extend(["", _why_line(resolved_topic)])
    return "\n".join(output).strip()


def format_explainer_response(
    result: Any,
    tone: float,
    verbosity: float,
    transparency: float,
    help_mode: str = "explain",
    hint_stage: int = 0,
) -> str:
    if not result:
        return "I can help explain what the question is asking, but I need the full wording of the question."
    summary = getattr(result, "summary", "") or ""
    teaching_points = getattr(result, "teaching_points", []) or []
    core_lines: List[str] = []
    if isinstance(summary, str) and summary.strip():
        core_lines.append(summary.strip())
    if isinstance(teaching_points, list):
        for item in teaching_points:
            text = str(item).strip()
            if text:
                core_lines.append(text)
    if not core_lines:
        core_lines = ["Start by identifying what the question is asking."]
    return format_reply(
        core="\n".join(core_lines),
        tone=tone,
        verbosity=verbosity,
        transparency=transparency,
        help_mode=help_mode,
        hint_stage=hint_stage,
        topic=getattr(result, "topic", None),
    )