nex_math/backend/services/knowledge_service.py

227 lines
6.4 KiB
Python

"""知识点关系:全局 Knowledge + 章节/题目多对多 + 用户掌握度。"""
from __future__ import annotations
from sqlalchemy.orm import Session
from models import (
ChapterKnowledge,
Knowledge,
KnowledgeRelation,
KnowledgeResource,
Question,
QuestionKnowledge,
UserKnowledge,
)
def ensure_knowledge(db: Session, name: str) -> Knowledge:
clean = name.strip()
row = db.query(Knowledge).filter(Knowledge.name == clean).first()
if row is None:
row = Knowledge(name=clean)
db.add(row)
db.flush()
return row
def ensure_knowledge_relation(
db: Session,
source: Knowledge,
target: Knowledge,
relation_type: str = "前置",
) -> KnowledgeRelation:
row = (
db.query(KnowledgeRelation)
.filter(
KnowledgeRelation.source_id == source.id,
KnowledgeRelation.target_id == target.id,
KnowledgeRelation.relation_type == relation_type,
)
.first()
)
if row is None:
row = KnowledgeRelation(
source_id=source.id,
target_id=target.id,
relation_type=relation_type,
)
db.add(row)
db.flush()
return row
def chapter_knowledge_names(db: Session, chapter_id: int) -> list[str]:
rows = (
db.query(Knowledge.name)
.join(ChapterKnowledge, ChapterKnowledge.knowledge_id == Knowledge.id)
.filter(ChapterKnowledge.chapter_id == chapter_id)
.order_by(ChapterKnowledge.id.asc())
.all()
)
return [name for (name,) in rows]
def knowledge_resources(db: Session, knowledge_id: int) -> list[KnowledgeResource]:
return (
db.query(KnowledgeResource)
.filter(KnowledgeResource.knowledge_id == knowledge_id)
.order_by(KnowledgeResource.position.asc(), KnowledgeResource.id.asc())
.all()
)
def ensure_knowledge_resource(
db: Session,
knowledge_id: int,
*,
kind: str,
title: str,
url: str,
textbook_id: int | None = None,
course_id: int | None = None,
) -> KnowledgeResource:
row = (
db.query(KnowledgeResource)
.filter(
KnowledgeResource.knowledge_id == knowledge_id,
KnowledgeResource.kind == kind,
KnowledgeResource.url == url,
)
.first()
)
if row is None:
row = KnowledgeResource(
knowledge_id=knowledge_id,
kind=kind,
title=title,
url=url,
textbook_id=textbook_id,
course_id=course_id,
)
db.add(row)
db.flush()
return row
def set_chapter_knowledge_names(
db: Session, chapter_id: int, names: list[str]
) -> list[Knowledge]:
cleaned = list(dict.fromkeys(name.strip() for name in names if name.strip()))
db.query(ChapterKnowledge).filter(
ChapterKnowledge.chapter_id == chapter_id
).delete(synchronize_session=False)
db.flush()
ordered: list[Knowledge] = []
for name in cleaned:
knowledge = ensure_knowledge(db, name)
db.add(ChapterKnowledge(chapter_id=chapter_id, knowledge_id=knowledge.id))
ordered.append(knowledge)
db.flush()
return ordered
def set_chapter_knowledge_ids(
db: Session, chapter_id: int, knowledge_ids: list[int]
) -> list[Knowledge]:
ids = list(dict.fromkeys(int(kid) for kid in knowledge_ids))
rows = (
db.query(Knowledge).filter(Knowledge.id.in_(ids)).all()
if ids
else []
)
by_id = {row.id: row for row in rows}
if len(by_id) != len(ids):
raise ValueError("部分知识点不存在")
db.query(ChapterKnowledge).filter(
ChapterKnowledge.chapter_id == chapter_id
).delete(synchronize_session=False)
db.flush()
ordered = [by_id[kid] for kid in ids]
for knowledge in ordered:
db.add(ChapterKnowledge(chapter_id=chapter_id, knowledge_id=knowledge.id))
db.flush()
return ordered
def question_knowledge_names(db: Session, question: Question) -> list[str]:
rows = (
db.query(Knowledge.name)
.join(QuestionKnowledge, QuestionKnowledge.knowledge_id == Knowledge.id)
.filter(QuestionKnowledge.question_id == question.id)
.order_by(QuestionKnowledge.id.asc())
.all()
)
names = [name for (name,) in rows]
return names or ([question.knowledge_name] if question.knowledge_name else [])
def set_question_knowledge_names(
db: Session,
question: Question,
names: list[str],
*,
attach_to_chapter: bool = False,
) -> list[Knowledge]:
cleaned = list(dict.fromkeys(name.strip() for name in names if name.strip()))
if not cleaned and question.knowledge_name:
cleaned = [question.knowledge_name]
db.query(QuestionKnowledge).filter(
QuestionKnowledge.question_id == question.id
).delete(synchronize_session=False)
db.flush()
ordered: list[Knowledge] = []
for name in cleaned:
knowledge = ensure_knowledge(db, name)
db.add(
QuestionKnowledge(question_id=question.id, knowledge_id=knowledge.id)
)
ordered.append(knowledge)
if attach_to_chapter and question.chapter_id is not None:
exists = (
db.query(ChapterKnowledge)
.filter(
ChapterKnowledge.chapter_id == question.chapter_id,
ChapterKnowledge.knowledge_id == knowledge.id,
)
.first()
)
if exists is None:
db.add(
ChapterKnowledge(
chapter_id=question.chapter_id,
knowledge_id=knowledge.id,
)
)
db.flush()
if cleaned:
question.knowledge_name = cleaned[0]
return ordered
def ensure_user_knowledge(
db: Session, user_id: int, knowledge: Knowledge
) -> UserKnowledge:
row = (
db.query(UserKnowledge)
.filter(
UserKnowledge.user_id == user_id,
UserKnowledge.knowledge_id == knowledge.id,
)
.first()
)
if row is None:
max_position = (
db.query(UserKnowledge.position)
.filter(UserKnowledge.user_id == user_id)
.count()
)
row = UserKnowledge(
user_id=user_id,
knowledge_id=knowledge.id,
mastery=60,
position=max_position,
)
db.add(row)
db.flush()
return row