# Phenotyping 模块专业数据录入需求文档 V2

## 1. 文档目的

本文档用于指导 Phenotyping 表型模块的前端页面、后端接口、字段校验、数据导入和测试验收设计。本文档不只描述表关系，而是从真实育种表型采集业务出发，解释每个对象和字段的业务意义、录入方式、控件建议、校验规则和上下游影响。

## 2. 模块定位

Phenotyping 模块用于管理田间、温室、实验室或高通量设备采集到的表型数据。它的核心不是“录一张 observation 表”，而是完整描述：

```text
在哪个 study 里
对哪个 observation_unit
按照哪个 observation_variable
在什么时间、由谁、用什么方法
采集到了什么 value
并能关联事件、图片、坐标和上下文
```

## 3. 核心业务概念

| 概念                         | 业务含义                                                   | 关键表                                            |
| ---------------------------- | ---------------------------------------------------------- | ------------------------------------------------- |
| Ontology 本体                | 性状、方法、标尺、变量的术语来源，可以是本地本体或公开本体 | `ontology`                                        |
| Trait 性状                   | 测什么。例如株高、穗长、病害等级、籽粒颜色                 | `trait`                                           |
| Method 方法                  | 怎么测。例如尺测、人工评分、无人机图像分析、实验室检测     | `method`                                          |
| Scale 标尺                   | 用什么单位或尺度表达。例如 cm、kg、0-5 等级、文本、布尔值  | `scale`、`scale_valid_value_category`             |
| ObservationVariable 观测变量 | Trait + Method + Scale 的组合，表示一个可采集指标          | `observation_variable`                            |
| StudyVariable 研究采集指标   | 某个 study 计划采集哪些 observation variable               | `study_variable`                                  |
| ObservationUnit 观测单元     | 被观测对象。通常是 plot、plant、block、field、sample       | `observation_unit`                                |
| Event 事件                   | 管理动作或环境事件，如施肥、灌溉、打药、移栽、极端天气     | `event`、`event_param`、`event_observation_units` |
| Image 图像                   | 田间图片、无人机图片、病害图片、长势图像等                 | `image`、`image_observations`                     |
| Observation 观测值           | 某个观测单元在某个变量上的一次实际测量结果                 | `observation`                                     |

## 4. 推荐业务流程

```text
1. 准备 Core 上下文：crop、program、trial、study、location、season
2. 维护或导入本体 ontology
3. 定义 trait / method / scale
4. 组合 observation_variable
5. 把 observation_variable 绑定到 study_variable，形成本 study 的采集指标清单
6. 根据田间设计生成 observation_unit，例如 block / plot / plant
7. 给 observation_unit 绑定材料来源：germplasm / seed_lot / cross
8. 记录 event，例如播种、移栽、施肥、灌溉、打药、采样、极端天气
9. 通过矩阵方式批量录入 observation
10. 上传 image，并关联 observation_unit 或 observation
11. 做数据质控：缺失值、异常值、重复采集、单位和分类值校验
```

## 5. 推荐页面形态

### 5.1 Study 表型工作台

```text
Study 表型工作台
├─ 试验上下文：study / trial / program / crop / location / season
├─ 指标清单：study_variable
├─ 田间设计：observation_unit 层级、block、plot、plant、row、column
├─ 材料布置：germplasm、seed_lot、cross
├─ 表型矩阵：observation_unit × observation_variable
├─ 田间事件：event + event_param + event_observation_units
├─ 图片证据：image + image_observations
├─ 数据质控：缺失值、异常值、重复值、单位校验、分类值校验
└─ 导入导出：观测单元模板、表型采集模板、图片元数据模板
```

### 5.2 表型矩阵录入

真实采集时不应只提供单条 observation 表单，而应支持矩阵录入：

| observation_unit | germplasm | 株高 cm | 穗长 cm | 倒伏等级 | 备注       |
| ---------------- | --------- | ------: | ------: | -------: | ---------- |
| Plot-001         | 华占      |   112.5 |    24.1 |        1 | 正常       |
| Plot-002         | 抗倒伏A   |    98.3 |    22.8 |        0 | 正常       |
| Plot-003         | F2-001    |   101.6 |    23.0 |        4 | 台风后倒伏 |

保存时，系统按每个非空单元格生成一条 `observation`，每一列对应一个 `observation_variable`。

---

# 6. 字段级专业录入需求

## 6.1 ontology 本体

### 业务说明

`ontology` 表示术语体系来源，用来说明 trait、method、scale、observation_variable 的定义来自哪里。它可以是本地维护的术语库，也可以是公开 Crop Ontology、Trait Ontology 或机构内部标准。

### 字段说明

