自动学习建议解决薄弱知识点

学习报告不只是"统计",还会给出:

  • 当前最薄弱的知识点
  • 为什么判定它薄弱
  • 建议先练什么
  • 自动推荐对应练习题

后端 schema 新增

修改 backend/app/schemas.py

python 复制代码
class WeakKnowledgeItem(BaseModel):
    name: str
    wrong_count: int
    total_count: int
    wrong_rate: float
    suggestion: str


class StudySuggestionResponse(BaseModel):
    weak_knowledge_points: List[WeakKnowledgeItem]
    overall_suggestion: str

新增建议分析服务

新增 backend/app/suggestion_service.py

ini 复制代码
import json
from collections import defaultdict
from sqlalchemy.orm import Session
from app.models import QuestionHistory


def build_study_suggestion(db: Session):
    rows = db.query(QuestionHistory).all()

    stat_map = defaultdict(lambda: {"total": 0, "wrong": 0})

    for row in rows:
        try:
            knowledge_points = json.loads(row.knowledge_points or "[]")
        except Exception:
            knowledge_points = []

        for kp in knowledge_points:
            if not kp:
                continue
            stat_map[kp]["total"] += 1
            if row.is_wrong:
                stat_map[kp]["wrong"] += 1

    weak_list = []
    for name, stat in stat_map.items():
        total = stat["total"]
        wrong = stat["wrong"]
        wrong_rate = round((wrong / total * 100), 2) if total > 0 else 0.0

        if wrong > 0:
            if wrong_rate >= 60:
                suggestion = f"建议优先强化"{name}"基础概念,并连续练习 3~5 道同类题。"
            elif wrong_rate >= 30:
                suggestion = f"建议针对"{name}"做专项复习,并配合 2~3 道练习题巩固。"
            else:
                suggestion = f""{name}"有少量错误,建议复盘错题并适量练习。"

            weak_list.append({
                "name": name,
                "wrong_count": wrong,
                "total_count": total,
                "wrong_rate": wrong_rate,
                "suggestion": suggestion,
            })

    weak_list.sort(key=lambda x: (x["wrong_rate"], x["wrong_count"]), reverse=True)
    weak_list = weak_list[:5]

    if not weak_list:
        overall_suggestion = "当前暂无明显薄弱知识点,建议继续保持练习,并适度拓展更高难度题目。"
    else:
        top_name = weak_list[0]["name"]
        overall_suggestion = f"当前最需要优先突破的知识点是"{top_name}",建议先复习核心方法,再进行针对性训练。"

    return {
        "weak_knowledge_points": weak_list,
        "overall_suggestion": overall_suggestion,
    }

后端主接口新增

修改 backend/app/main.py

1)补充 import

新增:

javascript 复制代码
from app.suggestion_service import build_study_suggestion
from app.schemas import StudySuggestionResponse

2)新增学习建议接口

把这个接口加到 main.py 里:

less 复制代码
@app.get("/api/study-suggestion", response_model=StudySuggestionResponse)
def get_study_suggestion(db: Session = Depends(get_db)):
    try:
        result = build_study_suggestion(db)
        return StudySuggestionResponse(**result)
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

前端 API 新增

修改 frontend/src/api/math.ts

新增类型:

typescript 复制代码
export interface WeakKnowledgeItem {
  name: string
  wrong_count: number
  total_count: number
  wrong_rate: number
  suggestion: string
}

export interface StudySuggestionResponse {
  weak_knowledge_points: WeakKnowledgeItem[]
  overall_suggestion: string
}

新增请求方法:

csharp 复制代码
export function getStudySuggestion() {
  return request.get<StudySuggestionResponse>('/api/study-suggestion')
}

前端页面状态新增

修改 frontend/src/App.vue

1)补充 import

api import 补上:

python 复制代码
import {
  solveMathQuestion,
  solveMathImage,
  getHistoryList,
  getWrongQuestionList,
  markWrongQuestion,
  generatePracticeByKnowledge,
  regenerateQuestion,
  getLearningReport,
  getStudySuggestion,
  type SolveResponse,
  type HistoryItem,
  type PracticeQuestionItem,
  type LearningReportResponse,
  type StudySuggestionResponse,
} from './api/math'

2)activeTab 扩展

改成:

csharp 复制代码
const activeTab = ref<'solve' | 'history' | 'wrong' | 'report' | 'suggestion'>('solve')

3)新增状态

script setup 里新增:

csharp 复制代码
const suggestionLoading = ref(false)
const studySuggestion = ref<StudySuggestionResponse | null>(null)

4)新增方法

