# 比赛评委存储设计说明

## 📋 设计决策

### 问题：是否需要专门的评委表？

**结论：需要创建 `ContestJudge` 关联表，但不需要单独的评委信息表。**

## 🎯 设计分析

### 1. 为什么需要 `ContestJudge` 关联表？

#### 1.1 业务需求

1. **比赛与评委的多对多关系**
   - 一个比赛可以有多个评委
   - 一个评委可以评审多个比赛
   - 需要管理这种多对多关系

2. **评委管理功能**
   - 查询某个比赛的所有评委列表
   - 批量添加/删除比赛的评委
   - 管理评委在特定比赛中的特殊信息

3. **评委特殊属性**
   - 评审专业领域（specialty）：如"创意设计"、"技术实现"等
   - 评审权重（weight）：用于加权平均计算最终得分
   - 评委说明（description）：在该比赛中的特殊说明

#### 1.2 当前设计的不足

**之前的设计**：
- 评委信息只存储在 `ContestWorkJudgeAssignment` 表中
- 无法直接查询某个比赛有哪些评委
- 无法批量管理比赛的评委列表
- 无法存储评委在特定比赛中的特殊信息

**问题场景**：
```typescript
// ❌ 无法直接查询比赛的评委列表
// 需要从作品分配表中去重查询，效率低且不准确

// ✅ 有了 ContestJudge 表后
const judges = await prisma.contestJudge.findMany({
  where: { contestId: contestId }
});
```

### 2. 为什么不需要单独的评委信息表？

#### 2.1 评委就是用户

- 评委本身就是系统中的 `User`
- 基本信息（姓名、账号等）存储在 `User` 表
- 如果是教师，详细信息存储在 `Teacher` 表
- 如果是外部专家，可以创建普通 `User` 账号

#### 2.2 统一用户体系

- 系统采用统一的用户体系
- 通过角色和权限区分用户身份
- 评委通过角色（如 `judge`）和权限（如 `review:score`）控制

## 📊 数据模型设计

### 1. 表结构

```prisma
model ContestJudge {
  id          Int       @id @default(autoincrement())
  contestId   Int       @map("contest_id") /// 比赛id
  judgeId     Int       @map("judge_id") /// 评委用户id
  specialty   String? /// 评审专业领域（可选）
  weight      Decimal?  @db.Decimal(3, 2) /// 评审权重（可选）
  description String?   @db.Text /// 评委在该比赛中的说明
  creator     Int? /// 创建人ID
  modifier    Int? /// 修改人ID
  createTime  DateTime  @default(now()) @map("create_time")
  modifyTime  DateTime  @updatedAt @map("modify_time")
  validState  Int       @default(1) @map("valid_state")

  contest     Contest   @relation(fields: [contestId], references: [id])
  judge       User      @relation(fields: [judgeId], references: [id])
  
  @@unique([contestId, judgeId])
  @@index([contestId])
  @@index([judgeId])
}
```

### 2. 数据关系

```
Contest (比赛)
  ↓ (1:N)
ContestJudge (比赛评委关联)
  ↓ (N:1)
User (用户/评委)
  ↓ (1:1, 可选)
Teacher (教师信息，如果是教师评委)
```

### 3. 与其他表的关系

```
ContestJudge (比赛评委)
  ↓
ContestWorkJudgeAssignment (作品分配)
  ↓
ContestWorkScore (作品评分)
```

**关系说明**：
- `ContestJudge`：定义哪些评委可以评审某个比赛
- `ContestWorkJudgeAssignment`：将具体作品分配给评委
- `ContestWorkScore`：记录评委的评分结果

## 🔄 业务流程

### 1. 添加评委到比赛

```typescript
// 1. 创建比赛评委关联
const contestJudge = await prisma.contestJudge.create({
  data: {
    contestId: contestId,
    judgeId: userId,
    specialty: "创意设计",
    weight: 1.2, // 权重1.2倍
    description: "专业设计领域评委"
  }
});
```

### 2. 查询比赛的评委列表

```typescript
// 查询某个比赛的所有评委
const judges = await prisma.contestJudge.findMany({
  where: { 
    contestId: contestId,
    validState: 1 
  },
  include: {
    judge: {
      include: {
        teacher: true // 如果是教师，获取教师信息
      }
    }
  }
});
```

### 3. 分配作品给评委

```typescript
// 分配作品时，验证评委是否属于该比赛
const contestJudge = await prisma.contestJudge.findFirst({
  where: {
    contestId: contestId,
    judgeId: judgeId,
    validState: 1
  }
});

if (!contestJudge) {
  throw new Error('该评委不属于此比赛');
}

// 创建作品分配记录
const assignment = await prisma.contestWorkJudgeAssignment.create({
  data: {
    contestId: contestId,
    workId: workId,
    judgeId: judgeId
  }
});
```

### 4. 计算加权平均分

```typescript
// 获取所有评委的评分和权重
const scores = await prisma.contestWorkScore.findMany({
  where: { workId: workId },
  include: {
    judge: {
      include: {
        contestJudges: {
          where: { contestId: contestId }
        }
      }
    }
  }
});

// 计算加权平均分
let totalWeightedScore = 0;
let totalWeight = 0;

for (const score of scores) {
  const contestJudge = score.judge.contestJudges[0];
  const weight = contestJudge?.weight || 1.0;
  totalWeightedScore += score.totalScore * weight;
  totalWeight += weight;
}

const finalScore = totalWeightedScore / totalWeight;
```

## ✅ 设计优势

### 1. 清晰的业务逻辑

- **比赛评委管理**：通过 `ContestJudge` 表统一管理
- **作品分配**：通过 `ContestWorkJudgeAssignment` 表管理
- **评分记录**：通过 `ContestWorkScore` 表记录

### 2. 灵活的扩展性

- 支持评委专业领域分类
- 支持评审权重设置
- 支持评委说明信息

### 3. 高效的查询

- 直接查询比赛的评委列表
- 支持评委维度的统计分析
- 支持权重计算

### 4. 数据一致性

- 通过外键约束保证数据完整性
- 删除比赛时，级联删除评委关联
- 删除用户时，级联删除评委关联

## 📝 使用建议

### 1. 评委添加流程

```
1. 确保用户已创建（User 表）
2. 为用户分配评委角色和权限
3. 创建 ContestJudge 记录，关联比赛和用户
4. 可选：设置专业领域、权重等信息
```

### 2. 评委验证

在分配作品给评委时，应该验证：
- 该评委是否属于该比赛（查询 `ContestJudge` 表）
- 该评委是否有效（`validState = 1`）

### 3. 权限控制

- 只有比赛管理员可以添加/删除评委
- 评委只能查看和评分分配给自己的作品
- 通过 RBAC 权限系统控制访问

## 🔍 总结

**评委存储方案**：
- ✅ **需要** `ContestJudge` 关联表：管理比赛与评委的多对多关系
- ❌ **不需要** 单独的评委信息表：评委信息通过 `User` 和 `Teacher` 表存储

**核心设计原则**：
1. 评委就是用户，统一用户体系
2. 通过关联表管理比赛与评委的关系
3. 支持评委在特定比赛中的特殊属性
4. 保证数据一致性和查询效率