| 字段               | 业务意义                                  | 录入方式                         | 控件建议        | 校验规则                         |
| ------------------ | ----------------------------------------- | -------------------------------- | --------------- | -------------------------------- |
| `id`               | 本体主键，系统内部唯一标识                | 新增时系统生成；导入时可允许指定 | 隐藏/只读       | 必填、唯一；编辑时不可修改       |
| `auth_user_id`     | 数据所属用户或租户                        | 登录上下文自动写入               | 隐藏            | 不允许前端手填                   |
| `authors`          | 本体作者或维护者                          | 用户录入                         | 文本框          | 可选；多个作者可用分号或数组扩展 |
| `copyright`        | 本体版权说明                              | 用户录入                         | 文本框/多行文本 | 可选                             |
| `description`      | 本体用途说明                              | 用户录入                         | 多行文本        | 可选，建议填写                   |
| `documentationurl` | 本体文档链接                              | 用户录入                         | URL 输入框      | 可选；若填写必须是合法 URL       |
| `licence`          | 本体许可证，例如 CC-BY、MIT、机构内部许可 | 用户录入/选择                    | 文本框/下拉框   | 可选                             |
| `ontology_name`    | 本体名称，例如 Rice Trait Ontology        | 用户录入                         | 文本框          | 必填；同一用户下建议唯一         |
| `version`          | 本体版本，例如 2024.1、v2.0               | 用户录入                         | 文本框          | 可选；建议与外部本体版本一致     |

### 录入建议

- 若系统只是内部使用，可先创建一个“默认本体”或“本地表型本体”。
- 若对接 Crop Ontology，建议保存 ontology 名称、版本、文档 URL。
- 本体删除前必须检查是否被 trait、method、scale、observation_variable 引用。

---

## 6.2 trait 性状

### 业务说明

`trait` 描述“测什么”。例如株高、叶长、穗长、倒伏等级、病害严重度、籽粒颜色。专业上 trait 可以拆成 `entity` 和 `attribute`：

```text
Trait = Entity + Attribute
例如：grain colour
entity = grain
attribute = colour
```

### 字段说明

| 字段                | 业务意义                                              | 录入方式             | 控件建议          | 校验规则                                 |
| ------------------- | ----------------------------------------------------- | -------------------- | ----------------- | ---------------------------------------- |
| `id`                | 性状主键，系统内部唯一标识                            | 系统生成；导入可指定 | 隐藏/只读         | 必填、唯一                               |
| `auth_user_id`      | 数据所属用户或租户                                    | 登录上下文自动写入   | 隐藏              | 不允许手填                               |
| `attribute`         | 性状属性，即被观察的特征，如 height、colour、severity | 用户录入/本体选择    | 文本框/本体选择器 | 可选；若填写 PUI，建议同步填写 attribute |
| `attributepui`      | 属性永久唯一标识，通常是 URI                          | 用户录入/本体带出    | 文本框/URL 输入框 | 可选；若填写建议唯一                     |
| `entity`            | 性状实体，即观察对象部位，如 plant、leaf、grain、root | 用户录入/本体选择    | 文本框/本体选择器 | 可选                                     |
| `entitypui`         | 实体永久唯一标识，通常是 URI                          | 用户录入/本体带出    | 文本框/URL 输入框 | 可选；若填写建议唯一                     |
| `main_abbreviation` | 性状主缩写，如 PH、GY、DTH                            | 用户录入             | 文本框            | 可选；同一 ontology 下建议唯一           |
| `status`            | 性状状态，如 recommended、obsolete、legacy            | 用户选择             | 下拉框            | 可选；推荐使用枚举                       |
| `trait_class`       | 性状分类，如 phenology、morphology、disease、quality  | 用户选择/录入        | 下拉框/文本框     | 可选；建议字典化                         |
| `trait_description` | 性状定义和说明                                        | 用户录入             | 多行文本          | 可选，但专业使用强烈建议填写             |
| `trait_name`        | 性状名称，给用户看的名称                              | 用户录入             | 文本框            | 必填；作为下拉展示名称                   |
| `traitpui`          | 性状永久唯一标识                                      | 用户录入/本体带出    | 文本框/URL 输入框 | 可选；若填写必须唯一                     |
| `ontology_id`       | 所属本体                                              | 从 ontology 选择     | 本体选择器        | 可选；必须引用存在的 ontology            |

### 录入建议

- 简单业务可只填：`trait_name`、`trait_description`、`trait_class`。
- 专业数据交换场景建议填写：`entity`、`attribute`、`traitpui`、`ontology_id`。
- 不要把单位写进 trait。比如“株高 cm”不应该作为 trait，正确拆分是 trait=株高，scale=cm。

---

## 6.3 method 方法

### 业务说明

`method` 描述“怎么测”。同一个 trait 用不同方法测，会形成不同 observation_variable。例如：

```text
Trait: plant height
Method 1: tape measure
Method 2: drone image processing
Scale: cm
```

这两个变量不能混为一谈，因为它们的数据来源、误差和可比性不同。

### 字段说明

| 字段           | 业务意义                                                     | 录入方式           | 控件建议          | 校验规则                         |
| -------------- | ------------------------------------------------------------ | ------------------ | ----------------- | -------------------------------- |
| `id`           | 方法主键                                                     | 系统生成           | 隐藏/只读         | 必填、唯一                       |
| `auth_user_id` | 数据所属用户或租户                                           | 登录上下文自动写入 | 隐藏              | 不允许手填                       |
| `description`  | 方法说明，如测量位置、工具、采样规则                         | 用户录入           | 多行文本          | 可选；建议必填                   |
| `formula`      | 如果该方法通过计算得到结果，填写公式                         | 用户录入           | 文本框/公式编辑器 | 可选；需要格式提示               |
| `method_class` | 方法分类，如 measurement、estimation、computed、image_analysis | 用户选择/录入      | 下拉框/文本框     | 可选；建议字典化                 |
| `methodpui`    | 方法永久唯一标识                                             | 用户录入/本体带出  | 文本框/URL 输入框 | 可选；若填写建议唯一             |
| `name`         | 方法名称                                                     | 用户录入           | 文本框            | 必填；同一 ontology 下不建议重复 |
| `reference`    | 方法参考文献、SOP、标准链接                                  | 用户录入           | 文本框/URL 输入框 | 可选；若为 URL 需校验格式        |
| `ontology_id`  | 所属本体                                                     | 从 ontology 选择   | 本体选择器        | 可选；必须引用存在的 ontology    |