typescript 复制代码
const loadStudySuggestion = async () => {
  suggestionLoading.value = true
  try {
    const { data } = await getStudySuggestion()
    studySuggestion.value = data
  } catch (error: any) {
    console.error('加载学习建议失败:', error)
    alert(error?.response?.data?.detail || '加载学习建议失败')
  } finally {
    suggestionLoading.value = false
  }
}

const switchToSuggestion = async () => {
  activeTab.value = 'suggestion'
  await loadStudySuggestion()
}

5)这些操作成功后顺手刷新建议

在下面几个方法成功后追加:

handleSubmit
scss 复制代码
await loadStudySuggestion()
handleImageChange
scss 复制代码
await loadStudySuggestion()
toggleWrong
scss 复制代码
await loadStudySuggestion()

前端 tabs 增加入口

修改 frontend/src/App.vue

在 tabs 按钮区域新增:

ini 复制代码
<button
  :class="['tab-btn', activeTab === 'suggestion' ? 'active' : '']"
  @click="switchToSuggestion"
>
  学习建议
</button>

前端模板新增"学习建议"页

修改 frontend/src/App.vue

现在已经有:

  • solve
  • history
  • wrong
  • report

所以需要把 report 分支改成 v-else-if="activeTab === 'report'"

然后最后新增一个 v-else 作为 suggestion 页面。


1)先把 report 分支改成:

arduino 复制代码
<template v-else-if="activeTab === 'report'">

2)最后新增 suggestion 分支:

xml 复制代码
<template v-else>
  <div v-if="suggestionLoading" class="empty">学习建议加载中...</div>

  <div v-else-if="studySuggestion" class="report-panel">
    <div class="result-card">
      <h2>整体学习建议</h2>
      <p>{{ studySuggestion.overall_suggestion }}</p>
    </div>

    <div class="result-card">
      <h2>薄弱知识点分析</h2>

      <div v-if="studySuggestion.weak_knowledge_points.length === 0" class="empty">
        暂无薄弱知识点
      </div>

      <div
        v-for="(item, index) in studySuggestion.weak_knowledge_points"
        :key="index"
        class="weak-item"
      >
        <div class="weak-header">
          <strong>{{ item.name }}</strong>
          <span class="weak-rate">错误率 {{ item.wrong_rate }}%</span>
        </div>

        <div class="weak-meta">
          错误 {{ item.wrong_count }} 次 / 共出现 {{ item.total_count }} 次
        </div>

        <div class="weak-suggestion">
          {{ item.suggestion }}
        </div>

        <button
          class="retry-btn"
          @click="
            practiceKnowledge = item.name;
            activeTab = 'solve';
            handleGeneratePractice();
          "
        >
          生成该知识点练习题
        </button>
      </div>
    </div>
  </div>
</template>

前端样式补充

修改 frontend/src/App.vue

style scoped 里新增:

css 复制代码
.weak-item {
  padding: 16px 0;
  border-bottom: 1px solid #eee;
}

.weak-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 8px;
}

.weak-rate {
  color: #d03050;
  font-weight: 600;
}

.weak-meta {
  color: #666;
  font-size: 14px;
  margin-bottom: 8px;
}

.weak-suggestion {
  margin-bottom: 12px;
  color: #333;
  line-height: 1.7;
}

初始化时顺手加载建议

修改 onMounted

改成:

scss 复制代码
onMounted(async () => {
  await loadHistory()
  await loadWrongList()
  await loadReport()
  await loadStudySuggestion()
})

启动查看效果

重启后端

lua 复制代码
uvicorn app.main:app --reload --port 8000

重启前端

arduino 复制代码
npm run dev

学习建议来源于错题

相关推荐
deephub1 小时前
LangGraph vs Semantic Kernel:状态图与内核插件的两条技术路线对比
人工智能·python·深度学习·大语言模型·agent
SuperEugene1 小时前
Vite 实战教程:alias/env/proxy 配置 + 打包优化避坑|Vue 工程化篇
前端·javascript·vue.js·状态模式·vite
文心快码 Baidu Comate1 小时前
Comate 4.0的自我进化:后端“0帧起手”写前端、自己修自己!
前端·人工智能·后端·ai编程·文心快码·ai编程助手
与虾牵手1 小时前
多轮对话 API 怎么实现?从原理到代码,踩完坑我总结了这套方案
python·aigc·ai编程
geovindu1 小时前
python: Simple Factory Pattern
开发语言·python·设计模式·简单工厂模式
We་ct1 小时前
LeetCode 17. 电话号码的字母组合:回溯算法入门实战
前端·算法·leetcode·typescript·深度优先·深度优先遍历
lihaiting11 小时前
css面试题
前端·css·css3
望京十三兄2 小时前
前端排查项目上线后页面白屏
前端
程序员Sunday2 小时前
vite 8 发布,双引擎时代结束,webpack 的时代真的快过去了
前端