Files
piano-plan/docs/MODELS.md
T

362 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 数据模型说明
## 概述
本系统使用 SQLite 数据库,ORM 框架为 Flask-SQLAlchemy。
**数据库文件**: `data/piano_plans.db`
---
## 数据表
### 0. Problem (问题定义)
系统预定义的15种常见钢琴学习问题。
| 字段 | 类型 | 说明 |
|------|------|------|
| id | Integer | 主键,自增 |
| code | String(50) | 问题编号,如 "05_掌关节支撑差" |
| name | String(100) | 问题名称,如 "掌关节支撑差" |
| category | String(20) | 分类:技术类/认知类/节奏类/习惯类/综合类 |
| created_at | DateTime | 创建时间 |
> ⚠️ 问题数据已从文件系统迁移到数据库。student_problems 表通过 `problem_id` 外键关联到此表。
---
### 1. User (用户)
系统用户,用于登录认证和权限管理。
| 字段 | 类型 | 说明 |
|------|------|------|
| id | Integer | 主键,自增 |
| username | String(50) | 用户名,唯一,必填 |
| password_hash | String(200) | 密码哈希值 |
| role | String(20) | 角色:admin/user,默认 user |
| created_at | DateTime | 创建时间 |
**角色说明**:
- `admin`: 管理员,拥有所有权限
- `user`: 普通用户,受限权限
**密码规则**: 8位以上,包含大小写字母、数字和特殊字符
---
### 2. Student (学员)
| 字段 | 类型 | 说明 |
|------|------|------|
| id | Integer | 主键,自增 |
| name | String(100) | 学员姓名,必填 |
| phone | String(20) | 手机号,可选 |
| wechat_nickname | String(100) | 微信昵称,可选 |
| practice_time | String(20) | 每日练习时间,默认"30分钟" |
| notes | Text | 备注信息,可选 |
| class_id | Integer | 外键,关联 Class,可选 |
| created_at | DateTime | 创建时间 |
**练习时间选项**: `15分钟`, `30分钟`, `45分钟`, `60分钟`, `90分钟`, `120分钟`, `150分钟以上`
**关系**:
- `problems`: 与 StudentProblem 一对多
- `plans`: 与 PracticePlan 一对多
- `class`: 与 Class 多对一
---
### 3. StudentProblem (学员问题记录)
| 字段 | 类型 | 说明 |
|------|------|------|
| id | Integer | 主键,自增 |
| student_id | Integer | 外键,关联 Student |
| problem_id | Integer | 外键,关联 Problem.id |
| severity | String(10) | 严重程度:轻微/中等/严重 |
| level | String(20) | 级别:启蒙/入门/进阶/熟练/精通 |
| created_at | DateTime | 创建时间 |
> ⚠️ `problem_id` 现为数字外键,关联 `Problem.id`。通过 `student_problem.problem` 关系获取问题名称。
---
### 4. Class (班级)
| 字段 | 类型 | 说明 |
|------|------|------|
| id | Integer | 主键,自增 |
| name | String(100) | 班级名称,必填 |
| level | String(20) | 班级级别:启蒙/入门/进阶/熟练/精通 |
| description | Text | 班级描述,可选 |
| teacher_id | Integer | 外键,关联 User(班主任) |
| active | Boolean | 是否进行中,默认 true |
| created_at | DateTime | 创建时间 |
**关系**:
- `students`: 与 Student 一对多
- `teacher`: 与 User 多对一(班主任)
---
### 5. PracticePlan (练习方案)
| 字段 | 类型 | 说明 |
|------|------|------|
| id | Integer | 主键,自增 |
| student_id | Integer | 外键,关联 Student |
| template_id | Integer | 外键,关联 TemplateAI提示词模板) |
| is_typical | Boolean | 是否为典型方案 |
| content | Text | 方案内容(JSON格式) |
| created_by | Integer | 外键,关联 User(创建人) |
| created_at | DateTime | 创建时间 |
| updated_by | Integer | 外键,关联 User(更新人,仅编辑时设置) |
| updated_at | DateTime | 更新时间(仅编辑时设置) |
**审计字段说明**
- `created_by`:创建时设置
- `updated_by``updated_at`:仅在编辑更新时设置,初次创建时为空
**content 字段结构**:
```json
{
"student_name": "张三",
"practice_time": "30分钟",
"total_daily_minutes": 30,
"problems": [
{
"name": "手小",
"severity": "中等",
"level": "入门",
"focus": {"basic": 15, "tech": 10, "piece": 20}
}
],
"ai_report": "## 个性化练习方案报告\n\n..."
}
```
---
## ER 关系图
```
┌─────────────┐ ┌──────────────────┐ ┌───────────────┐
│ User │ │ Student │ │ Class │
├─────────────┤ ├──────────────────┤ ├───────────────┤
│ id │ │ id │◄──────│ id │
│ username │ │ name │ │ name │
│ password │ │ phone │ │ description │
│ role │ │ wechat_nickname │ │ created_at │
│ created_at │ │ practice_time │ └───────────────┘
└─────────────┘ │ notes │
│ class_id │──┐
│ created_at │ │
└──────────────────┘ │
│ │
▼ │
┌──────────────────┐ │
│ StudentProblem │ │
├──────────────────┤ │
│ id │ │
│ student_id ─────┘ │
│ problem_id │◄─┼──► Problem
│ severity │
│ level │
│ created_at │
└──────────────────┘
┌──────────────────┐
│ PracticePlan │
├──────────────────┤
│ id │
│ student_id ─────┼──► Student
│ content │
│ created_at │
└──────────────────┘
```
---
## 常用查询
### 查询用户角色
```python
from app.models import User
user = User.query.filter_by(username="admin").first()
print(user.role) # "admin" or "user"
```
### 查询学员及其问题
```python
student = Student.query.get(1)
for sp in student.problems:
print(sp.problem.name, sp.problem.code, sp.severity, sp.level)
```
### 查询班级及其学员
```python
cls = Class.query.get(1)
for student in cls.students:
print(student.name)
```
### 查询学员及其方案
```python
student = Student.query.get(1)
for plan in student.plans:
print(plan.created_at)
```
### 获取最新方案
```python
from app.models import PracticePlan
latest_plan = PracticePlan.query.order_by(
PracticePlan.created_at.desc()
).first()
```
---
## 数据库管理
### 初始化数据库
首次运行应用时会自动创建所有表:
```python
from app import create_app
from app.models import db
app = create_app()
with app.app_context():
db.create_all()
```
### 备份数据库
```bash
# 停止服务后复制数据库文件
copy data\piano_plans.db backup\piano_plans_backup.db
```
### 查看数据库内容
```bash
# 使用 SQLite 命令行
sqlite3 data\piano_plans.db
# 查看表
.schema
# 查询数据
SELECT * FROM users;
```
---
## 数据字典
### User.role 取值
| 值 | 说明 |
|----|------|
| admin | 管理员,拥有所有权限 |
| user | 普通用户,受限权限 |
### StudentProblem.severity 取值
| 值 | 说明 |
|----|------|
| 轻微 | 问题不明显,日常练习即可改善 |
| 中等 | 需要针对性练习,建议重点关注 |
| 严重 | 需要系统训练,建议额外辅导 |
### StudentProblem.level 取值
| 值 | 说明 |
|----|------|
| 启蒙 | 初期入门阶段 |
| 入门 | 基础学习阶段 |
| 进阶 | 技能提升阶段 |
| 熟练 | 技术熟练阶段 |
| 精通 | 高级演奏阶段 |
### Student.practice_time 取值
| 值 | 说明 |
|----|------|
| 15分钟 | 初级学员 |
| 30分钟 | 进阶学员 |
| 45分钟 | 中级学员 |
| 60分钟 | 中高级学员 |
| 90分钟 | 高级学员 |
| 120分钟 | 专业学员 |
| 150分钟以上 | 竞技水平 |
---
## 目标管理模块
### Goal (目标表)
| 字段 | 类型 | 说明 |
|------|------|------|
| id | Integer | 主键 |
| name | String(100) | 目标名称 |
| content | Text | 目标内容(Markdown |
| level | String(20) | 级别:启蒙/入门/进阶/熟练/精通 |
| category | String(20) | 分类:综合/乐理相关/演奏能力/其他 |
| created_at | DateTime | 创建时间 |
| updated_at | DateTime | 更新时间 |
### GoalRelation (目标关联表)
自关联多对多关系,用于表示目标之间的父子关系(DAG)。
| 字段 | 类型 | 说明 |
|------|------|------|
| parent_goal_id | Integer | 父目标ID,外键 |
| child_goal_id | Integer | 子目标ID,外键 |
**关系类型**:自引用多对多(一个目标可以有多个子目标,也可以有多个父目标)
**约束**:通过应用层循环检测防止形成循环
### StudentGoal (学员目标记录表)
| 字段 | 类型 | 说明 |
|------|------|------|
| id | Integer | 主键 |
| student_id | Integer | 学员ID,外键 |
| goal_id | Integer | 目标ID,外键 |
| goal_content | Text | 目标内容副本(冗余存储,避免目标被删除后丢失) |
| start_date | DateTime | 开始日期 |
| assessment_date | DateTime | 评估日期 |
| mastery_level | Integer | 掌握程度 1-5(评估时填写) |
| achievement_date | DateTime | 达成日期 |
| comment | Text | 评语 |
| created_at | DateTime | 创建时间 |
**状态计算逻辑**
- `status``start_date``assessment_date` 自动计算,不存储
- 早于 `start_date` → 未开始
- `start_date``assessment_date` 之间 → 进行中
- 晚于 `assessment_date` → 已结束
**排序规则**:按状态(进行中→未开始→已结束),再按评估日期倒序
**关系**
- 一个学员可以分配多个目标
- 一个目标可以分配给多个学员
**操作入口**
- "调整目标":修改开始/评估日期,移除目标
- "评估目标":填写掌握程度、达成日期、评语