### 录入建议

- 方法页面应重点让用户说明“测量步骤”，否则后续数据不可复现。
- 对于人工评分，必须在 description 中说明评分标准，并通过 scale 配置有效分类值。
- 对于高通量图像方法，reference 建议填写算法版本、模型版本或 SOP 链接。

---

## 6.4 scale 标尺

### 业务说明

`scale` 描述“用什么尺度表达结果”。它决定 observation.value 的合法值、单位、数据类型、小数位和分类值。常见类型：

```text
Numerical: 株高 cm、产量 kg/ha
Ordinal: 倒伏等级 0-5、病害等级 1-9
Categorical: 籽粒颜色 red/yellow/white
Text: 描述性备注
Boolean: 是否开花、是否倒伏
Date: 开花日期
```

### 字段说明

| 字段              | 业务意义                                              | 录入方式           | 控件建议          | 校验规则                          |
| ----------------- | ----------------------------------------------------- | ------------------ | ----------------- | --------------------------------- |
| `id`              | 标尺主键                                              | 系统生成           | 隐藏/只读         | 必填、唯一                        |
| `auth_user_id`    | 数据所属用户或租户                                    | 登录上下文自动写入 | 隐藏              | 不允许手填                        |
| `data_type`       | 数据类型，决定 observation.value 的输入控件和校验方式 | 用户选择           | 下拉框            | 必填；使用系统枚举                |
| `decimal_places`  | 小数位数                                              | 用户录入           | 数字输入框        | 数值型可填；必须为非负整数        |
| `scale_name`      | 标尺名称，如 cm、0-5 lodging score、disease score 1-9 | 用户录入           | 文本框            | 必填；同一 ontology 下建议唯一    |
| `scalepui`        | 标尺永久唯一标识                                      | 用户录入/本体带出  | 文本框/URL 输入框 | 可选；若填写建议唯一              |
| `units`           | 单位，如 cm、mm、kg/ha、days                          | 用户选择/录入      | 单位选择器/文本框 | 数值型建议必填；分类型可为空      |
| `valid_value_max` | 最大有效值                                            | 用户录入           | 数字输入框/文本框 | 数值/等级型校验；必须大于等于 min |
| `valid_value_min` | 最小有效值                                            | 用户录入           | 数字输入框/文本框 | 数值/等级型校验；必须小于等于 max |
| `ontology_id`     | 所属本体                                              | 从 ontology 选择   | 本体选择器        | 可选；必须引用存在 ontology       |

### 录入建议

- `data_type` 是动态表单核心字段。前端要根据它渲染 observation.value：
  - Numerical：数字输入框，按 min/max/decimal_places 校验；
  - Ordinal：数字或下拉，通常有 min/max；
  - Categorical：下拉框，选项来自 `scale_valid_value_category`；
  - Text：文本框；
  - Boolean：开关/单选；
  - Date：日期选择器。
- 如果 data_type 是分类型，必须至少配置一个 `scale_valid_value_category`。
- 如果 data_type 是数值型，建议填写单位和有效范围。

---

## 6.5 scale_valid_value_category 分类有效值

### 业务说明

`scale_valid_value_category` 是分类型或等级型 scale 的合法取值表。例如倒伏等级 0-5、病害等级 1-9、颜色 red/yellow/white。

### 字段说明

| 字段       | 业务意义                          | 录入方式                    | 控件建议          | 校验规则                 |
| ---------- | --------------------------------- | --------------------------- | ----------------- | ------------------------ |
| `id`       | 分类值主键                        | 系统生成                    | 隐藏/只读         | 必填、唯一               |
| `label`    | 用户界面展示文案，如 “0 - 不倒伏” | 用户录入                    | 文本框            | 必填                     |
| `value`    | 实际保存值，如 0、1、red          | 用户录入                    | 文本框            | 必填；同一 scale 内唯一  |
| `scale_id` | 所属 scale                        | 从 scale 选择或由详情页带出 | Scale 选择器/隐藏 | 必选；必须引用存在 scale |

### 录入建议

- 在 scale 详情页中以内嵌表格维护。
- 保存 observation 时，如果 scale 是分类型，value 必须在该表中存在。
- label 可以给用户看，value 用于保存和导出，二者可以不同。

---

## 6.6 observation_variable 观测变量

### 业务说明

`observation_variable` 是真正进入采集模板的指标。它由 trait、method、scale 组合而成。

```text
observation_variable = trait + method + scale
例如：株高 + 尺子测量 + cm = 株高-尺测法-cm
```

它不是观测结果，而是“可以被测的一列数据”。

### 字段说明

