feat:更新练习题接口

This commit is contained in:
2026-07-30 13:07:26 +08:00
parent 50a27323cc
commit 9c9ac3ab1c
9 changed files with 83 additions and 266 deletions

View File

@@ -21,6 +21,7 @@ study_prompt = """
- JSON 必须是合法格式,可被程序直接解析。
4. **返回格式(严格遵循)**
- select字段是用户实际选择的永远为空数组
[
{
"id": 1,
@@ -32,7 +33,8 @@ study_prompt = """
"选项C内容",
"选项D内容"
],
"answer": ["A"]
"answer": ["A"],
"select": []
},
{
"id": 2,
@@ -44,7 +46,8 @@ study_prompt = """
"选项C内容",
"选项D内容"
],
"answer": ["A", "C"]
"answer": ["A", "C"],
"select": []
}
]
"""

25
main.py
View File

@@ -8,8 +8,8 @@ from starlette.responses import StreamingResponse
from database import get_db
from schemas.agent import QueryRequest
from schemas.library import LibraryFileRequest, LibraryFileResponse
from schemas.mistake import MistakeResponse
from schemas.practice import PracticeQuestionResponse, PracticeCheckResult, PracticeRecordResponse
from schemas.mistake import MistakeResponse, MistakeRequest
from schemas.practice import PracticeRecordResponse, PracticeRecordRequest
from service import library_service, practice_service, mistake_service
from service.agent_service import query_agent
from storage import upload_rustfs
@@ -60,21 +60,26 @@ async def edit_file(id: int, data: LibraryFileRequest, db: AsyncSession = Depend
return await library_service.edit_file(db, id, data)
@app.get("/practice/files/{id}", response_model=List[PracticeQuestionResponse])
async def get_question(id: int, db: AsyncSession = Depends(get_db)):
return await practice_service.get_question(db, id)
@app.get("/practice/record/{id}", response_model=List[PracticeRecordResponse])
async def get_record(id: int, db: AsyncSession = Depends(get_db)):
return await practice_service.get_record(db, id)
@app.post("/practice/check/{id}", response_model=List[PracticeCheckResult])
async def check_practice(id: int, data: List[PracticeQuestionResponse], db: AsyncSession = Depends(get_db)):
return await practice_service.check_practice(db, id, data)
@app.post("/practice/record", response_model=bool)
async def add_record(data: PracticeRecordRequest, db: AsyncSession = Depends(get_db)):
return await practice_service.add_record(db, data)
@app.get("/mistake", response_model=List[MistakeResponse])
async def list_mistakes(db: AsyncSession = Depends(get_db)):
return await mistake_service.list_mistakes(db)
@app.post("/mistake", response_model=bool)
async def list_mistakes(data: list[MistakeRequest], db: AsyncSession = Depends(get_db)):
return await mistake_service.add_mistakes(db, data)
@app.delete("/mistake/{id}", response_model=bool)
async def remove_mistake(id: int, db: AsyncSession = Depends(get_db)):
return await mistake_service.remove_mistake(db, id)

View File

@@ -13,44 +13,12 @@ class LibraryFile(AuditBase):
size = Column(Integer, nullable=False)
content = Column(Text, nullable=False)
questions = relationship(
"LibraryFileQuestion",
back_populates="library_file",
cascade="all, delete-orphan"
)
practice_record = relationship(
"PracticeRecord",
back_populates="library_file",
cascade="all, delete-orphan"
)
mistake = relationship(
"Mistake",
back_populates="library_file",
cascade="all, delete-orphan"
)
class LibraryFileQuestion(AuditBase):
library_file_id = Column(BigInteger, ForeignKey("library_file.id"), nullable=False)
type = Column(String(45), nullable=False)
question = Column(Text, nullable=False)
options = Column(JSONB, nullable=False)
answer = Column(JSONB, nullable=False)
library_file = relationship(
"LibraryFile",
back_populates="questions"
)
mistake = relationship(
"Mistake",
back_populates="library_file_question",
cascade="all, delete-orphan"
)
class PracticeRecord(AuditBase):
library_file_id = Column(BigInteger, ForeignKey("library_file.id"), nullable=False)
@@ -64,18 +32,12 @@ class PracticeRecord(AuditBase):
back_populates="practice_record",
)
class Mistake(AuditBase):
library_file_id = Column(BigInteger, ForeignKey("library_file.id"), nullable=False)
question_id = Column(BigInteger, ForeignKey("library_file_question.id"), nullable=False)
wrong_count = Column(Integer, nullable=False)
library_file = relationship(
"LibraryFile",
back_populates="mistake",
)
library_file_question = relationship(
"LibraryFileQuestion",
back_populates="mistake",
)
subject = Column(String(45), nullable=False)
module = Column(String(45), nullable=False)
name = Column(String(45), nullable=False)
type = Column(String(45), nullable=False)
question = Column(Text, nullable=False)
options = Column(JSONB, nullable=False)
answer = Column(JSONB, nullable=False)

View File

@@ -1,19 +1,4 @@
from pydantic import BaseModel
from typing import List
class QuestionRequest(BaseModel):
type: str
question: str
options: List[str]
answer: List[str]
class QuestionResponse(QuestionRequest):
id: int
class Config:
from_attributes = True
class LibraryFileRequest(BaseModel):
@@ -24,13 +9,10 @@ class LibraryFileRequest(BaseModel):
type: str
content: str
questions: List[QuestionRequest] = []
class LibraryFileResponse(LibraryFileRequest):
id: int
uploadTime: str
questions: List[QuestionResponse] = []
class Config:
from_attributes = True

View File

@@ -3,8 +3,7 @@ from typing import List
from pydantic import BaseModel
class MistakeResponse(BaseModel):
id: int
class MistakeRequest(BaseModel):
subject: str
module: str
name: str
@@ -12,7 +11,13 @@ class MistakeResponse(BaseModel):
question: str
options: List[str]
answer: List[str]
wrongCount: int
class Config:
from_attributes = True
class MistakeResponse(MistakeRequest):
id: int
class Config:
from_attributes = True

View File

@@ -2,8 +2,6 @@ from typing import List
from pydantic import BaseModel
from schemas.library import QuestionRequest
class PracticeRecordRequest(BaseModel):
libraryFileId: int
@@ -20,14 +18,6 @@ class PracticeRecordResponse(PracticeRecordRequest):
from_attributes = True
class PracticeQuestionResponse(QuestionRequest):
id: int
select: List[str]
class Config:
from_attributes = True
class PracticeCheckResult(BaseModel):
id: int
answer: List[str]
@@ -35,4 +25,4 @@ class PracticeCheckResult(BaseModel):
isCorrect: bool
class Config:
from_attributes = True
from_attributes = True

View File

@@ -1,9 +1,8 @@
from sqlalchemy import select, delete
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload
from models.library import LibraryFile, LibraryFileQuestion
from schemas.library import LibraryFileRequest, LibraryFileResponse, QuestionResponse
from models.library import LibraryFile
from schemas.library import LibraryFileRequest, LibraryFileResponse
async def list_files(db: AsyncSession) -> list[LibraryFileResponse]:
@@ -20,7 +19,6 @@ async def list_files(db: AsyncSession) -> list[LibraryFileResponse]:
size=file.size,
content=file.content,
uploadTime=file.create_time.strftime("%Y-%m-%d %H:%M:%S"),
questions=[],
)
for file in files
]
@@ -28,7 +26,7 @@ async def list_files(db: AsyncSession) -> list[LibraryFileResponse]:
async def add_file(db: AsyncSession, data: LibraryFileRequest) -> bool:
"""
创建题库文件及其题目
创建题库文件
"""
file = LibraryFile(
subject=data.subject,
@@ -40,18 +38,6 @@ async def add_file(db: AsyncSession, data: LibraryFileRequest) -> bool:
)
db.add(file)
await db.flush()
for q in data.questions:
question = LibraryFileQuestion(
library_file_id=file.id,
type=q.type,
question=q.question,
options=q.options,
answer=q.answer
)
db.add(question)
await db.commit()
return True
@@ -59,7 +45,7 @@ async def add_file(db: AsyncSession, data: LibraryFileRequest) -> bool:
async def edit_file(db: AsyncSession, file_id: int, data: LibraryFileRequest) -> bool:
"""
编辑题库文件及其题目
编辑题库文件
"""
result = await db.execute(
@@ -76,33 +62,13 @@ async def edit_file(db: AsyncSession, file_id: int, data: LibraryFileRequest) ->
file.type = data.type
file.content = data.content
await db.execute(
delete(LibraryFileQuestion)
.where(LibraryFileQuestion.library_file_id == file.id)
)
# 4. 插入新题目
for q in data.questions:
question = LibraryFileQuestion(
library_file_id=file.id,
type=q.type,
question=q.question,
options=q.options,
answer=q.answer,
)
db.add(question)
await db.commit()
return True
async def get_file(db: AsyncSession, file_id: int) -> LibraryFileResponse:
stmt = (
select(LibraryFile)
.options(selectinload(LibraryFile.questions))
.where(LibraryFile.id == file_id)
)
stmt = (select(LibraryFile).where(LibraryFile.id == file_id))
result = await db.execute(stmt)
file = result.scalar_one_or_none()
@@ -115,14 +81,4 @@ async def get_file(db: AsyncSession, file_id: int) -> LibraryFileResponse:
size=file.size,
content=file.content,
uploadTime=file.create_time.strftime("%Y-%m-%d %H:%M:%S"),
questions=[
QuestionResponse(
id=q.id,
type=q.type,
question=q.question,
options=q.options,
answer=q.answer
)
for q in file.questions
],
)

