File size: 2,795 Bytes
430c7f3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
# api/learning_tracker_backend.py
"""
Learning Tracker AI Backend — v2.2
Generates weekly highlights and improvement suggestions for a student's learning summary.
Called by the Hanbridge backend service (no sessions, no RAG).
Ref: docs/AI_Interface_Design_v2.2.md §3.4
"""
import json

from api.config import async_client, DEFAULT_MODEL
from api.quiz_backend import _strip_markdown


async def generate_learning_tracker_insights(
    context: dict,
    language: str = "CN",
    model_name: str | None = None,
) -> tuple[dict, int]:
    """
    Analyze a student's weekly learning summary and return AI insights.
    Returns: ({"weekHighlights": "...", "improvementSuggestions": "..."}, tokens_used)
    Raises ValueError on bad AI output.
    """
    model = model_name or DEFAULT_MODEL
    lang_instruction = (
        "Respond in Chinese (中文)."
        if language.upper() in ("CN", "ZH", "中文")
        else "Respond in English."
    )

    system = (
        "You are an academic advisor AI. You will receive a JSON object containing a student's "
        "weekly learning data: overall grade, course progress, attendance, skill mastery scores, "
        "learning hours, and grade trend. Analyze the data and output ONLY a valid JSON object "
        "with exactly two keys:\n"
        '  "weekHighlights": a 1-3 sentence summary of what the student did well this week,\n'
        '  "improvementSuggestions": a 1-3 sentence actionable recommendation for next week.\n'
        "Do not include any other text, markdown, or explanation outside the JSON object.\n"
        f"{lang_instruction}"
    )

    user = (
        "Here is the student's weekly learning summary. Analyze it and return your insights:\n\n"
        f"{json.dumps(context, ensure_ascii=False, default=str)}"
    )

    resp = await async_client.chat.completions.create(
        model=model,
        messages=[{"role": "system", "content": system}, {"role": "user", "content": user}],
        temperature=0.4,
        max_tokens=800,
    )
    content = (resp.choices[0].message.content or "").strip()
    tokens_used = getattr(resp.usage, "total_tokens", None) or 0

    content = _strip_markdown(content)

    try:
        data = json.loads(content)
    except json.JSONDecodeError as exc:
        raise ValueError("json_parse_error") from exc

    if not isinstance(data, dict):
        raise ValueError("json_parse_error")

    week_highlights = (data.get("weekHighlights") or "").strip()
    improvement_suggestions = (data.get("improvementSuggestions") or "").strip()

    if not week_highlights or not improvement_suggestions:
        raise ValueError("missing_fields")

    return {
        "weekHighlights": week_highlights,
        "improvementSuggestions": improvement_suggestions,
    }, tokens_used