| 字段                   | 业务意义                                   | 录入方式                 | 控件建议          | 校验规则                                 |
| ---------------------- | ------------------------------------------ | ------------------------ | ----------------- | ---------------------------------------- |
| `id`                   | 观测变量主键                               | 系统生成                 | 隐藏/只读         | 必填、唯一                               |
| `auth_user_id`         | 数据所属用户或租户                         | 登录上下文自动写入       | 隐藏              | 不允许手填                               |
| `default_value`        | 默认观测值，例如默认等级、默认文本         | 用户录入                 | 动态控件          | 可选；按 scale 校验                      |
| `documentationurl`     | 变量说明文档链接                           | 用户录入                 | URL 输入框        | 可选；若填写必须合法 URL                 |
| `growth_stage`         | 建议采集生育期，如 flowering、maturity     | 用户录入/选择            | 下拉框/文本框     | 可选；建议字典化                         |
| `institution`          | 定义该变量的机构                           | 用户录入                 | 文本框            | 可选                                     |
| `language`             | 变量语言，如 zh、en                        | 用户选择                 | 下拉框            | 可选；建议 ISO 639-1                     |
| `scientist`            | 提交或维护该变量的科学家/负责人            | 用户录入/人员选择        | 文本框/选择器     | 可选                                     |
| `status`               | 变量状态，如 recommended、obsolete、legacy | 用户选择                 | 下拉框            | 可选；obsolete 不建议用于新 study        |
| `submission_timestamp` | 变量提交时间                               | 系统默认当前时间，可调整 | 日期时间选择器    | 可选；新增默认当前时间                   |
| `crop_id`              | 适用作物                                   | 从 crop 选择             | 作物选择器        | 可选；如果填写，下游 study 应尽量同 crop |
| `method_id`            | 测定方法                                   | 从 method 选择           | 方法选择器        | 必选                                     |
| `ontology_id`          | 所属本体                                   | 从 ontology 选择         | 本体选择器        | 可选                                     |
| `scale_id`             | 标尺                                       | 从 scale 选择            | 标尺选择器        | 必选                                     |
| `trait_id`             | 被测性状                                   | 从 trait 选择            | 性状选择器        | 必选                                     |
| `name`                 | 变量名称，采集矩阵列名                     | 自动生成后允许修改       | 文本框            | 必填；同一 crop/ontology 下建议唯一      |
| `pui`                  | 变量永久唯一标识                           | 用户录入/本体带出        | 文本框/URL 输入框 | 可选；若填写建议唯一                     |

### 录入建议

- 新建变量采用“三段式选择”：先选 trait，再选 method，再选 scale。
- 系统自动生成 name，例如：`trait_name + method_name + scale_name`，用户可修改。
- 变量详情页应展示：被哪些 study 使用、已有多少 observation、是否仍推荐使用。
- 如果变量已经产生 observation，不建议修改 trait/method/scale，只能停用旧变量并创建新变量。

---

## 6.7 study_variable 研究采集指标

### 业务说明

`study_variable` 是 study 和 observation_variable 的多对多关系，表示某个 study 计划采集哪些指标。它是“配置采集模板”的动作痕迹。

### 字段说明

| 字段             | 业务意义              | 录入方式                     | 控件建议          | 校验规则                    |
| ---------------- | --------------------- | ---------------------------- | ----------------- | --------------------------- |
| `study_db_id`    | 所属 study            | 从 study 选择或由工作台带出  | Study 选择器/隐藏 | 必选；必须引用存在 study    |
| `variable_db_id` | 该 study 要采集的变量 | 从 observation_variable 选择 | 变量多选器        | 必选；必须引用存在 variable |

### 录入建议

- 在 Study 表型工作台的“指标清单”Tab 中维护。
- 支持批量选择变量和从历史 study 复制变量清单。
- 若某变量已有 observation，不允许从 study_variable 中直接移除，除非先处理已有观测数据。

---

## 6.8 observation_unit 观测单元

### 业务说明

`observation_unit` 是“被观测对象”。通常是 plot 或 plant，也可以是 field、block、sub-plot、sample 等。它连接了 Core 上下文和材料来源，是表型数据的入口，也可以成为后续 genotyping sample 的来源。

### 字段说明

| 字段                    | 业务意义                             | 录入方式                  | 控件建议          | 校验规则                                           |
| ----------------------- | ------------------------------------ | ------------------------- | ----------------- | -------------------------------------------------- |
| `id`                    | 观测单元主键                         | 系统生成；导入可指定      | 隐藏/只读         | 必填、唯一                                         |
| `auth_user_id`          | 数据所属用户或租户                   | 登录上下文自动写入        | 隐藏              | 不允许手填                                         |
| `observation_unit_name` | 观测单元名称，如 Plot-001、Plant-003 | 用户录入/批量生成         | 文本框            | 必填；同一 study 内建议唯一                        |
| `observation_unitpui`   | 观测单元永久唯一标识                 | 用户录入/系统生成         | 文本框/URL 输入框 | 可选；若填写建议唯一                               |
| `crop_id`               | 作物                                 | 由 study 自动带出，可只读 | 作物选择器/只读   | 可选但建议有；需与 study.crop 一致                 |
| `cross_id`              | 材料来源 cross                       | 从 cross_entity 选择      | Cross 选择器      | 可选；与 germplasm/seed_lot 至少建议选一个材料来源 |
| `germplasm_id`          | 材料来源 germplasm                   | 从 germplasm 选择         | 材料选择器        | 可选；常用字段                                     |
| `program_id`            | 项目                                 | 由 study 自动带出         | 项目选择器/只读   | 可选但建议有；需与 study.program 一致              |
| `seed_lot_id`           | 种子批次来源                         | 从 seed_lot 选择          | SeedLot 选择器    | 可选；田间播种建议填写                             |
| `study_id`              | 所属 study                           | 从 study 选择或工作台带出 | Study 选择器/隐藏 | 必选                                               |
| `trial_id`              | 所属 trial                           | 由 study 自动带出         | Trial 选择器/只读 | 可选但建议有；需与 study.trial 一致                |