View File

@@ -1,68 +1,51 @@
from sqlalchemy import select
from sqlalchemy import select, delete
from sqlalchemy.ext.asyncio import AsyncSession
from models.library import Mistake, LibraryFile, LibraryFileQuestion
from schemas.mistake import MistakeResponse
from models.library import Mistake
from schemas.mistake import MistakeResponse, MistakeRequest
async def list_mistakes(db: AsyncSession) -> list[MistakeResponse]:
stmt = (
select(
Mistake.id,
LibraryFile.subject,
LibraryFile.module,
LibraryFile.name,
LibraryFileQuestion.type,
LibraryFileQuestion.question,
LibraryFileQuestion.options,
LibraryFileQuestion.answer,
Mistake.wrong_count,
)
.join(
LibraryFileQuestion,
Mistake.question_id == LibraryFileQuestion.id,
)
.join(
LibraryFile,
Mistake.library_file_id == LibraryFile.id,
)
)
stmt = stmt.order_by(Mistake.create_time.desc())
result = await db.execute(stmt)
rows = result.mappings().all()
stmt = select(Mistake).order_by(Mistake.id.desc())
mistakes = (await db.execute(stmt)).scalars().all()
return [
MistakeResponse(
id=row["id"],
subject=row["subject"],
module=row["module"],
name=row["name"],
type=row["type"],
question=row["question"],
options=row["options"],
answer=row["answer"],
wrongCount=row["wrong_count"],
id=mistake.id,
subject=mistake.subject,
module=mistake.module,
name=mistake.name,
type=mistake.type,
question=mistake.question,
options=mistake.options,
answer=mistake.answer
)
for row in rows
for mistake in mistakes
]
async def update_mistake(db: AsyncSession, library_file_id: int, question_id: int):
stmt = select(Mistake).where(
Mistake.library_file_id == library_file_id,
Mistake.question_id == question_id,
async def add_mistakes(db: AsyncSession, data: list[MistakeRequest]):
db.add_all(
[
Mistake(
subject=item.subject,
module=item.module,
name=item.name,
type=item.type,
question=item.question,
options=item.options,
answer=item.answer,
)
for item in data
]
)
result = await db.execute(stmt)
wrong = result.scalar_one_or_none()
if wrong:
wrong.wrong_count += 1
else:
wrong = Mistake(
library_file_id=library_file_id,
question_id=question_id,
wrong_count=1
)
db.add(wrong)
await db.commit()
return True
await db.flush()
async def remove_mistake(db: AsyncSession, mistake_id: int) -> bool:
await db.execute(delete(Mistake).where(Mistake.id == mistake_id))
await db.commit()
return True

View File

@@ -2,18 +2,13 @@ from typing import List
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload
from models.library import LibraryFile, PracticeRecord
from schemas.practice import PracticeQuestionResponse, PracticeRecordRequest, PracticeCheckResult, \
PracticeRecordResponse
from service.library_service import get_file
from service.mistake_service import update_mistake
from models.library import PracticeRecord
from schemas.practice import PracticeRecordRequest, PracticeRecordResponse
async def get_record(db: AsyncSession, library_file_id: int) -> List[PracticeRecordResponse]:
stmt = select(PracticeRecord).where(PracticeRecord.library_file_id == library_file_id).order_by(
PracticeRecord.create_time.desc())
stmt = select(PracticeRecord).where(PracticeRecord.library_file_id == library_file_id).order_by(PracticeRecord.create_time.desc())
records = (await db.execute(stmt)).scalars().all()
return [
@@ -29,71 +24,7 @@ async def get_record(db: AsyncSession, library_file_id: int) -> List[PracticeRec
]
async def get_question(db: AsyncSession, file_id: int) -> List[PracticeQuestionResponse]:
stmt = (
select(LibraryFile)
.options(selectinload(LibraryFile.questions))
.where(LibraryFile.id == file_id)
)
result = await db.execute(stmt)
file = result.scalar_one_or_none()
return [
PracticeQuestionResponse(
id=q.id,
type=q.type,
question=q.question,
options=q.options,
answer=[],
select=[]
)
for q in file.questions
]
async def check_practice(db: AsyncSession,
library_file_id: int,
questions: List[PracticeQuestionResponse]) -> List[PracticeCheckResult]:
library_file = await get_file(db, library_file_id)
db_questions = library_file.questions
check_results = []
for user_q in questions:
db_q = next((q for q in db_questions if q.id == user_q.id), None)
if not db_q:
continue
is_correct = set(user_q.select) == set(db_q.answer)
if not is_correct:
await update_mistake(
db=db,
library_file_id=library_file_id,
question_id=user_q.id,
)
check_results.append(PracticeCheckResult(
id=user_q.id,
answer=db_q.answer,
select=user_q.select,
isCorrect=is_correct,
))
correct_count = sum(1 for r in check_results if r.isCorrect)
await add_practice_record(
db,
PracticeRecordRequest(
libraryFileId=library_file_id,
totalCount=len(check_results),
correctCount=correct_count,
wrongCount=len(check_results) - correct_count,
),
)
return check_results
async def add_practice_record(db: AsyncSession, data: PracticeRecordRequest):
async def add_record(db: AsyncSession, data: PracticeRecordRequest):
record = PracticeRecord(
library_file_id=data.libraryFileId,
total_count=data.totalCount,