nex_math/backend/schemas/__init__.py

562 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

"""API 请求 / 响应模型。"""
from __future__ import annotations
from datetime import date, datetime
from typing import Any
from pydantic import BaseModel, ConfigDict, Field
class ORMModel(BaseModel):
model_config = ConfigDict(from_attributes=True)
class KnowledgeOut(ORMModel):
id: int
knowledge_id: int
name: str
mastery: int
position: int
status: str = ""
domain: str = ""
category: str = ""
class KnowledgeResourceOut(BaseModel):
id: int
kind: str
title: str
url: str
source_name: str = ""
class KnowledgeRelatedOut(BaseModel):
id: int
name: str
relation_type: str
direction: str = "out" # out: 当前节点指向对方;in: 对方指向当前节点
mastery: int | None = None
class KnowledgeChapterOut(BaseModel):
chapter_id: int
chapter_name: str
textbook_id: int
textbook_name: str
question_count: int = 0
# 电子书定位:has_ebook 为假时前端不显示「读教材」入口
ebook_page: int = 0
has_ebook: bool = False
class KnowledgeDetailOut(BaseModel):
id: int
name: str
domain: str
category: str
description: str
mastery: int
status: str
related_knowledge_count: int
chapter_count: int
question_count: int
related: list[KnowledgeRelatedOut] = []
chapters: list[KnowledgeChapterOut] = []
resources: list[KnowledgeResourceOut] = []
class KnowledgeResourceIn(BaseModel):
kind: str = "video"
title: str = ""
url: str = ""
textbook_id: int | None = None
course_id: int | None = None
class QuestionOut(BaseModel):
id: int
stem: str
options: list[str]
knowledge_name: str
class PracticeSessionOut(BaseModel):
mode: str
title: str
chapter_id: int | None = None
questions: list[QuestionOut]
class AnswerIn(BaseModel):
question_id: int
selected: int
class SubmitIn(BaseModel):
mode: str
chapter_id: int | None = None
answers: list[AnswerIn] = Field(min_length=1)
class PerQuestionOut(BaseModel):
question_id: int
correct: bool
selected: int
correct_index: int
explanation: str
class SubmitResultOut(BaseModel):
mode: str
title: str
total: int
score: int
accuracy: float
per_question: list[PerQuestionOut]
knowledge: list[KnowledgeOut]
class ErrorOut(BaseModel):
id: int
source_label: str
knowledge_name: str
diagnosis: str
status: str
created_on: date
attempt_id: int | None = None
question_id: int | None = None
stem: str = ""
options: list[str] = []
selected: int | None = None
correct_index: int | None = None
explanation: str = ""
knowledge_names: list[str] = []
class RecordOut(BaseModel):
id: int
mode: str
title: str
total: int
correct: int
happened_on: date
class AttemptItemOut(BaseModel):
question_index: int
question_id: int | None
stem: str
options: list[str]
selected: int | None
correct_index: int
correct: bool
knowledge_name: str
explanation: str
knowledge_names: list[str] = []
class RecordDetailOut(RecordOut):
items: list[AttemptItemOut] = []
class DailyOut(BaseModel):
today: date
completed: bool
streak: int
chapter_id: int | None = None
chapter_name: str = ""
book_name: str = ""
# 今日章节对应的电子书定位,没有电子书时 has_ebook 为假
textbook_id: int | None = None
ebook_page: int = 0
has_ebook: bool = False
rating: str = ""
knowledge_names: list[str] = []
reason: str = ""
class DailyCompleteIn(BaseModel):
chapter_id: int
rating: str = ""
class MetricItem(BaseModel):
label: str
value: str
class PlanOut(BaseModel):
notice: str
focus_names: list[str]
steps: list[str]
class OverviewOut(BaseModel):
metrics: list[MetricItem]
knowledge: list[KnowledgeOut]
plan: PlanOut
class DiagnosisOut(BaseModel):
name: str
mastery: int
error_count: int
action: str
class AnalyticsOut(BaseModel):
metrics: list[MetricItem]
diagnosis: list[DiagnosisOut]
class ImportPayload(BaseModel):
version: int = 1
knowledge: list[dict[str, Any]] = []
records: list[Any] = []
errors: list[Any] = []
daily_completions: list[date] = []
class ImportResult(BaseModel):
imported: bool
knowledge: int
records: int
errors: int
items: int = 0
message: str
class ChapterOut(ORMModel):
id: int
textbook_id: int
name: str
position: int
summary: str = ""
ebook_page: int = 0
question_count: int = 0
knowledge_names: list[str] = []
knowledge_ids: list[int] = []
class EbookProgressOut(ORMModel):
textbook_id: int
locator: str = ""
page: int = 0
total: int = 0
position: float = 0.0
updated_at: datetime | None = None
class EbookProgressIn(BaseModel):
locator: str = Field(default="", max_length=512)
page: int = 0
total: int = 0
position: float = 0.0
class TextbookOut(ORMModel):
id: int
name: str
author: str
publisher: str
isbn: str
edition: str
grade: str
link: str
description: str
position: int
chapters: list[ChapterOut] = []
# 在线阅读:文件本身走 /api/textbooks/{id}/ebook/file/{file}
ebook_file: str = ""
ebook_format: str = ""
ebook_name: str = ""
ebook_size: int = 0
ebook_pages: int = 0
ebook_uploaded_at: datetime | None = None
ebook_progress: EbookProgressOut | None = None
class CourseOut(ORMModel):
id: int
name: str
provider: str
url: str
grade: str
description: str
position: int
class QuestionBankItemOut(BaseModel):
id: int
chapter_id: int | None
chapter_name: str = ""
textbook_name: str = ""
knowledge_name: str
stem: str
options: list[str]
correct_index: int
explanation: str
difficulty: int
is_generated: bool
used_count: int
knowledge_names: list[str] = []
knowledge_ids: list[int] = []
class QuestionUpdate(BaseModel):
stem: str = Field(min_length=1)
options: list[str] = Field(min_length=4, max_length=4)
correct_index: int = Field(ge=0, le=3)
explanation: str = ""
knowledge_name: str = Field(min_length=1, max_length=64)
difficulty: int = Field(default=1, ge=1, le=3)
chapter_id: int | None = None
knowledge_names: list[str] = []
knowledge_ids: list[int] = []
class GenerateIn(BaseModel):
chapter_id: int
count: int = Field(default=3, ge=1, le=10)
difficulty: int = Field(default=2, ge=1, le=3)
knowledge_name: str = ""
instructions: str = ""
channel_id: int | None = None
knowledge_names: list[str] = []
knowledge_ids: list[int] = []
need_figure: bool = False
class LlmProviderOut(BaseModel):
id: str
label: str
base_url: str
model: str
class LlmChannelOut(BaseModel):
id: int
name: str
provider: str
provider_label: str
base_url: str
model: str
temperature: float
max_tokens: int | None = None
has_api_key: bool
api_key_preview: str
is_default: bool
class LlmSettingsOut(BaseModel):
channels: list[LlmChannelOut] = []
providers: list[LlmProviderOut]
class LlmChannelCreate(BaseModel):
name: str = Field(min_length=1, max_length=128)
provider: str = "openai"
base_url: str = ""
model: str = ""
temperature: float = Field(default=0.3, ge=0.0, le=2.0)
max_tokens: int | None = Field(default=None, ge=64, le=12000)
api_key: str | None = None
class LlmChannelUpdate(BaseModel):
name: str | None = Field(default=None, min_length=1, max_length=128)
provider: str | None = None
base_url: str | None = None
model: str | None = None
temperature: float | None = Field(default=None, ge=0.0, le=2.0)
max_tokens: int | None = Field(default=None, ge=64, le=12000)
clear_max_tokens: bool = False
api_key: str | None = None
clear_api_key: bool = False
class LlmTestResult(BaseModel):
ok: bool
message: str
class LlmTaskCreate(BaseModel):
kind: str
params: dict[str, Any] = {}
class LlmTaskOut(BaseModel):
id: int
kind: str
status: str
progress: int
message: str
result: dict[str, Any] = {}
class LoginIn(BaseModel):
username: str
password: str
class TokenOut(BaseModel):
access_token: str
token_type: str = "bearer"
role: str
display_name: str
class ChangePasswordIn(BaseModel):
old_password: str
new_password: str = Field(min_length=6, max_length=128)
class RoleOut(ORMModel):
id: int
code: str
name: str
description: str
class UserOut(BaseModel):
id: int
username: str
display_name: str
active: bool
roles: list[RoleOut] = []
created_at: datetime | None = None
class UserCreate(BaseModel):
username: str = Field(min_length=2, max_length=64, pattern=r"^[A-Za-z0-9_.-]+$")
display_name: str = Field(default="", max_length=64)
password: str = Field(min_length=6, max_length=128)
role_code: str = "student"
class UserUpdate(BaseModel):
display_name: str | None = Field(default=None, max_length=64)
active: bool | None = None
role_code: str | None = None
class ResetPasswordIn(BaseModel):
password: str = Field(min_length=6, max_length=128)
class TextbookCreate(BaseModel):
name: str = Field(min_length=1, max_length=128)
author: str = ""
publisher: str = ""
isbn: str = ""
edition: str = ""
grade: str = ""
link: str = ""
description: str = ""
class TextbookUpdate(BaseModel):
name: str | None = Field(default=None, max_length=128)
author: str | None = None
publisher: str | None = None
isbn: str | None = None
edition: str | None = None
grade: str | None = None
link: str | None = None
description: str | None = None
class CourseCreate(BaseModel):
name: str = Field(min_length=1, max_length=128)
provider: str = ""
url: str = ""
grade: str = ""
description: str = ""
class CourseUpdate(BaseModel):
name: str | None = Field(default=None, max_length=128)
provider: str | None = None
url: str | None = None
grade: str | None = None
description: str | None = None
class ChapterCreate(BaseModel):
name: str = Field(min_length=1, max_length=128)
summary: str = ""
knowledge_names: list[str] = []
knowledge_ids: list[int] = []
class ChapterUpdate(BaseModel):
name: str | None = Field(default=None, max_length=128)
summary: str | None = None
ebook_page: int | None = None
knowledge_names: list[str] | None = None
knowledge_ids: list[int] | None = None
class ChaptersGenerateIn(BaseModel):
count: int | None = Field(default=None, ge=1, le=12)
channel_id: int | None = None
instructions: str = ""
knowledge_ids: list[int] = []
class ChaptersGenerateOut(BaseModel):
created: list[ChapterOut] = []
skipped_duplicates: list[str] = []
message: str = ""
class ChaptersOrganizeIn(BaseModel):
reference_text: str = Field(min_length=1)
channel_id: int | None = None
class ProfileUpdate(BaseModel):
display_name: str = Field(min_length=1, max_length=64)
class KnowledgeGraphNode(BaseModel):
id: int
name: str
domain: str
category: str
description: str
chapter_count: int = 0
question_count: int = 0
resources: list[KnowledgeResourceOut] = []
class KnowledgeGraphEdge(BaseModel):
source: int
target: int
relation_type: str
class KnowledgeGraphOut(BaseModel):
nodes: list[KnowledgeGraphNode] = []
edges: list[KnowledgeGraphEdge] = []
class KnowledgeNodeCreate(BaseModel):
name: str = Field(min_length=1, max_length=64)
domain: str = "初等"
category: str = ""
description: str = ""
class KnowledgeNodeUpdate(BaseModel):
name: str | None = Field(default=None, min_length=1, max_length=64)
domain: str | None = None
category: str | None = None
description: str | None = None
class KnowledgeRelationIn(BaseModel):
source_id: int
target_id: int
relation_type: str = "前置"