### 录入建议

- 推荐从 Study 表型工作台批量生成，不建议逐条手工建。
- 必须支持田间布局字段：field、block、rep、plot、row、column、plant。若当前表没有这些字段，应通过 position/level 附属表或 additional_info 保存。
- `germplasm_id` 代表材料身份；`seed_lot_id` 代表实际播种批次；二者建议都保留。
- 选择 seed_lot 时可自动带出 germplasm，但不应强行覆盖用户选择。

### 观测层级建议

| 层级   | 含义           | 例子       |
| ------ | -------------- | ---------- |
| field  | 田块或试验场   | Field-01   |
| block  | 区组           | Block-01   |
| rep    | 重复           | Rep-01     |
| plot   | 小区           | Plot-001   |
| plant  | 单株           | Plant-001  |
| sample | 样本级观测对象 | Sample-001 |

---

## 6.9 event 事件

### 业务说明

`event` 记录发生在 study 或 observation_unit 上的离散事件。它既可以是处理的一部分，也可以是影响结果的外部背景。例如播种、移栽、施肥、灌溉、打药、收获、采样、暴雨、台风、病害暴发等。

### 字段说明

| 字段                | 业务意义                                                   | 录入方式                  | 控件建议          | 校验规则                   |
| ------------------- | ---------------------------------------------------------- | ------------------------- | ----------------- | -------------------------- |
| `id`                | 事件主键                                                   | 系统生成                  | 隐藏/只读         | 必填、唯一                 |
| `auth_user_id`      | 数据所属用户或租户                                         | 登录上下文自动写入        | 隐藏              | 不允许手填                 |
| `event_description` | 事件说明，如“台风后倒伏严重”                               | 用户录入                  | 多行文本          | 可选；异常事件建议必填     |
| `event_type`        | 事件类型名称，如 fertilizer、irrigation、planting、harvest | 用户选择/录入             | 下拉框/文本框     | 必填；建议使用事件类型字典 |
| `event_type_db_id`  | 事件类型 ID，可引用标准事件类型字典                        | 用户选择/系统带出         | 选择器/隐藏       | 可选                       |
| `study_id`          | 事件所属 study                                             | 从 study 选择或工作台带出 | Study 选择器/隐藏 | 必选                       |

### 录入建议

- 在 Study 表型工作台的“事件”Tab 中维护。
- 新增事件时先选择作用范围：整个 study 或部分 observation_unit。
- 如果只作用于部分 plot/plant，必须通过 `event_observation_units` 记录范围。
- 事件发生时间字段在你当前表结构中未显式列出，但业务上非常重要；如果数据库已有 event_date/time 字段，应强制录入；如果没有，建议补字段或放入 additional_info。

---

## 6.10 event_param 事件参数

### 业务说明

`event_param` 是事件的参数明细。例如施肥事件可以有肥料类型、剂量、单位；灌溉事件可以有水量、持续时间；打药事件可以有药剂名称、浓度、剂量。

### 字段说明

| 字段                | 业务意义                              | 录入方式          | 控件建议          | 校验规则                  |
| ------------------- | ------------------------------------- | ----------------- | ----------------- | ------------------------- |
| `id`                | 事件参数主键                          | 系统生成          | 隐藏/只读         | 必填、唯一                |
| `code`              | 参数代码，如 N_RATE、IRR_VOL          | 用户录入/字典带出 | 文本框            | 可选；同一事件内建议唯一  |
| `description`       | 参数解释                              | 用户录入          | 多行文本          | 可选                      |
| `key`               | 参数键，如 fertilizerType、dose、unit | 用户录入/字典选择 | 文本框            | 必填；同一事件内不应重复  |
| `name`              | 参数名称，如 氮肥施用量               | 用户录入          | 文本框            | 可选                      |
| `rdf_value`         | RDF 或本体语义值                      | 用户录入/系统带出 | 文本框            | 可选；普通业务可隐藏      |
| `units`             | 参数单位，如 kg/ha、mm、L             | 用户选择/录入     | 单位选择器        | 可选；数值参数建议必填    |
| `value`             | 参数值                                | 用户录入          | 动态控件          | 可选；若 key 需要值则必填 |
| `value_description` | 参数值说明                            | 用户录入          | 多行文本          | 可选                      |
| `event_id`          | 所属事件                              | 由事件详情页带出  | Event 选择器/隐藏 | 必选                      |

### 录入建议

- 前端以键值表格方式维护。
- 可以为常用事件类型配置参数模板，例如 fertilizer 事件默认出现 fertilizerType、amount、units。
- 参数值是否必填取决于事件类型配置。

---

## 6.11 event_observation_units 事件作用范围

### 业务说明

