feat:增加训练场和错题集接口
This commit is contained in:
6
.env
6
.env
@@ -7,8 +7,8 @@ OCR_API_URL=https://paddleocr.aistudio-app.com/api/v2/ocr/jobs
|
|||||||
OCR_API_TOKEN=df7dcc85a5c3c9d64e421f353d11d13ec45512f6
|
OCR_API_TOKEN=df7dcc85a5c3c9d64e421f353d11d13ec45512f6
|
||||||
OCR_MODEL=PaddleOCR-VL-1.6
|
OCR_MODEL=PaddleOCR-VL-1.6
|
||||||
|
|
||||||
MODEL_NAME=deepseek-v4-flash
|
MODEL_NAME="glm-5.2"
|
||||||
MODEL_BASE_URL=https://api.deepseek.com
|
MODEL_BASE_URL="https://dashscope.aliyuncs.com/compatible-mode/v1"
|
||||||
MODEL_API_KEY=sk-0b237d41f6bc44fc9732ea66bd7eade0
|
MODEL_API_KEY="sk-52bcd98e9c1d45908437c4e8706eefff"
|
||||||
|
|
||||||
DATABASE_URL=postgresql+asyncpg://agent:medical%402019@60.247.145.200:5432/study
|
DATABASE_URL=postgresql+asyncpg://agent:medical%402019@60.247.145.200:5432/study
|
||||||
|
|||||||
40
main.py
40
main.py
@@ -8,8 +8,10 @@ from starlette.responses import StreamingResponse
|
|||||||
from database import get_db
|
from database import get_db
|
||||||
from schemas.agent import QueryRequest
|
from schemas.agent import QueryRequest
|
||||||
from schemas.library import LibraryFileRequest, LibraryFileResponse
|
from schemas.library import LibraryFileRequest, LibraryFileResponse
|
||||||
|
from schemas.mistake import MistakeResponse
|
||||||
|
from schemas.practice import PracticeQuestionResponse, PracticeCheckResult, PracticeRecordResponse
|
||||||
|
from service import library_service, practice_service, mistake_service
|
||||||
from service.agent_service import query_agent
|
from service.agent_service import query_agent
|
||||||
from service.library_service import add_file, get_file, list_files, edit_file
|
|
||||||
from storage import upload_rustfs
|
from storage import upload_rustfs
|
||||||
from ocr import ocr_from_url
|
from ocr import ocr_from_url
|
||||||
|
|
||||||
@@ -39,20 +41,40 @@ def generate(query: QueryRequest):
|
|||||||
|
|
||||||
|
|
||||||
@app.get("/library/files", response_model=List[LibraryFileResponse])
|
@app.get("/library/files", response_model=List[LibraryFileResponse])
|
||||||
async def list_library_file(db: AsyncSession = Depends(get_db)):
|
async def list_files(db: AsyncSession = Depends(get_db)):
|
||||||
return await list_files(db)
|
return await library_service.list_files(db)
|
||||||
|
|
||||||
|
|
||||||
@app.get("/library/files/{id}", response_model=LibraryFileResponse)
|
@app.get("/library/files/{id}", response_model=LibraryFileResponse)
|
||||||
async def get_library_file(id: int, db: AsyncSession = Depends(get_db)):
|
async def get_file(id: int, db: AsyncSession = Depends(get_db)):
|
||||||
return await get_file(db, id)
|
return await library_service.get_file(db, id)
|
||||||
|
|
||||||
|
|
||||||
@app.post("/library/files", response_model=bool)
|
@app.post("/library/files", response_model=bool)
|
||||||
async def add_library_file(data: LibraryFileRequest, db: AsyncSession = Depends(get_db)):
|
async def add_file(data: LibraryFileRequest, db: AsyncSession = Depends(get_db)):
|
||||||
return await add_file(db, data)
|
return await library_service.add_file(db, data)
|
||||||
|
|
||||||
|
|
||||||
@app.put("/library/files/{id}", response_model=bool)
|
@app.put("/library/files/{id}", response_model=bool)
|
||||||
async def edit_library_file(id: int, data: LibraryFileRequest, db: AsyncSession = Depends(get_db)):
|
async def edit_file(id: int, data: LibraryFileRequest, db: AsyncSession = Depends(get_db)):
|
||||||
return await edit_file(db, id, data)
|
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.get("/mistake", response_model=List[MistakeResponse])
|
||||||
|
async def list_mistakes(db: AsyncSession = Depends(get_db)):
|
||||||
|
return await mistake_service.list_mistakes(db)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from sqlalchemy import Column, BigInteger, String, Text, TIMESTAMP, Integer, ForeignKey
|
from sqlalchemy import Column, BigInteger, String, Text, Integer, ForeignKey
|
||||||
from sqlalchemy.dialects.postgresql import JSONB
|
from sqlalchemy.dialects.postgresql import JSONB
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
|
|
||||||
@@ -19,6 +19,18 @@ class LibraryFile(AuditBase):
|
|||||||
cascade="all, delete-orphan"
|
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):
|
class LibraryFileQuestion(AuditBase):
|
||||||
library_file_id = Column(BigInteger, ForeignKey("library_file.id"), nullable=False)
|
library_file_id = Column(BigInteger, ForeignKey("library_file.id"), nullable=False)
|
||||||
@@ -32,3 +44,38 @@ class LibraryFileQuestion(AuditBase):
|
|||||||
"LibraryFile",
|
"LibraryFile",
|
||||||
back_populates="questions"
|
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)
|
||||||
|
|
||||||
|
total_count = Column(Integer, nullable=False)
|
||||||
|
correct_count = Column(Integer, nullable=False)
|
||||||
|
wrong_count = Column(Integer, nullable=False)
|
||||||
|
|
||||||
|
library_file = relationship(
|
||||||
|
"LibraryFile",
|
||||||
|
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",
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
|
|||||||
18
schemas/mistake.py
Normal file
18
schemas/mistake.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
from typing import List
|
||||||
|
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class MistakeResponse(BaseModel):
|
||||||
|
id: int
|
||||||
|
subject: str
|
||||||
|
module: str
|
||||||
|
name: str
|
||||||
|
type: str
|
||||||
|
question: str
|
||||||
|
options: List[str]
|
||||||
|
answer: List[str]
|
||||||
|
wrongCount: int
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
from_attributes = True
|
||||||
38
schemas/practice.py
Normal file
38
schemas/practice.py
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
from typing import List
|
||||||
|
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from schemas.library import QuestionRequest
|
||||||
|
|
||||||
|
|
||||||
|
class PracticeRecordRequest(BaseModel):
|
||||||
|
libraryFileId: int
|
||||||
|
totalCount: int
|
||||||
|
correctCount: int
|
||||||
|
wrongCount: int
|
||||||
|
|
||||||
|
|
||||||
|
class PracticeRecordResponse(PracticeRecordRequest):
|
||||||
|
id: int
|
||||||
|
createTime: str
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
from_attributes = True
|
||||||
|
|
||||||
|
|
||||||
|
class PracticeQuestionResponse(QuestionRequest):
|
||||||
|
id: int
|
||||||
|
select: List[str]
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
from_attributes = True
|
||||||
|
|
||||||
|
|
||||||
|
class PracticeCheckResult(BaseModel):
|
||||||
|
id: int
|
||||||
|
answer: List[str]
|
||||||
|
select: List[str]
|
||||||
|
isCorrect: bool
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
from_attributes = True
|
||||||
@@ -121,7 +121,7 @@ async def get_file(db: AsyncSession, file_id: int) -> LibraryFileResponse:
|
|||||||
type=q.type,
|
type=q.type,
|
||||||
question=q.question,
|
question=q.question,
|
||||||
options=q.options,
|
options=q.options,
|
||||||
answer=q.answer,
|
answer=q.answer
|
||||||
)
|
)
|
||||||
for q in file.questions
|
for q in file.questions
|
||||||
],
|
],
|
||||||
|
|||||||
68
service/mistake_service.py
Normal file
68
service/mistake_service.py
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
from sqlalchemy import select
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
|
from models.library import Mistake, LibraryFile, LibraryFileQuestion
|
||||||
|
from schemas.mistake import MistakeResponse
|
||||||
|
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
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"],
|
||||||
|
)
|
||||||
|
for row in rows
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
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.flush()
|
||||||
107
service/practice_service.py
Normal file
107
service/practice_service.py
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
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())
|
||||||
|
records = (await db.execute(stmt)).scalars().all()
|
||||||
|
|
||||||
|
return [
|
||||||
|
PracticeRecordResponse(
|
||||||
|
id=record.id,
|
||||||
|
libraryFileId=record.library_file_id,
|
||||||
|
totalCount=record.total_count,
|
||||||
|
correctCount=record.correct_count,
|
||||||
|
wrongCount=record.wrong_count,
|
||||||
|
createTime=record.create_time.strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
|
)
|
||||||
|
for record in records
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
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):
|
||||||
|
record = PracticeRecord(
|
||||||
|
library_file_id=data.libraryFileId,
|
||||||
|
total_count=data.totalCount,
|
||||||
|
correct_count=data.correctCount,
|
||||||
|
wrong_count=data.wrongCount,
|
||||||
|
)
|
||||||
|
|
||||||
|
db.add(record)
|
||||||
|
await db.commit()
|
||||||
|
|
||||||
|
return True
|
||||||
Reference in New Issue
Block a user