Files
wordloop/backend/schemas.py
T
john e76fa586f1 Add word-book practice, iOS app shell, and fix embedded WebView blank screen.
Ship dual-track learning (daily accumulation vs textbook),沪教/商务词书 APIs and UI, native iOS wrapper with bundled H5, and production book import on deploy.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 21:09:27 +08:00

385 lines
8.2 KiB
Python

from typing import Optional
from pydantic import BaseModel, Field, computed_field
# Auth
class UserRegister(BaseModel):
username: str = Field(min_length=2, max_length=50)
password: str = Field(min_length=6, max_length=100)
class UserLogin(BaseModel):
username: str
password: str
remember: bool = True
class TokenResponse(BaseModel):
access_token: str
token_type: str = "bearer"
class UserOut(BaseModel):
id: int
username: str
created_at: str
class Config:
from_attributes = True
class UserUpdate(BaseModel):
username: Optional[str] = Field(None, min_length=2, max_length=50)
current_password: Optional[str] = Field(None, min_length=6, max_length=100)
new_password: Optional[str] = Field(None, min_length=6, max_length=100)
# Translate
class TranslateRequest(BaseModel):
text: str = Field(min_length=1)
class TranslateResponse(BaseModel):
source_text: str
target_text: str
source_lang: str
target_lang: str
phonetic: Optional[str] = None
example_en: Optional[str] = None
example_cn: Optional[str] = None
found: bool = True
# Words
class WordCreate(BaseModel):
source_text: str
target_text: str
source_lang: str
target_lang: str
phonetic: Optional[str] = None
example_en: Optional[str] = None
example_cn: Optional[str] = None
class WordUpdate(BaseModel):
status: Optional[str] = None
class WordOut(BaseModel):
id: int
user_id: int
book_id: int = 0
book_entry_id: Optional[int] = None
source_text: str
target_text: str
source_lang: str
target_lang: str
phonetic: Optional[str] = None
example_en: Optional[str] = None
example_cn: Optional[str] = None
status: str
correct_count: int
wrong_count: int
consecutive_correct_count: int
mastery_score: int
train_count: int = 0
total_train_seconds: int = 0
review_due_date: Optional[str] = None
last_reviewed_at: Optional[str] = None
created_at: str
@computed_field # type: ignore[prop-decorator]
@property
def entered_at(self) -> str:
"""词库进入时间(与 created_at 一致)。"""
return self.created_at
class Config:
from_attributes = True
class MemoryCurvePoint(BaseModel):
day_index: int
date: str
forgetting: float
mastery: float
risk: float
class MemoryFutureRiskPoint(BaseModel):
day_offset: int
date: str
risk: float
class MemoryGraphNode(BaseModel):
id: str
label: str
zh: str
status: str
mastery: int
entered_at: str
size: int
class MemoryGraphLink(BaseModel):
source: str
target: str
kind: str
strength: float
class MemoryGraph(BaseModel):
nodes: list[MemoryGraphNode]
links: list[MemoryGraphLink]
class MemoryWordSummary(BaseModel):
id: int
en: str
zh: str
status: str
mastery_score: int
correct_count: int = 0
wrong_count: int = 0
train_count: int = 0
total_train_seconds: int = 0
entered_at: str
retention_now: float
risk_7d: float
class WordMemoryCurvePoint(BaseModel):
date: str
datetime: str
forgetting: float
mastery: float
risk: float
wrong_count: int
train_count: int
train_seconds: int
is_correct: Optional[bool] = None
class WordMemoryDetailResponse(BaseModel):
word_id: int
en: str
zh: str
correct_count: int
wrong_count: int
train_count: int
total_train_seconds: int
curve_points: list[WordMemoryCurvePoint]
future_risk: list[MemoryFutureRiskPoint]
class MemoryVisualizationResponse(BaseModel):
curve_points: list[MemoryCurvePoint]
future_risk: list[MemoryFutureRiskPoint]
words: list[MemoryWordSummary]
graph: MemoryGraph
# Quiz
class QuizOption(BaseModel):
label: str
text: str
class QuizQuestion(BaseModel):
word_id: int
question_type: str
prompt: str
options: list[QuizOption] = []
correct_answer: str
phonetic: Optional[str] = None
class DailyQuizResponse(BaseModel):
questions: list[QuizQuestion]
total: int
track: str = "accumulation"
book_id: Optional[int] = None
class QuizAnswerRequest(BaseModel):
word_id: int
question_type: str
user_answer: str
correct_answer: str
duration_seconds: Optional[int] = Field(None, ge=0, le=3600)
class QuizAnswerResponse(BaseModel):
is_correct: bool
correct_answer: str
word: WordOut
class QuizStatsResponse(BaseModel):
total_words: int
new_count: int
learning_count: int
mastered_count: int
weak_count: int
today_quiz_count: int
today_correct_count: int
today_accuracy: float
daily_target: int
today_completed: int
streak_days: int
track: str = "accumulation"
book_id: Optional[int] = None
# Word books
class WordBookOut(BaseModel):
id: int
slug: str
title: str
description: Optional[str] = None
level: str
word_count: int
unit_count: int
sort_order: int
daily_target: int
master_required_count: int
weak_wrong_threshold: int
learn_mode: str
class Config:
from_attributes = True
class WordBookUnitOut(BaseModel):
unit: int
title: Optional[str] = None
word_count: int
class WordBookDetailOut(WordBookOut):
units: list[WordBookUnitOut] = []
class WordBookProgressOut(BaseModel):
book_id: int
total: int
introduced: int
mastered: int
learning: int
locked: int
class BookPracticeSettingsOut(BaseModel):
book_id: int
daily_target: int
master_required_count: int
weak_wrong_threshold: int
class ActiveBookOut(BaseModel):
book: Optional[WordBookOut] = None
progress: Optional[WordBookProgressOut] = None
settings: Optional[BookPracticeSettingsOut] = None
class SetActiveBookRequest(BaseModel):
book_id: int = Field(ge=1)
class BookPracticeSettingsUpdate(BaseModel):
book_id: Optional[int] = Field(None, ge=1)
daily_target: Optional[int] = Field(None, ge=5, le=50)
master_required_count: Optional[int] = Field(None, ge=1, le=10)
weak_wrong_threshold: Optional[int] = Field(None, ge=1, le=10)
# Settings
class SettingsOut(BaseModel):
daily_target: int
master_required_count: int
weak_wrong_threshold: int
class Config:
from_attributes = True
class SettingsUpdate(BaseModel):
daily_target: Optional[int] = Field(None, ge=1, le=100)
master_required_count: Optional[int] = Field(None, ge=1, le=20)
weak_wrong_threshold: Optional[int] = Field(None, ge=1, le=20)
# Memory coach (Q/K/V dialogue)
class MemoryToken(BaseModel):
role: str
key: str
label: str
value: str
revealed: bool = False
class CoachWordBrief(BaseModel):
word_id: int
zh: str
phonetic: Optional[str] = None
retention_now: float
mastery_score: int
status: str
class CoachSessionResponse(BaseModel):
words: list[CoachWordBrief]
total: int
class CoachTurnRequest(BaseModel):
word_id: int
stage: str = "intro"
user_message: str = ""
hints_used: int = Field(0, ge=0, le=20)
duration_seconds: Optional[int] = Field(None, ge=0, le=3600)
class MemoryTransformerCurvePoint(BaseModel):
hours_ahead: float
recall_percent: float
forgetting_percent: float
class MemoryTransformerAttention(BaseModel):
index: int
label: str
weight: float
class MemoryTransformerPredictResponse(BaseModel):
word_id: int
en: str
recall_now_percent: float
formula_recall_percent: float
model_recall_percent: float
blend_weight: float
event_count: int
model_trained: bool
recommended_review_days: int
half_life_hours: Optional[float] = None
curve: list[MemoryTransformerCurvePoint]
attention_hint: list[MemoryTransformerAttention]
class CoachTurnResponse(BaseModel):
assistant_messages: list[str]
tokens: list[MemoryToken] = []
stage: str
expect_input: bool
prompt_zh: str = ""
prompt_phonetic: Optional[str] = None
input_hint: str = "请输入对应的英文单词"
is_correct: Optional[bool] = None
quiz_recorded: bool = False
word_complete: bool = False
hints_used: int = 0
target_en: Optional[str] = None
target_zh: Optional[str] = None