`event_observation_units` 记录事件作用到哪些 observation_unit。比如一次施肥作用于整个 study，则可以不逐个绑定；如果某个处理只作用于部分 plot，则必须明确绑定。

### 字段说明

| 字段                   | 业务意义     | 录入方式                                | 控件建议          | 校验规则                 |
| ---------------------- | ------------ | --------------------------------------- | ----------------- | ------------------------ |
| `event_entity_id`      | 事件         | 由事件详情页带出                        | Event 选择器/隐藏 | 必选                     |
| `observation_units_id` | 事件作用对象 | 从当前 study 的 observation_unit 中选择 | 多选器/表格       | 必选；必须属于同一 study |

### 录入建议

- 支持按 block、rep、plot 范围批量选择。
- 如果事件作用于整个 study，可以在 event 上记录 scope=study，不必生成大量关系记录。
- 如果 event_observation_units 有记录，所有 observation_units 必须属于 event.study_id。

---

## 6.12 image 图像

### 业务说明

`image` 用于保存图片或图片元数据。图片可以是人工拍摄、无人机、固定相机、显微图、病害图片等。图片可以直接关联 observation_unit，也可以通过 `image_observations` 关联具体 observation。

### 字段说明

| 字段                  | 业务意义                              | 录入方式                 | 控件建议            | 校验规则                               |
| --------------------- | ------------------------------------- | ------------------------ | ------------------- | -------------------------------------- |
| `id`                  | 图片主键                              | 系统生成                 | 隐藏/只读           | 必填、唯一                             |
| `auth_user_id`        | 数据所属用户或租户                    | 登录上下文自动写入       | 隐藏                | 不允许手填                             |
| `copyright`           | 图片版权或授权说明                    | 用户录入                 | 文本框              | 可选                                   |
| `description`         | 图片说明，如“Plot-003 台风后倒伏照片” | 用户录入                 | 多行文本            | 可选，建议填写                         |
| `image_data`          | 图片二进制内容                        | 用户上传                 | 文件上传            | 与 `imageurl` 至少一个；限制大小和格式 |
| `image_file_name`     | 图片文件名                            | 上传时自动填充           | 只读/文本框         | 可选；上传时自动生成                   |
| `image_file_size`     | 文件大小                              | 上传时自动识别           | 只读                | 可选；应限制最大大小                   |
| `image_height`        | 图片高度                              | 上传后自动识别           | 只读/数字框         | 可选；必须为非负整数                   |
| `imagemimetype`       | MIME 类型，如 image/jpeg、image/png   | 上传后自动识别           | 只读/下拉框         | 可选；只允许图片 MIME 类型             |
| `imageurl`            | 外部图片 URL 或对象存储 URL           | 用户填写/上传后生成      | URL 输入框          | 与 `image_data` 至少一个；URL 格式校验 |
| `image_width`         | 图片宽度                              | 上传后自动识别           | 只读/数字框         | 可选；必须为非负整数                   |
| `name`                | 图片名称                              | 用户录入/文件名带出      | 文本框              | 必填                                   |
| `time_stamp`          | 拍摄或上传时间                        | 用户录入/系统默认        | 日期时间选择器      | 可选；建议填写拍摄时间                 |
| `coordinates_id`      | 图片坐标                              | 地图取点/坐标对象选择    | 地图控件/坐标选择器 | 可选；建议支持 Point/Polygon           |
| `observation_unit_id` | 所属观测单元                          | 从 observation_unit 选择 | 观测单元选择器      | 可选；若填写应属于同一 study 上下文    |

### 录入建议

- 推荐图片文件存对象存储，数据库只存 URL 和元数据，避免 bytea 过大。
- 手机拍照或无人机导入时，应自动提取文件名、大小、宽高、MIME、拍摄时间和 GPS 信息。
- 对无人机俯拍图，可保存图像中心点，也可保存图像覆盖范围 Polygon。

---

## 6.13 image_observations 图片与观测值关系

### 业务说明

`image_observations` 表示某张图片支持或关联哪些 observation。比如一张倒伏照片可以关联一个 plot 的倒伏等级 observation。

### 字段说明

| 字段              | 业务意义   | 录入方式            | 控件建议           | 校验规则                                 |
| ----------------- | ---------- | ------------------- | ------------------ | ---------------------------------------- |
| `image_entity_id` | 图片       | 由图片详情页带出    | Image 选择器/隐藏  | 必选                                     |
| `observations_id` | 关联观测值 | 从 observation 选择 | Observation 选择器 | 必选；建议与图片的 observation_unit 一致 |

### 录入建议

- 图片详情页支持关联多个 observation。
- observation 详情页也支持反向查看相关图片。
- 如果图片已绑定 observation_unit，则关联 observation 时应过滤为同一 observation_unit 下的 observation。

---

## 6.14 observation 观测值

### 业务说明

`observation` 是 Phenotyping 模块最核心的事实表。它表示“某个 observation_unit 在某个 observation_variable 上的一次实际采集结果”。

```text
observation_unit = Plot-003
observation_variable = 株高-尺测法-cm
value = 112.5
observation_time_stamp = 2026-07-12 09:30
```

### 字段说明

| 字段                      | 业务意义           | 录入方式                                 | 控件建议            | 校验规则                                   |
| ------------------------- | ------------------ | ---------------------------------------- | ------------------- | ------------------------------------------ |
| `id`                      | 观测值主键         | 系统生成；导入可指定                     | 隐藏/只读           | 必填、唯一                                 |
| `auth_user_id`            | 数据所属用户或租户 | 登录上下文自动写入                       | 隐藏                | 不允许手填                                 |
| `collector`               | 采集人             | 用户选择/登录用户带出                    | 人员选择器/文本框   | 可选；人工采集建议填写                     |
| `observation_time_stamp`  | 观测时间           | 用户录入/采集设备带出                    | 日期时间选择器      | 建议必填；按 ISO 8601 保存                 |
| `uploaded_by`             | 上传人             | 登录用户自动写入                         | 只读                | 可选；批量导入时自动填                     |
| `value`                   | 实际观测值         | 用户录入/导入/设备上传                   | 动态控件            | 必填；按 variable.scale 校验               |
| `crop_id`                 | 作物上下文         | 由 observation_unit 或 study 自动带出    | 只读/隐藏           | 可选但建议保存；需与 observation_unit 一致 |
| `geo_coordinates_id`      | 观测发生坐标       | 地图取点/设备 GPS                        | 地图控件/坐标选择器 | 可选；坐标需合法                           |
| `observation_unit_id`     | 被观测对象         | 从 observation_unit 选择或矩阵行带出     | 观测单元选择器/隐藏 | 必选                                       |
| `observation_variable_id` | 观测变量           | 从 observation_variable 选择或矩阵列带出 | 变量选择器/隐藏     | 必选                                       |
| `program_id`              | 项目上下文         | 由 observation_unit/study 自动带出       | 只读/隐藏           | 可选但建议保存                             |
| `season_id`               | 季节               | 从 study season 或用户选择               | Season 选择器       | 可选；多季节 study 建议填写                |
| `study_id`                | 所属 study         | 由 observation_unit 自动带出             | 只读/隐藏           | 必选；需与 observation_unit.study 一致     |
| `trial_id`                | 所属 trial         | 由 observation_unit/study 自动带出       | 只读/隐藏           | 可选但建议保存                             |

### 录入建议

- 普通录入优先走矩阵：行是 observation_unit，列是 observation_variable。
- 单条表单只用于补录、纠错、详情编辑。
- value 必须使用 observation_variable.scale 决定控件和校验：
  - 数值型：数字、小数位、范围；
  - 分类型：只能从 valid category 中选；
  - 日期型：日期格式；
  - 布尔型：true/false；
  - 文本型：长度限制。
- 同一 `observation_unit_id + observation_variable_id + observation_time_stamp` 可允许重复，但必须在页面展示重复标记、采集人和上传来源。

---

# 7. 跨表联动规则

| 场景                      | 联动规则                                                     |
| ------------------------- | ------------------------------------------------------------ |
| 创建 observation_variable | 必须先选择 trait、method、scale；系统自动生成变量名称        |
| 配置 study_variable       | 变量选择器优先展示同 crop 或未限定 crop 的变量               |
| 创建 observation_unit     | 选择 study 后自动带出 crop、program、trial                   |
| 选择 seed_lot             | 可自动带出 germplasm，但允许用户确认                         |
| 录入 observation          | 选择 observation_unit 后自动带出 study、trial、program、crop |
| 录入 observation.value    | 根据 observation_variable.scale 动态渲染控件和校验           |
| 新增 event                | 从 study 工作台进入时自动带出 study_id                       |
| 选择事件作用范围          | 只能选择同一 study 下的 observation_unit                     |
| 上传 image                | 如果从 observation_unit 详情页上传，自动绑定 observation_unit_id |
| 关联 image_observations   | 如果图片已绑定 observation_unit，只能关联同 observation_unit 的 observation |

---

# 8. 删除和停用规则

| 对象                 | 删除限制                                                     |
| -------------------- | ------------------------------------------------------------ |
| ontology             | 已被 trait/method/scale/variable 引用时不可物理删除          |
| trait                | 已被 observation_variable 或 germplasm_attribute_definition 引用时不可物理删除 |
| method               | 已被 observation_variable 引用时不可物理删除                 |
| scale                | 已被 observation_variable 或 attribute definition 引用时不可物理删除 |
| observation_variable | 已产生 observation 时不可删除，只能停用或设为 obsolete       |
| study_variable       | 若已有该变量的 observation，不允许直接移除                   |
| observation_unit     | 已有 observation、image、event、sample 时不可删除            |
| event                | 已有关联 event_param 或 event_observation_units 时删除需级联确认或禁止 |
| image                | 已关联 observation 时不可直接删除，应先解除关系或作废        |
| observation          | 原始数据原则上不建议物理删除；建议保留修改历史或作废状态     |

---

# 9. 批量导入要求

## 9.1 ObservationUnit 导入模板

必需列建议：

```text
study_id 或 study_name
observation_unit_name
observation_level_name
observation_level_code
```

强烈建议列：

```text
germplasm_id 或 germplasm_name
seed_lot_id 或 seed_lot_name
block
rep
plot
row
column
```

## 9.2 ObservationVariable 导入模板

必需列建议：

```text
trait_name
method_name
scale_name
variable_name
```

可选列：

```text
trait_pui
method_pui
scale_pui
variable_pui
crop_name
growth_stage
status
```

## 9.3 Observation 矩阵导入模板

推荐格式：

```text
observation_unit_name,germplasm_name,collection_time,collector,PlantHeight_cm,LodgingScore_0_5,DiseaseScore_1_9
Plot-001,华占,2026-07-12T09:30:00+08:00,张三,112.5,1,2
Plot-002,抗倒伏A,2026-07-12T09:35:00+08:00,张三,98.3,0,1
```

导入规则：

1. 每个变量列必须能映射到一个 observation_variable。
2. 每一行必须能匹配一个 observation_unit。
3. 每个非空单元格生成一条 observation。
4. 空值不生成 observation，除非用户选择“导入为空观测”。
5. 导入前必须预校验，错误报告包含行号、列名、错误原因、建议修复方式。

## 9.4 Image 导入模板

必需列建议：

```text
image_name
image_url 或 image_file
```

可选列：

```text
observation_unit_name
observation_id
capture_time
longitude
latitude
polygon
copyright
description
```

---

# 10. 后端接口建议

## 10.1 主数据接口

```text
GET    /ontologies
POST   /ontologies
GET    /ontologies/{id}
PUT    /ontologies/{id}
DELETE /ontologies/{id}

GET    /traits
POST   /traits
GET    /traits/{id}
PUT    /traits/{id}

GET    /methods
POST   /methods
GET    /methods/{id}
PUT    /methods/{id}

GET    /scales
POST   /scales
GET    /scales/{id}
PUT    /scales/{id}
GET    /scales/{id}/valid-values
POST   /scales/{id}/valid-values

GET    /observation-variables
POST   /observation-variables
GET    /observation-variables/{id}
PUT    /observation-variables/{id}

GET    /studies/{studyId}/variables
POST   /studies/{studyId}/variables
DELETE /studies/{studyId}/variables/{variableId}

GET    /studies/{studyId}/observation-units
POST   /studies/{studyId}/observation-units/batch-generate
POST   /observation-units
PUT    /observation-units/{id}

GET    /studies/{studyId}/events
POST   /studies/{studyId}/events
GET    /events/{id}/params
POST   /events/{id}/params
POST   /events/{id}/observation-units

GET    /images
POST   /images
POST   /images/upload
POST   /images/{id}/observations

GET    /observations
POST   /observations
POST   /observations/matrix
POST   /observations/import
```

## 10.2 选择器接口

```text
GET /selectors/ontologies
GET /selectors/traits?ontologyId=&keyword=
GET /selectors/methods?ontologyId=&keyword=
GET /selectors/scales?ontologyId=&dataType=&keyword=
GET /selectors/observation-variables?cropId=&studyId=&keyword=
GET /selectors/studies?programId=&trialId=&keyword=
GET /selectors/observation-units?studyId=&level=&germplasmId=&keyword=
GET /selectors/events?studyId=&eventType=&keyword=
GET /selectors/images?observationUnitId=&keyword=
```

---

# 11. 测试验收清单

1. 创建 trait 时，`trait_name` 必填。
2. 创建 method 时，`name` 必填。
3. 创建 scale 时，`scale_name` 和 `data_type` 必填。
4. 分类型 scale 没有 valid value 时，应提示或禁止用于 observation_variable。
5. 创建 observation_variable 时，trait、method、scale 必选。
6. observation_variable 名称可自动生成，也允许用户手动修改。
7. 配置 study_variable 时，不能重复绑定同一个 variable。
8. 创建 observation_unit 时，study 必选。
9. 选择 study 后，crop、program、trial 自动带出。
10. 同一 study 内 observation_unit_name 重复时提示。
11. observation_unit 至少建议绑定 germplasm、seed_lot、cross 中一个材料来源。
12. 创建 event 时，study 和 event_type 必填。
13. event 绑定 observation_unit 时，所有 observation_unit 必须属于该 event 的 study。
14. 图片上传时，image_data 和 imageurl 至少一个存在。
15. 图片 MIME 类型必须是允许的图片类型。
16. observation 创建时，observation_unit、observation_variable、value 必填。
17. observation.value 必须按 scale.data_type 校验。
18. 数值型 value 必须在 valid_value_min 和 valid_value_max 范围内。
19. 分类型 value 必须属于 scale_valid_value_category。
20. observation 的 study/trial/program/crop 必须与 observation_unit 上下文一致。
21. 矩阵录入保存时，每个有效单元格生成一条 observation。
22. 导入失败时必须返回行号、字段、错误原因和修复建议。
23. 已产生 observation 的 observation_variable 不允许直接删除。
24. 已被 observation、image、event、sample 引用的 observation_unit 不允许物理删除。
25. 所有日期时间字段按 ISO 8601 保存，并保留时区或统一转换为 UTC。

---

# 12. 开发实现重点

1. `observation_variable` 是表型指标定义，不是观测值。
2. `observation_unit` 是被观测对象，不是实际测量值。
3. `observation` 才是真正的表型事实数据。
4. `value` 字段虽然数据库是字符串，但前端和后端必须按 scale 做类型校验。
5. `study` 是 Phenotyping 的核心上下文入口，几乎所有表型数据都应能追溯到 study。
6. 表型采集页面应优先做矩阵录入，不要只做单条 CRUD。
7. 图片不要直接塞数据库大字段为主，建议走对象存储 URL + 元数据。
8. Event 不只是备注，它是解释表型差异的重要上下文，应支持作用范围和参数化。

