fix:sample/plate 之前的开发
This commit is contained in:
155
.gitignore
vendored
155
.gitignore
vendored
@@ -1,10 +1,149 @@
|
||||
.pydevproject
|
||||
target
|
||||
.classpath
|
||||
.project
|
||||
.settings
|
||||
.dbeaver-data-sources.xml
|
||||
/bin/
|
||||
# =============================================================================
|
||||
# Java / Maven
|
||||
# =============================================================================
|
||||
target/
|
||||
**/target/
|
||||
*.class
|
||||
*.jar
|
||||
*.war
|
||||
*.ear
|
||||
*.nar
|
||||
hs_err_pid*
|
||||
replay_pid*
|
||||
.factorypath
|
||||
.mvn/wrapper/maven-wrapper.jar
|
||||
|
||||
# Local Spring config (use *.template as the committed baseline)
|
||||
**/application.properties
|
||||
!**/application.properties.template
|
||||
**/properties/application.properties
|
||||
!**/properties/application.properties.template
|
||||
|
||||
# =============================================================================
|
||||
# Node.js / Next.js (frontend)
|
||||
# =============================================================================
|
||||
node_modules/
|
||||
**/node_modules/
|
||||
|
||||
.next/
|
||||
**/.next/
|
||||
out/
|
||||
**/out/
|
||||
dist/
|
||||
**/dist/
|
||||
build/
|
||||
**/build/
|
||||
.turbo/
|
||||
.vercel/
|
||||
|
||||
# TypeScript / tooling caches
|
||||
*.tsbuildinfo
|
||||
.eslintcache
|
||||
.stylelintcache
|
||||
.parcel-cache
|
||||
.cache/
|
||||
|
||||
# Package manager debug logs
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# OpenAPI codegen error dumps
|
||||
openapi-ts-error-*.log
|
||||
|
||||
# =============================================================================
|
||||
# Python (BrAPI test scripts)
|
||||
# =============================================================================
|
||||
__pycache__/
|
||||
**/__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
.Python
|
||||
.venv/
|
||||
venv/
|
||||
ENV/
|
||||
env/
|
||||
.pytest_cache/
|
||||
.mypy_cache/
|
||||
.ruff_cache/
|
||||
*.egg-info/
|
||||
.eggs/
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# =============================================================================
|
||||
# Environment & secrets
|
||||
# =============================================================================
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
!.env.example
|
||||
!.env.template
|
||||
!.env.*.example
|
||||
|
||||
*.pem
|
||||
*.key
|
||||
*.p12
|
||||
*.pfx
|
||||
credentials.json
|
||||
secrets.json
|
||||
**/secrets/
|
||||
|
||||
# =============================================================================
|
||||
# IDE & editors
|
||||
# =============================================================================
|
||||
.idea/
|
||||
brapi-Java-TestServer.iml
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.vscode/
|
||||
!.vscode/extensions.json
|
||||
!.vscode/settings.json.example
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
.project
|
||||
.classpath
|
||||
.settings/
|
||||
.pydevproject
|
||||
|
||||
# =============================================================================
|
||||
# Database tools
|
||||
# =============================================================================
|
||||
.dbeaver/
|
||||
.dbeaver-data-sources.xml
|
||||
|
||||
# =============================================================================
|
||||
# OS junk
|
||||
# =============================================================================
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
Desktop.ini
|
||||
|
||||
# =============================================================================
|
||||
# Logs & local runtime output
|
||||
# =============================================================================
|
||||
*.log
|
||||
logs/
|
||||
run*.log
|
||||
run*.out.log
|
||||
run*.err.log
|
||||
|
||||
# =============================================================================
|
||||
# Local dev tools (bundled JDK/Maven, etc.)
|
||||
# =============================================================================
|
||||
.tools/
|
||||
|
||||
# Legacy / Eclipse output
|
||||
/bin/
|
||||
|
||||
5
AGENTS.md
Normal file
5
AGENTS.md
Normal file
@@ -0,0 +1,5 @@
|
||||
1.尽量不要动brapi自己写好的java代码,不要改路径,接口定义,返回值
|
||||
2.一般我只会让你加前端,或者加一些删除等简单的后端接口,千万不要破坏原接口的路径,入参定义,出参定义。
|
||||
3.下拉框的数据要缓存下来,同一选项源不要重复请求接口。
|
||||
4.前端首页(及同类入口页)加载首页数据时,副作用只触发一次查询,避免 Strict Mode 或重复 mount 导致多次请求。
|
||||
5.保存、提交类操作要做防抖(debounce),防止连续点击重复提交。
|
||||
227
docs/architecture/00-overall-data-architecture.md
Normal file
227
docs/architecture/00-overall-data-architecture.md
Normal file
@@ -0,0 +1,227 @@
|
||||
# BrAPI Test Server 总体数据架构图
|
||||
|
||||
本文档把 4 个模块串成一张总览图:
|
||||
|
||||
```text
|
||||
Core -> Germplasm/Seed -> Phenotyping -> Genotyping
|
||||
```
|
||||
|
||||
对应的模块文档:
|
||||
|
||||
| 模块 | 文档 | 核心作用 |
|
||||
| --- | --- | --- |
|
||||
| Core | `core-data-flow.md` | crop、program、trial、study、location、person 等基础上下文 |
|
||||
| Germplasm/Seed | `04-germplasm-seed-data-flow.md` | germplasm、breeding_method、seed_lot、cross、pedigree、attribute |
|
||||
| Phenotyping | `02-phenotyping-data-flow.md` | observation_unit、observation_variable、event、image、observation |
|
||||
| Genotyping | `03-genotyping-data-flow.md` | sample、plate、reference、variantset、variant、callset、allele_call |
|
||||
|
||||
## 总体结论
|
||||
|
||||
整个数据模型的主干是:
|
||||
|
||||
```text
|
||||
Core: crop -> program -> trial -> study
|
||||
Germplasm: breeding_method -> germplasm -> cross / seed_lot / pedigree / attribute
|
||||
Phenotyping: study + germplasm/seed_lot/cross -> observation_unit -> observation
|
||||
Genotyping: observation_unit/study -> sample -> callset -> allele_call
|
||||
Genotyping: reference_set -> variantset -> variant -> allele_call
|
||||
```
|
||||
|
||||
`study` 是 Core 到 Phenotyping/Genotyping 的主桥;`germplasm` 是 Germplasm/Seed 到 Phenotyping/Genotyping 的主桥;`observation_unit` 是 Phenotyping 到 Genotyping 的主桥。
|
||||
|
||||
## 总架构图
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph CORE["Core 基础上下文"]
|
||||
CROP["crop<br/>作物"]
|
||||
PERSON["person<br/>人员"]
|
||||
PROGRAM["program<br/>项目"]
|
||||
LOCATION["location<br/>地点"]
|
||||
TRIAL["trial<br/>试验批次"]
|
||||
SEASON["season<br/>季节"]
|
||||
STUDY["study<br/>研究/试验实施单元"]
|
||||
LIST["list / list_item<br/>通用列表"]
|
||||
|
||||
CROP --> PROGRAM
|
||||
PERSON --> PROGRAM
|
||||
PROGRAM --> TRIAL
|
||||
CROP --> TRIAL
|
||||
PROGRAM --> LOCATION
|
||||
CROP --> LOCATION
|
||||
TRIAL --> STUDY
|
||||
PROGRAM --> STUDY
|
||||
CROP --> STUDY
|
||||
LOCATION --> STUDY
|
||||
SEASON --> STUDY
|
||||
PERSON --> LIST
|
||||
end
|
||||
|
||||
subgraph GERM["Germplasm / Seed 种质与种子"]
|
||||
BM["breeding_method<br/>育种方法"]
|
||||
GERMPLASM["germplasm<br/>种质"]
|
||||
GAD["germplasm_attribute_definition<br/>属性定义"]
|
||||
GAV["germplasm_attribute_value<br/>属性值"]
|
||||
CP["crossing_project<br/>杂交项目"]
|
||||
CROSS["cross_entity<br/>Cross / PlannedCross"]
|
||||
XP["cross_parent<br/>杂交亲本"]
|
||||
PEDNODE["pedigree_node<br/>系谱节点"]
|
||||
PEDEDGE["pedigree_edge<br/>系谱边"]
|
||||
SEEDLOT["seed_lot<br/>种子批次"]
|
||||
MIX["seed_lot_content_mixture<br/>批次组成"]
|
||||
TX["seed_lot_transaction<br/>批次流转"]
|
||||
|
||||
BM --> GERMPLASM
|
||||
GAD --> GAV
|
||||
GERMPLASM --> GAV
|
||||
CP --> CROSS
|
||||
CROSS --> XP
|
||||
GERMPLASM --> XP
|
||||
CROSS --> CROSS_PLANNED["cross_entity<br/>planned cross 自关联"]
|
||||
GERMPLASM --> PEDNODE
|
||||
CP --> PEDNODE
|
||||
PEDNODE --> PEDEDGE
|
||||
PEDEDGE --> PEDNODE2["pedigree_node<br/>父本/子代节点"]
|
||||
GERMPLASM --> MIX
|
||||
CROSS --> MIX
|
||||
MIX --> SEEDLOT
|
||||
SEEDLOT --> TX
|
||||
TX --> SEEDLOT
|
||||
end
|
||||
|
||||
subgraph PHENO["Phenotyping 表型"]
|
||||
ONTOLOGY["ontology<br/>本体"]
|
||||
TRAIT["trait<br/>性状"]
|
||||
METHOD["method<br/>方法"]
|
||||
SCALE["scale<br/>标尺"]
|
||||
OV["observation_variable<br/>观测变量"]
|
||||
OU["observation_unit<br/>观测单元"]
|
||||
EVENT["event<br/>事件"]
|
||||
IMAGE["image<br/>图像"]
|
||||
OBS["observation<br/>观测值"]
|
||||
|
||||
ONTOLOGY --> TRAIT
|
||||
ONTOLOGY --> METHOD
|
||||
ONTOLOGY --> SCALE
|
||||
TRAIT --> OV
|
||||
METHOD --> OV
|
||||
SCALE --> OV
|
||||
OU --> OBS
|
||||
OV --> OBS
|
||||
EVENT --> OU
|
||||
OU --> IMAGE
|
||||
IMAGE --> OBS
|
||||
end
|
||||
|
||||
subgraph GENO["Genotyping 基因型"]
|
||||
PLATE["plate<br/>样本板"]
|
||||
SAMPLE["sample<br/>样本"]
|
||||
REFSET["reference_set<br/>参考集"]
|
||||
REF["reference<br/>参考序列"]
|
||||
REFB["reference_bases<br/>参考片段"]
|
||||
VARSET["variantset<br/>变异集合"]
|
||||
VARIANT["variant<br/>变异位点"]
|
||||
CALLSET["callset<br/>样本调用集合"]
|
||||
CALL["allele_call<br/>基因型结果"]
|
||||
GMAP["genome_map<br/>遗传图谱"]
|
||||
LG["linkageGroup<br/>连锁群"]
|
||||
MP["marker_position<br/>图谱位置"]
|
||||
|
||||
PLATE --> SAMPLE
|
||||
SAMPLE --> CALLSET
|
||||
CALLSET --> CALL
|
||||
REFSET --> REF
|
||||
REF --> REFB
|
||||
REFSET --> VARSET
|
||||
VARSET --> VARIANT
|
||||
REFSET --> VARIANT
|
||||
VARIANT --> CALL
|
||||
GMAP --> LG
|
||||
LG --> MP
|
||||
VARIANT --> MP
|
||||
end
|
||||
|
||||
CROP --> GERMPLASM
|
||||
CROP --> GAD
|
||||
TRAIT --> GAD
|
||||
METHOD --> GAD
|
||||
SCALE --> GAD
|
||||
ONTOLOGY --> GAD
|
||||
PROGRAM --> CP
|
||||
PROGRAM --> SEEDLOT
|
||||
LOCATION --> SEEDLOT
|
||||
|
||||
STUDY --> OU
|
||||
TRIAL --> OU
|
||||
PROGRAM --> OU
|
||||
CROP --> OU
|
||||
GERMPLASM --> OU
|
||||
SEEDLOT --> OU
|
||||
CROSS --> OU
|
||||
|
||||
STUDY --> EVENT
|
||||
STUDY --> OBS
|
||||
TRIAL --> OBS
|
||||
PROGRAM --> OBS
|
||||
CROP --> OBS
|
||||
|
||||
STUDY --> PLATE
|
||||
TRIAL --> PLATE
|
||||
PROGRAM --> PLATE
|
||||
STUDY --> SAMPLE
|
||||
TRIAL --> SAMPLE
|
||||
PROGRAM --> SAMPLE
|
||||
OU --> SAMPLE
|
||||
|
||||
GERMPLASM --> REFSET
|
||||
STUDY --> VARSET
|
||||
CROP --> GMAP
|
||||
```
|
||||
|
||||
## 跨模块关键桥接关系
|
||||
|
||||
| 桥接点 | 连接模块 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `crop` | Core -> Germplasm/Pheno/Geno | 作物维度贯穿 program、trial、study、germplasm、变量、图谱 |
|
||||
| `program` | Core -> Germplasm/Seed/Pheno/Geno | 项目维度连接 crossing_project、seed_lot、observation_unit、sample、plate |
|
||||
| `trial` | Core -> Pheno/Geno | 试验批次维度连接 study、observation_unit、observation、sample、plate |
|
||||
| `study` | Core -> Pheno/Geno | 最重要的实验上下文,连接 observation_unit、event、observation、sample、plate、variantset |
|
||||
| `germplasm` | Germplasm -> Pheno/Geno | 种质可连接 observation_unit、cross_parent、seed_lot_content_mixture、reference_set |
|
||||
| `seed_lot` | Germplasm/Seed -> Pheno | SeedLot 可作为 observation_unit 的材料来源 |
|
||||
| `cross_entity` | Germplasm/Seed -> Pheno | Cross/PlannedCross 可作为 observation_unit 或 seed_lot_content_mixture 的来源 |
|
||||
| `observation_unit` | Pheno -> Geno | 表型观测单元可生成或关联 genotyping sample |
|
||||
| `sample` | Geno 内部入口 | 从 observation_unit/study/trial/program 进入 callset 和 allele_call |
|
||||
| `variant` | Geno 内部位点 | 与 allele_call、marker_position 连接,承载基因型结果定位 |
|
||||
|
||||
## 推荐整体录入顺序
|
||||
|
||||
1. 录入 Core 基础上下文:`crop`、`person`、`program`、`location`、`trial`、`season`、`study`。
|
||||
2. 录入 Germplasm 上游:`breeding_method`、`germplasm_attribute_definition` 依赖的 `trait/method/scale/ontology`。
|
||||
3. 录入 `germplasm`,再补充 `germplasm_attribute_value`、donor、origin、institute、synonym、taxon 等扩展信息。
|
||||
4. 如果涉及杂交,录入 `crossing_project`、`cross_entity`、`cross_parent`;计划杂交使用 `cross_entity.planned` 和 `planned_cross_id` 自关联表达。
|
||||
5. 录入 Seed 数据:`seed_lot`、`seed_lot_content_mixture`、`seed_lot_transaction`。
|
||||
6. 录入 Phenotyping 定义:`ontology`、`trait`、`method`、`scale`、`observation_variable`。
|
||||
7. 录入 Phenotyping 实体与事实:`observation_unit`、`event`、`image`、`observation`。
|
||||
8. 录入 Genotyping 样本入口:`plate`、`sample`。
|
||||
9. 录入 Genotyping 参考和变异:`reference_set`、`reference`、`reference_bases`、`variantset`、`variant`。
|
||||
10. 录入 Genotyping 结果:`callset`、`callset_variant_sets`、`allele_call`。
|
||||
11. 如需遗传图谱定位,录入 `genome_map`、`linkageGroup`、`marker_position`。
|
||||
|
||||
## 模块边界速记
|
||||
|
||||
| 模块 | 根节点 | 主要事实表 | 向外输出 |
|
||||
| --- | --- | --- | --- |
|
||||
| Core | `crop/program/trial/study` | `study` | 给所有业务模块提供上下文 |
|
||||
| Germplasm/Seed | `germplasm` | `germplasm_attribute_value`, `seed_lot_content_mixture`, `seed_lot_transaction`, `cross_parent`, `pedigree_edge` | 给 Pheno 提供材料来源,给 Geno 提供 reference source |
|
||||
| Phenotyping | `observation_unit` | `observation` | 给 Geno 提供 sample 的观测对象来源 |
|
||||
| Genotyping | `sample`, `variant` | `allele_call` | 输出样本在位点上的 genotype 结果 |
|
||||
|
||||
## 关键注意点
|
||||
|
||||
1. `study` 是大多数实验数据的上下文入口;如果数据要进入 Pheno 或 Geno,通常都应该能追溯到 `study`。
|
||||
2. `germplasm` 描述种质主数据,`seed_lot` 描述库存批次;二者通过 `seed_lot_content_mixture` 间接关联。
|
||||
3. `plannedcross` 没有独立数据库表,落库在 `cross_entity`,通过 `planned` 和 `planned_cross_id` 表达。
|
||||
4. `observation_unit` 可以关联 `germplasm`、`seed_lot`、`cross_entity`,是材料进入表型观测的入口。
|
||||
5. `sample` 可以从 `observation_unit` 来,也冗余关联 `study/trial/program`,是基因型流程入口。
|
||||
6. `allele_call` 是最终 genotype 结果表,连接 `callset` 与 `variant`。
|
||||
7. `additional_info` 和 `external_references` 是跨模块通用扩展表,主图中未展开,以免遮挡主干关系。
|
||||
290
docs/architecture/02-phenotyping-data-flow.md
Normal file
290
docs/architecture/02-phenotyping-data-flow.md
Normal file
@@ -0,0 +1,290 @@
|
||||
# Phenotyping 模块数据流与表关系
|
||||
|
||||
本文档分析 Phenotyping 模块的数据录入顺序、核心表关系,以及它与 Core 模块 `study/trial/program/crop` 的衔接方式。
|
||||
|
||||
## 结论
|
||||
|
||||
Phenotyping 模块的数据主线是:
|
||||
|
||||
```text
|
||||
core study -> ontology -> trait / method / scale -> observation_variable -> observation_unit -> event / image -> observation
|
||||
```
|
||||
|
||||
更贴近业务录入的顺序可以理解为:
|
||||
|
||||
```text
|
||||
1. 先有 Core 数据:crop、program、trial、study
|
||||
2. 录入 Ontology / Trait / Method / Scale
|
||||
3. 组装 ObservationVariable
|
||||
4. 录入 ObservationUnit
|
||||
5. 录入 Event 和 Image
|
||||
6. 录入 Observation
|
||||
```
|
||||
|
||||
初始化脚本中与 Phenotyping 相关的执行顺序是:
|
||||
|
||||
```text
|
||||
R__init_data_14_observation_units.sql
|
||||
R__init_data_17_events.sql
|
||||
R__init_data_18_images.sql
|
||||
R__init_data_19_observation_variables.sql
|
||||
R__init_data_20_observations.sql
|
||||
R__init_data_26_observation_variables2.sql
|
||||
R__init_data_26_observation_variables3.sql
|
||||
```
|
||||
|
||||
注意:初始化脚本为了构造演示数据,`observation_unit` 早于 `observation_variable` 插入;从业务建模角度看,二者都依赖已存在的 `study`,而真正的观测值 `observation` 需要同时引用 `observation_unit` 和 `observation_variable`。
|
||||
|
||||
## 核心表说明
|
||||
|
||||
| 表 | 作用 | 主要上游依赖 | 主要下游 |
|
||||
| --- | --- | --- | --- |
|
||||
| `ontology` | 本体信息,定义术语来源 | 无 | `trait`、`method`、`scale`、`observation_variable` |
|
||||
| `ontology_ref` | 本体引用项 | 可独立录入 | `trait_ontology_reference`、`method_ontology_reference`、`scale_ontology_reference` |
|
||||
| `trait` | 性状定义,描述“测什么” | 可选 `ontology` | `observation_variable` |
|
||||
| `method` | 测量方法,描述“怎么测” | 可选 `ontology` | `observation_variable` |
|
||||
| `scale` | 标尺/单位/数据类型,描述“用什么尺度表达” | 可选 `ontology` | `observation_variable`、`scale_valid_value_category` |
|
||||
| `observation_variable` | 观测变量,由 trait/method/scale 组成 | `crop`、`trait`、`method`、`scale`、`ontology` | `observation`、`study_variable` |
|
||||
| `observation_unit` | 观测对象,如 plot/plant/block | `crop`、`program`、`trial`、`study`,可选 germplasm/seed_lot/cross | `observation`、`event_observation_units`、`image` |
|
||||
| `event` | 田间事件,如施肥、灌溉、采样等 | `study` | `event_param`、`event_observation_units` |
|
||||
| `event_param` | 事件参数 | `event` | `event_parameter_entity_values_by_date` |
|
||||
| `image` | 图片/影像数据 | 可选 `observation_unit`、`geojson` | `image_observations` |
|
||||
| `observation` | 实际观测值 | `observation_unit`、`observation_variable`、`study`、可选 `season` | `image_observations` |
|
||||
|
||||
## 建议录入顺序
|
||||
|
||||
### 1. 准备 Core 上游数据
|
||||
|
||||
Phenotyping 数据通常挂在 Core 的层级下面:
|
||||
|
||||
```text
|
||||
crop -> program -> trial -> study
|
||||
```
|
||||
|
||||
其中 `study` 是 Phenotyping 的入口节点。`observation_unit`、`event`、`observation` 都会直接或间接关联到 `study`。
|
||||
|
||||
### 2. 录入 Ontology
|
||||
|
||||
先录入 `ontology` 和需要的 `ontology_ref`。
|
||||
|
||||
`ontology` 用来标识术语体系来源,后续 `trait`、`method`、`scale`、`observation_variable` 都可以挂载本体信息。
|
||||
|
||||
### 3. 录入 Trait / Method / Scale
|
||||
|
||||
这三类数据共同描述一个观测指标:
|
||||
|
||||
```text
|
||||
Trait = 测什么,例如 plant height
|
||||
Method = 怎么测,例如 ruler measurement
|
||||
Scale = 用什么单位/数据类型表达,例如 cm、numeric
|
||||
```
|
||||
|
||||
`scale` 如果有枚举或分类值,还会录入:
|
||||
|
||||
```text
|
||||
scale_valid_value_category
|
||||
```
|
||||
|
||||
### 4. 组装 ObservationVariable
|
||||
|
||||
录入 `observation_variable`,它会引用:
|
||||
|
||||
```text
|
||||
crop
|
||||
trait
|
||||
method
|
||||
scale
|
||||
ontology
|
||||
```
|
||||
|
||||
业务上它相当于“可被采集的一项指标”。例如“株高-尺测法-cm”。
|
||||
|
||||
`study_variable` 是 `study` 和 `observation_variable` 的多对多关系,表示某个 study 会采集哪些变量。
|
||||
|
||||
### 5. 录入 ObservationUnit
|
||||
|
||||
录入 `observation_unit`,它表示被观测对象,例如 field、block、plot、plant。
|
||||
|
||||
它通常会引用:
|
||||
|
||||
```text
|
||||
crop
|
||||
program
|
||||
trial
|
||||
study
|
||||
```
|
||||
|
||||
并且可选关联:
|
||||
|
||||
```text
|
||||
germplasm
|
||||
seed_lot
|
||||
cross
|
||||
observation_unit_position
|
||||
observation_unit_treatment
|
||||
observation_unit_level
|
||||
```
|
||||
|
||||
### 6. 录入 Event
|
||||
|
||||
录入 `event`,用于表达发生在 study 或 observation unit 上的事件。
|
||||
|
||||
常见关系:
|
||||
|
||||
```text
|
||||
event -> study
|
||||
event_observation_units -> observation_unit
|
||||
event_param -> event
|
||||
```
|
||||
|
||||
### 7. 录入 Image
|
||||
|
||||
录入 `image`,图片可以直接关联 `observation_unit`,也可以通过 `image_observations` 关联一个或多个 `observation`。
|
||||
|
||||
图片坐标使用 `geojson/coordinate` 扩展。
|
||||
|
||||
### 8. 录入 Observation
|
||||
|
||||
最后录入 `observation`,这是 Phenotyping 模块的核心事实数据。
|
||||
|
||||
一条 observation 通常同时引用:
|
||||
|
||||
```text
|
||||
observation_unit
|
||||
observation_variable
|
||||
study
|
||||
trial
|
||||
program
|
||||
crop
|
||||
season
|
||||
```
|
||||
|
||||
代码里 `ObservationEntity.setObservationUnit(...)` 会从 observation unit 继承 study/trial/program/crop,因此 observation 的上下文可以由 observation unit 自动带出。
|
||||
|
||||
## Phenotyping 数据流图
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
C["Core: crop"] --> P["Core: program"]
|
||||
P --> T["Core: trial"]
|
||||
T --> S["Core: study"]
|
||||
|
||||
O["ontology 本体"] --> TR["trait 性状"]
|
||||
O --> M["method 方法"]
|
||||
O --> SC["scale 标尺"]
|
||||
OR["ontology_ref 本体引用"] --> TR
|
||||
OR --> M
|
||||
OR --> SC
|
||||
|
||||
C --> OV["observation_variable 观测变量"]
|
||||
TR --> OV
|
||||
M --> OV
|
||||
SC --> OV
|
||||
O --> OV
|
||||
|
||||
S --> SV["study_variable 研究-变量"]
|
||||
OV --> SV
|
||||
|
||||
C --> OU["observation_unit 观测单元"]
|
||||
P --> OU
|
||||
T --> OU
|
||||
S --> OU
|
||||
G["Germplasm/SeedLot/Cross 可选"] --> OU
|
||||
OU --> OUP["position / treatment / level"]
|
||||
|
||||
S --> E["event 事件"]
|
||||
E --> EP["event_param 事件参数"]
|
||||
E --> EOU["event_observation_units"]
|
||||
OU --> EOU
|
||||
|
||||
OU --> IMG["image 图像"]
|
||||
GEO["geojson / coordinate"] --> IMG
|
||||
|
||||
OU --> OB["observation 观测值"]
|
||||
OV --> OB
|
||||
S --> OB
|
||||
T --> OB
|
||||
P --> OB
|
||||
C --> OB
|
||||
SE["Core: season"] --> OB
|
||||
|
||||
IMG --> IO["image_observations"]
|
||||
OB --> IO
|
||||
```
|
||||
|
||||
## Phenotyping ER 关系图
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
crop ||--o{ observation_variable : "crop_id"
|
||||
crop ||--o{ observation_unit : "crop_id"
|
||||
crop ||--o{ observation : "crop_id"
|
||||
|
||||
program ||--o{ observation_unit : "program_id"
|
||||
program ||--o{ observation : "program_id"
|
||||
|
||||
trial ||--o{ observation_unit : "trial_id"
|
||||
trial ||--o{ observation : "trial_id"
|
||||
|
||||
study ||--o{ observation_unit : "study_id"
|
||||
study ||--o{ event : "study_id"
|
||||
study ||--o{ observation : "study_id"
|
||||
study ||--o{ study_variable : "study_db_id"
|
||||
|
||||
ontology ||--o{ trait : "ontology_id"
|
||||
ontology ||--o{ method : "ontology_id"
|
||||
ontology ||--o{ scale : "ontology_id"
|
||||
ontology ||--o{ observation_variable : "ontology_id"
|
||||
|
||||
ontology_ref ||--o{ trait_ontology_reference : "ontology_reference_id"
|
||||
ontology_ref ||--o{ method_ontology_reference : "ontology_reference_id"
|
||||
ontology_ref ||--o{ scale_ontology_reference : "ontology_reference_id"
|
||||
|
||||
trait ||--o{ observation_variable : "trait_id"
|
||||
method ||--o{ observation_variable : "method_id"
|
||||
scale ||--o{ observation_variable : "scale_id"
|
||||
scale ||--o{ scale_valid_value_category : "scale_id"
|
||||
|
||||
observation_variable ||--o{ observation : "observation_variable_id"
|
||||
observation_variable ||--o{ study_variable : "variable_db_id"
|
||||
|
||||
observation_unit ||--o{ observation : "observation_unit_id"
|
||||
observation_unit ||--o{ observation_unit_position : "observation_unit_id"
|
||||
observation_unit ||--o{ observation_unit_treatment : "observation_unit_id"
|
||||
observation_unit ||--o{ observation_unit_level : "observation_unit_id"
|
||||
|
||||
event ||--o{ event_param : "event_id"
|
||||
event ||--o{ event_observation_units : "event_entity_id"
|
||||
observation_unit ||--o{ event_observation_units : "observation_units_id"
|
||||
|
||||
image ||--o{ image_observations : "image_entity_id"
|
||||
observation ||--o{ image_observations : "observations_id"
|
||||
observation_unit ||--o{ image : "observation_unit_id"
|
||||
|
||||
season ||--o{ observation : "season_id"
|
||||
```
|
||||
|
||||
## API 与表的对应关系
|
||||
|
||||
| API | 主表 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `/brapi/v2/ontologies` | `ontology` | 本体查询、新增 |
|
||||
| `/brapi/v2/traits` | `trait` | 性状定义 |
|
||||
| `/brapi/v2/methods` | `method` | 测量方法 |
|
||||
| `/brapi/v2/scales` | `scale` | 标尺、单位、数据类型 |
|
||||
| `/brapi/v2/variables` | `observation_variable` | 观测变量,由 trait/method/scale 组合 |
|
||||
| `/brapi/v2/observationunits` | `observation_unit` | 观测单元 |
|
||||
| `/brapi/v2/events` | `event` | 田间/实验事件 |
|
||||
| `/brapi/v2/images` | `image` | 图像数据 |
|
||||
| `/brapi/v2/observations` | `observation` | 实际观测值 |
|
||||
|
||||
## 关键注意点
|
||||
|
||||
1. `study` 是 Phenotyping 与 Core 的连接点,大多数表型数据都应挂到具体 study。
|
||||
2. `observation_variable` 不是单独的数值,它是“性状 + 方法 + 标尺 + 本体”的指标定义。
|
||||
3. `observation_unit` 是被观测对象,`observation` 是对这个对象在某个变量上的一次测量结果。
|
||||
4. `event` 可以绑定多个 `observation_unit`,适合记录施肥、灌溉、采样等动作。
|
||||
5. `image` 可以直接绑定 `observation_unit`,也可以通过 `image_observations` 与观测值关联。
|
||||
6. `trait/method/scale/observation_variable` 都有 `*_additional_info` 和 `*_external_references` 扩展表,用于补充业务字段和外部引用。
|
||||
7. `observation` 冗余保存了 `crop/program/trial/study` 上下文,代码中会从 `observation_unit` 或 `study` 向上继承这些上下文,方便查询。
|
||||
|
||||
313
docs/architecture/03-genotyping-data-flow.md
Normal file
313
docs/architecture/03-genotyping-data-flow.md
Normal file
@@ -0,0 +1,313 @@
|
||||
# Genotyping 模块数据流与表关系
|
||||
|
||||
本文档分析 Genotyping 模块的数据录入顺序、核心表关系,以及 Java 实体名与真实数据库表名之间的对应关系。
|
||||
|
||||
## 结论
|
||||
|
||||
Genotyping 模块的数据主线是:
|
||||
|
||||
```text
|
||||
Core/Pheno 上游数据 -> sample / plate
|
||||
ReferenceSet -> Reference -> ReferenceBases
|
||||
ReferenceSet + Study -> VariantSet -> Variant
|
||||
Sample -> CallSet
|
||||
CallSet + Variant -> Call
|
||||
GenomeMap -> LinkageGroup -> MarkerPosition -> Variant
|
||||
```
|
||||
|
||||
更贴近业务录入的顺序是:
|
||||
|
||||
```text
|
||||
1. 先有 Core/Phenotyping 上游:crop、program、trial、study、observation_unit
|
||||
2. 录入 Plate 和 Sample
|
||||
3. 录入 ReferenceSet、Reference、ReferenceBases
|
||||
4. 录入 VariantSet
|
||||
5. 录入 Variant
|
||||
6. 录入 CallSet
|
||||
7. 录入 Call,也就是 allele_call 表里的基因型结果
|
||||
8. 录入 GenomeMap、LinkageGroup、MarkerPosition
|
||||
```
|
||||
|
||||
初始化脚本中与 Genotyping 相关的执行顺序是:
|
||||
|
||||
```text
|
||||
R__init_data_21_samples.sql
|
||||
R__init_data_22_references.sql
|
||||
R__init_data_23_variant_set_1.sql
|
||||
R__init_data_24_genome_maps.sql
|
||||
src/main/resources/db/sql/variant_set_4/variant_set_4.sql
|
||||
src/main/resources/db/sql/variant_set_4/variant_set_4_alleles.sql
|
||||
```
|
||||
|
||||
## 实体与真实表名
|
||||
|
||||
| 业务概念 | Java 实体 | 数据库表 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| Call | `CallEntity` | `allele_call` | 单个样本在某个 variant 上的 genotype 结果 |
|
||||
| CallSet | `CallSetEntity` | `callset` | 某个 sample 的一组 call,通常对应一个样本的基因型调用集合 |
|
||||
| Sample | `SampleEntity` | `sample` | 送检样本/测序样本 |
|
||||
| Plate | `PlateEntity` | `plate` | 样本板,包含多个 sample |
|
||||
| MarkerPosition | `MarkerPositionEntity` | `marker_position` | variant 在 linkage group 上的位置 |
|
||||
| Variant | `VariantEntity` | `variant` | 变异位点,如 SNP/Indel |
|
||||
| ReferenceSet | `ReferenceSetEntity` | `reference_set` | 参考基因组集合 |
|
||||
| GenomeMap | `GenomeMapEntity` | `genome_map` | 遗传图谱 |
|
||||
| VariantSet | `VariantSetEntity` | `variantset` | 一批 variant 的集合 |
|
||||
| Reference | `ReferenceEntity` | `reference` | 参考序列,如 chromosome/contig |
|
||||
| ReferenceBases | `ReferenceBasesPageEntity` | `reference_bases` | reference 的序列分页 |
|
||||
| LinkageGroup | `LinkageGroupEntity` | `linkageGroup` | 图谱中的连锁群;注意表名是驼峰 `linkageGroup` |
|
||||
|
||||
## 核心表说明
|
||||
|
||||
| 表 | 作用 | 主要上游依赖 | 主要下游 |
|
||||
| --- | --- | --- | --- |
|
||||
| `plate` | 样本板 | `program`、`trial`、`study`,可选 vendor submission | `sample` |
|
||||
| `sample` | 样本 | `plate`、`observation_unit`、`program`、`trial`、`study` | `callset` |
|
||||
| `reference_set` | 参考基因组集合 | 可选 `germplasm` | `reference`、`variantset`、`variant` |
|
||||
| `reference` | 参考序列 | `reference_set` | `reference_bases` |
|
||||
| `reference_bases` | 参考序列片段/分页 | `reference` | 无 |
|
||||
| `variantset` | 变异集合 | `reference_set`、`study` | `variant`、`callset_variant_sets`、`variantset_analysis`、`variantset_format` |
|
||||
| `variant` | 变异位点 | `reference_set`、`variantset` | `allele_call`、`marker_position` |
|
||||
| `callset` | 样本的 call 集合 | `sample` | `allele_call`、`callset_variant_sets` |
|
||||
| `allele_call` | genotype 调用结果 | `callset`、`variant` | 无 |
|
||||
| `genome_map` | 遗传图谱 | `crop`,可关联 `study` | `linkageGroup` |
|
||||
| `linkageGroup` | 连锁群 | `genome_map` | `marker_position` |
|
||||
| `marker_position` | marker/variant 在图谱上的位置 | `linkageGroup`、`variant` | 无 |
|
||||
|
||||
## 建议录入顺序
|
||||
|
||||
### 1. 准备 Core/Phenotyping 上游数据
|
||||
|
||||
Genotyping 数据通常挂在 Core 和 Phenotyping 之上。
|
||||
|
||||
必须或常见上游包括:
|
||||
|
||||
```text
|
||||
crop
|
||||
program
|
||||
trial
|
||||
study
|
||||
observation_unit
|
||||
```
|
||||
|
||||
`sample` 可以关联 `observation_unit`,也会冗余关联 `program/trial/study`,用于查询和筛选。
|
||||
|
||||
### 2. 录入 Plate
|
||||
|
||||
先录入 `plate`,表示样本板。
|
||||
|
||||
`plate` 可关联:
|
||||
|
||||
```text
|
||||
program
|
||||
trial
|
||||
study
|
||||
plate_submission
|
||||
```
|
||||
|
||||
如果样本不走板,也可以直接录入 `sample`;但当前模型中 sample 支持挂到 plate 上。
|
||||
|
||||
### 3. 录入 Sample
|
||||
|
||||
录入 `sample`,它是 genotyping 流程的样本入口。
|
||||
|
||||
主要关系:
|
||||
|
||||
```text
|
||||
sample -> plate
|
||||
sample -> observation_unit
|
||||
sample -> program / trial / study
|
||||
sample -> germplasm_taxon
|
||||
```
|
||||
|
||||
### 4. 录入 ReferenceSet 和 Reference
|
||||
|
||||
录入 `reference_set`,表示参考基因组集合。
|
||||
|
||||
然后录入 `reference`,表示具体参考序列,例如 chromosome、contig。
|
||||
|
||||
如需保存具体序列片段,再录入:
|
||||
|
||||
```text
|
||||
reference_bases
|
||||
```
|
||||
|
||||
### 5. 录入 VariantSet
|
||||
|
||||
录入 `variantset`,它把一批 variant 组织成集合。
|
||||
|
||||
主要关系:
|
||||
|
||||
```text
|
||||
variantset -> reference_set
|
||||
variantset -> study
|
||||
```
|
||||
|
||||
附属表包括:
|
||||
|
||||
```text
|
||||
variantset_analysis
|
||||
variantset_format
|
||||
variantset_additional_info
|
||||
variantset_external_references
|
||||
```
|
||||
|
||||
### 6. 录入 Variant
|
||||
|
||||
录入 `variant`,表示具体变异位点。
|
||||
|
||||
主要关系:
|
||||
|
||||
```text
|
||||
variant -> reference_set
|
||||
variant -> variantset
|
||||
```
|
||||
|
||||
附属表包括:
|
||||
|
||||
```text
|
||||
variant_entity_alternate_bases
|
||||
variant_entity_ciend
|
||||
variant_entity_cipos
|
||||
variant_entity_filters_failed
|
||||
```
|
||||
|
||||
### 7. 录入 CallSet
|
||||
|
||||
录入 `callset`,表示某个样本的一组 genotype calls。
|
||||
|
||||
主要关系:
|
||||
|
||||
```text
|
||||
callset -> sample
|
||||
callset_variant_sets -> variantset
|
||||
```
|
||||
|
||||
`callset_variant_sets` 是 `callset` 和 `variantset` 的多对多关系表。
|
||||
|
||||
### 8. 录入 Call
|
||||
|
||||
录入 `allele_call`,业务上就是 Call。
|
||||
|
||||
它是最终基因型调用结果,核心关系是:
|
||||
|
||||
```text
|
||||
allele_call -> callset
|
||||
allele_call -> variant
|
||||
```
|
||||
|
||||
也就是说,一条 call 表示“某个 sample/callset 在某个 variant 上的 genotype、read depth、likelihood 等结果”。
|
||||
|
||||
### 9. 录入 GenomeMap 和 MarkerPosition
|
||||
|
||||
如果需要遗传图谱定位,录入:
|
||||
|
||||
```text
|
||||
genome_map -> linkageGroup -> marker_position -> variant
|
||||
```
|
||||
|
||||
`marker_position` 实际上把 variant 放到某个 linkage group 的具体位置上。
|
||||
|
||||
## Genotyping 数据流图
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
C["Core: crop"] --> GM["genome_map 遗传图谱"]
|
||||
C --> P["Core: program"]
|
||||
P --> T["Core: trial"]
|
||||
T --> ST["Core: study"]
|
||||
ST --> PL["plate 样本板"]
|
||||
ST --> VS["variantset 变异集合"]
|
||||
ST --> SM["sample 样本"]
|
||||
|
||||
OU["Pheno: observation_unit"] --> SM
|
||||
PL --> SM
|
||||
|
||||
GER["Germplasm 可选"] --> RS["reference_set 参考集合"]
|
||||
RS --> R["reference 参考序列"]
|
||||
R --> RB["reference_bases 参考序列分页"]
|
||||
|
||||
RS --> VS
|
||||
VS --> V["variant 变异位点"]
|
||||
RS --> V
|
||||
|
||||
SM --> CS["callset 样本调用集合"]
|
||||
CS --> CSV["callset_variant_sets"]
|
||||
VS --> CSV
|
||||
|
||||
CS --> CALL["allele_call / Call 基因型结果"]
|
||||
V --> CALL
|
||||
|
||||
GM --> LG["linkageGroup 连锁群"]
|
||||
LG --> MP["marker_position 图谱位置"]
|
||||
V --> MP
|
||||
|
||||
VS --> VSA["variantset_analysis"]
|
||||
VS --> VSF["variantset_format"]
|
||||
```
|
||||
|
||||
## Genotyping ER 关系图
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
program ||--o{ plate : "program_id"
|
||||
trial ||--o{ plate : "trial_id"
|
||||
study ||--o{ plate : "study_id"
|
||||
|
||||
plate ||--o{ sample : "plate_id"
|
||||
observation_unit ||--o{ sample : "observation_unit_id"
|
||||
program ||--o{ sample : "program_id"
|
||||
trial ||--o{ sample : "trial_id"
|
||||
study ||--o{ sample : "study_id"
|
||||
|
||||
germplasm ||--o{ reference_set : "source_germplasm_id"
|
||||
reference_set ||--o{ reference : "reference_set_id"
|
||||
reference ||--o{ reference_bases : "reference_id"
|
||||
|
||||
reference_set ||--o{ variantset : "reference_set_id"
|
||||
study ||--o{ variantset : "study_id"
|
||||
variantset ||--o{ variant : "variant_set_id"
|
||||
reference_set ||--o{ variant : "reference_set_id"
|
||||
|
||||
sample ||--o{ callset : "sample_id"
|
||||
callset ||--o{ callset_variant_sets : "call_sets_id"
|
||||
variantset ||--o{ callset_variant_sets : "variant_sets_id"
|
||||
|
||||
callset ||--o{ allele_call : "call_set_id"
|
||||
variant ||--o{ allele_call : "variant_id"
|
||||
|
||||
crop ||--o{ genome_map : "crop_id"
|
||||
genome_map ||--o{ linkageGroup : "genome_map_id"
|
||||
linkageGroup ||--o{ marker_position : "linkage_group_id"
|
||||
variant ||--o{ marker_position : "variant_id"
|
||||
|
||||
variantset ||--o{ variantset_analysis : "variant_set_id"
|
||||
variantset ||--o{ variantset_format : "variant_set_id"
|
||||
```
|
||||
|
||||
## API 与表的对应关系
|
||||
|
||||
| API | 主表 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `/brapi/v2/samples` | `sample` | 样本查询、新增、修改 |
|
||||
| `/brapi/v2/plates` | `plate` | 样本板查询、新增、修改 |
|
||||
| `/brapi/v2/callsets` | `callset` | 样本调用集合 |
|
||||
| `/brapi/v2/calls` | `allele_call` | genotype 调用结果 |
|
||||
| `/brapi/v2/variants` | `variant` | 变异位点 |
|
||||
| `/brapi/v2/variantsets` | `variantset` | 变异集合 |
|
||||
| `/brapi/v2/referencesets` | `reference_set` | 参考基因组集合 |
|
||||
| `/brapi/v2/references` | `reference` | 参考序列 |
|
||||
| `/brapi/v2/maps` | `genome_map` | 遗传图谱 |
|
||||
| `/brapi/v2/markerpositions` | `marker_position` | variant/marker 在图谱上的位置 |
|
||||
|
||||
## 关键注意点
|
||||
|
||||
1. `CallEntity` 对应的数据库表不是 `call`,而是 `allele_call`。
|
||||
2. `CallSetEntity` 对应 `callset`,不是 `call_set`。
|
||||
3. `VariantSetEntity` 对应 `variantset`,不是 `variant_set`。
|
||||
4. `LinkageGroupEntity` 对应表名是 `linkageGroup`,schema 里另有外键引用时大小写需要特别注意。
|
||||
5. `sample` 是基因型流程的样本入口,向上关联 `plate/observation_unit/study/trial/program`。
|
||||
6. `variant` 是位点定义,`allele_call` 是样本在位点上的结果;不要把二者混成同一层数据。
|
||||
7. `reference_set/reference/reference_bases` 是参考基因组侧;`variantset/variant/callset/allele_call` 是变异和结果侧。
|
||||
8. `genome_map/linkageGroup/marker_position` 是遗传图谱定位侧,`marker_position` 通过 `variant_id` 与变异位点相连。
|
||||
9. 与前两篇一样,`*_additional_info` 和 `*_external_references` 是通用扩展关系,用于补充业务字段和外部引用。
|
||||
|
||||
142
docs/architecture/04-germplasm-seed-data-flow.md
Normal file
142
docs/architecture/04-germplasm-seed-data-flow.md
Normal file
@@ -0,0 +1,142 @@
|
||||
# Germplasm 与 Seed 数据流及表关系
|
||||
|
||||
本文档整理 Germplasm 模块中以 `germplasm` 为核心,并覆盖 SeedLot、CrossingProject、GermplasmAttribute、Cross、Pedigree 等相关表的关系。重点表包括:
|
||||
|
||||
```text
|
||||
germplasm
|
||||
seed_lot
|
||||
seed_lot_content_mixture
|
||||
crossing_project
|
||||
cross_entity / planned cross / cross_parent
|
||||
germplasm_attribute_definition
|
||||
germplasm_attribute_value
|
||||
pedigree_node / pedigree_edge
|
||||
breeding_method
|
||||
```
|
||||
|
||||
## 结论
|
||||
|
||||
Germplasm 关系主线可以理解为:
|
||||
|
||||
```text
|
||||
breeding_method -> germplasm -> germplasm_attribute_value -> germplasm_attribute_definition
|
||||
program -> crossing_project -> cross_entity -> cross_parent -> germplasm
|
||||
germplasm / cross_entity -> seed_lot_content_mixture -> seed_lot
|
||||
germplasm -> pedigree_node -> pedigree_edge
|
||||
```
|
||||
|
||||
其中 `plannedcross` 在数据库中不是独立表,而是 `cross_entity` 的自关联:`cross_entity.planned_cross_id -> cross_entity.id`,并通过 `planned` 字段区分计划杂交和实际杂交。
|
||||
|
||||
## 图 4 Germplasm 关系架构图
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
BM["breeding_method<br/>育种方法"] -->|"breeding_method_id"| G["germplasm<br/>种质主表"]
|
||||
CROP["crop<br/>作物"] -->|"crop_id"| G
|
||||
|
||||
GAD["germplasm_attribute_definition<br/>GermplasmAttribute 定义"] -->|"attribute_id"| GAV["germplasm_attribute_value<br/>GermplasmAttributeValue"]
|
||||
G -->|"germplasm_id"| GAV
|
||||
CROP -->|"crop_id"| GAD
|
||||
TR["trait"] -->|"trait_id"| GAD
|
||||
ME["method"] -->|"method_id"| GAD
|
||||
SC["scale"] -->|"scale_id"| GAD
|
||||
ONT["ontology"] -->|"ontology_id"| GAD
|
||||
|
||||
PR["program<br/>项目"] -->|"program_id"| CP["crossing_project<br/>CrossingProject"]
|
||||
CP -->|"crossing_project_id"| CR["cross_entity<br/>Cross / PlannedCross"]
|
||||
CP -->|"crossing_project_id"| XP["cross_parent<br/>CrossParent"]
|
||||
|
||||
CR -->|"cross_id"| XP
|
||||
G -->|"germplasm_id"| XP
|
||||
OU["observation_unit<br/>可选亲本来源"] -->|"observation_unit_id"| XP
|
||||
CR -->|"planned_cross_id 自关联"| PCR["cross_entity<br/>planned cross"]
|
||||
|
||||
G -->|"germplasm_id"| SCM["seed_lot_content_mixture<br/>SeedLot 组成"]
|
||||
CR -->|"cross_id"| SCM
|
||||
SCM -->|"seed_lot_id"| SL["seed_lot<br/>SeedLot"]
|
||||
PR -->|"program_id"| SL
|
||||
LOC["location<br/>库位/地点"] -->|"location_id"| SL
|
||||
|
||||
SL -->|"from_seed_lot_id"| TX["seed_lot_transaction<br/>SeedLot 流转"]
|
||||
TX -->|"to_seed_lot_id"| SL
|
||||
|
||||
CP -->|"crossing_project_id"| PN["pedigree_node<br/>PedigreeNode"]
|
||||
G -->|"germplasm_id"| PN
|
||||
PN -->|"this_node_id"| PE["pedigree_edge<br/>亲子/同胞关系"]
|
||||
PE -->|"connceted_node_id"| PN2["pedigree_node<br/>父本/子代节点"]
|
||||
```
|
||||
|
||||
## 图 4-2 Germplasm ER 关系图
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
crop ||--o{ germplasm : "crop_id"
|
||||
breeding_method ||--o{ germplasm : "breeding_method_id"
|
||||
|
||||
germplasm ||--o{ germplasm_attribute_value : "germplasm_id"
|
||||
germplasm ||--o{ germplasm_donor : "germplasm_id"
|
||||
germplasm ||--o{ germplasm_institute : "germplasm_id"
|
||||
germplasm ||--o{ germplasm_origin : "germplasm_id"
|
||||
germplasm ||--o{ germplasm_synonym : "germplasm_id"
|
||||
germplasm ||--o{ germplasm_taxon : "germplasm_id"
|
||||
|
||||
germplasm ||--o| pedigree_node : "germplasm_id"
|
||||
pedigree_node ||--o{ pedigree_edge : "this_node_id"
|
||||
pedigree_node ||--o{ pedigree_edge : "connceted_node_id"
|
||||
|
||||
crossing_project ||--o{ cross_entity : "crossing_project_id"
|
||||
cross_entity ||--o{ cross_entity : "planned_cross_id"
|
||||
cross_entity ||--o{ cross_parent : "cross_id"
|
||||
crossing_project ||--o{ cross_parent : "crossing_project_id"
|
||||
germplasm ||--o{ cross_parent : "germplasm_id"
|
||||
|
||||
seed_lot ||--o{ seed_lot_content_mixture : "seed_lot_id"
|
||||
germplasm ||--o{ seed_lot_content_mixture : "germplasm_id"
|
||||
cross_entity ||--o{ seed_lot_content_mixture : "cross_id"
|
||||
|
||||
location ||--o{ seed_lot : "location_id"
|
||||
program ||--o{ seed_lot : "program_id"
|
||||
|
||||
seed_lot ||--o{ seed_lot_transaction : "from_seed_lot_id"
|
||||
seed_lot ||--o{ seed_lot_transaction : "to_seed_lot_id"
|
||||
```
|
||||
|
||||
## 核心表说明
|
||||
|
||||
| 表 | 作用 | 主要上游依赖 | 主要下游 |
|
||||
| --- | --- | --- | --- |
|
||||
| `germplasm` | 种质主表,保存 accession、PUI、物种、采集来源、种子来源等信息 | `crop`, `breeding_method` | 属性、机构、来源、系谱、SeedLot 组成、Cross 亲本 |
|
||||
| `breeding_method` | 育种方法字典 | 无 | `germplasm` |
|
||||
| `germplasm_attribute_definition` | GermplasmAttribute 定义,继承变量定义体系,可关联 trait/method/scale/ontology/crop | `crop`, `trait`, `method`, `scale`, `ontology` | `germplasm_attribute_value` |
|
||||
| `germplasm_attribute_value` | GermplasmAttributeValue,保存某个 germplasm 在某个属性上的取值 | `germplasm`, `germplasm_attribute_definition` | 属性查询 |
|
||||
| `crossing_project` | CrossingProject,杂交项目 | `program` | `cross_entity`, `cross_parent`, `pedigree_node` |
|
||||
| `cross_entity` | Cross/PlannedCross 统一落库表;`planned_cross_id` 是对本表的自关联 | `crossing_project`, `cross_entity` | `cross_parent`, `seed_lot_content_mixture` |
|
||||
| `cross_parent` | CrossParent,连接 `cross_entity` 与 `germplasm` 或 observation unit | `cross_entity`, `crossing_project`, `germplasm`, `observation_unit` | 杂交亲本 |
|
||||
| `seed_lot` | 种子批次/库存批次,保存数量、单位、库位、项目、创建和更新时间 | `location`, `program` | `seed_lot_content_mixture`, `seed_lot_transaction` |
|
||||
| `seed_lot_content_mixture` | SeedLot 组成明细,连接 `seed_lot` 与 `germplasm` 或 `cross_entity` | `seed_lot`, `germplasm`, `cross_entity` | 表示批次内各成分占比 |
|
||||
| `seed_lot_transaction` | SeedLot 流转记录,记录从一个批次到另一个批次的数量变化 | `from_seed_lot`, `to_seed_lot` | 库存流向追踪 |
|
||||
| `pedigree_node` | 系谱节点,一个节点可关联一个 germplasm | `germplasm`, `crossing_project` | `pedigree_edge` |
|
||||
| `pedigree_edge` | 系谱边,描述 parent/child/sibling 关系 | `pedigree_node` | 系谱查询 |
|
||||
|
||||
## 建议录入顺序
|
||||
|
||||
1. 先录入上游基础数据:`crop`、`breeding_method`、`program`、`location`,以及属性定义需要的 `trait/method/scale/ontology`。
|
||||
2. 录入 `germplasm_attribute_definition`,定义可采集的 GermplasmAttribute。
|
||||
3. 录入 `germplasm` 主数据,并通过 `breeding_method_id` 关联育种方法。
|
||||
4. 录入 `germplasm_attribute_value`,把 germplasm 与 attribute definition 连接起来并保存具体值。
|
||||
5. 如果涉及杂交,录入 `crossing_project`,再录入计划杂交/实际杂交到 `cross_entity`;计划杂交通过 `planned=true` 或 `planned_cross_id` 自关联体现。
|
||||
6. 录入 `cross_parent`,把 cross 与 parent germplasm 或 observation unit 关联起来。
|
||||
7. 录入 `pedigree_node` 和 `pedigree_edge`,表达 germplasm 的 parent/child/sibling 系谱关系。
|
||||
8. 录入 `seed_lot`,保存批次数量、单位、库位和项目归属。
|
||||
9. 录入 `seed_lot_content_mixture`,把 seed lot 与一个或多个 `germplasm`/`cross_entity` 连接起来。
|
||||
10. 后续出入库、分装、合并或转移时,录入 `seed_lot_transaction`,通过 `from_seed_lot_id` 与 `to_seed_lot_id` 追踪流向。
|
||||
|
||||
## 关键注意点
|
||||
|
||||
1. `germplasm.seedSource` 和 `germplasm.seedSourceDescription` 是种质主表上的描述字段,不等同于库存批次。
|
||||
2. 真正表示库存批次的是 `seed_lot`,而批次与种质的关系在 `seed_lot_content_mixture` 中。
|
||||
3. `seed_lot_content_mixture` 可以关联 `germplasm`,也可以关联 `cross_entity`,适合表达混合种子批次或由杂交产生的批次。
|
||||
4. `seed_lot_transaction` 同时有 `fromSeedLot` 与 `toSeedLot`,因此它表达的是 seed lot 到 seed lot 的流转关系,而不是 seed lot 到 germplasm 的直接关系。
|
||||
5. `plannedcross` 没有独立数据库表,统一使用 `cross_entity`,通过 `planned` 字段和 `planned_cross_id` 自关联表达。
|
||||
6. `germplasm_attribute_definition` 是属性定义,`germplasm_attribute_value` 是种质上的实际属性值,两者通过 `attribute_id` 连接。
|
||||
7. 系谱关系由 `pedigree_node` 和 `pedigree_edge` 表达;杂交流程由 `cross_entity` 和 `cross_parent` 表达,两者都可以回到 `germplasm` 主数据。
|
||||
210
docs/architecture/core-data-flow.md
Normal file
210
docs/architecture/core-data-flow.md
Normal file
@@ -0,0 +1,210 @@
|
||||
# Core 模块数据流与表关系
|
||||
|
||||
本文档分析 `brapi-java` 项目 core 模块的数据录入顺序、主表关系,以及初始化脚本中的实际数据流。
|
||||
|
||||
## 结论
|
||||
|
||||
Core 模块的数据主线是:
|
||||
|
||||
```text
|
||||
crop -> person -> program -> location -> trial -> season -> study -> study 附属信息
|
||||
```
|
||||
|
||||
`list` 是相对独立的列表能力,可以较早录入;如果需要绑定列表所有人,则依赖 `person`。
|
||||
|
||||
初始化脚本实际执行顺序是:
|
||||
|
||||
```text
|
||||
R__init_data_01_crops.sql
|
||||
R__init_data_02_lists.sql
|
||||
R__init_data_03_locations.sql
|
||||
R__init_data_04_people.sql
|
||||
R__init_data_05_programs.sql
|
||||
R__init_data_06_trials.sql
|
||||
R__init_data_07_seasons.sql
|
||||
R__init_data_08_studies.sql
|
||||
```
|
||||
|
||||
其中 `R__init_data_05_programs.sql` 会插入 program 负责人到 `person` 表,并回填部分 `location.program_id/crop_id`。
|
||||
|
||||
## 核心表说明
|
||||
|
||||
| 表 | 作用 | 主要上游依赖 | 主要下游 |
|
||||
| --- | --- | --- | --- |
|
||||
| `crop` | 作物字典,Core 主根数据之一 | 无 | `program`、`location`、`trial`、`study` |
|
||||
| `person` | 人员、联系人、负责人 | 无 | `program.lead_person_id`、`trial_contact`、`study_contact`、`list.list_owner_person_id` |
|
||||
| `program` | 育种项目/业务项目 | `crop`、可选 `person` | `trial`、`study`、`location` |
|
||||
| `location` | 地点/试验点 | 可选 `crop`、`program`、父级 `location`、`geojson` | `study` |
|
||||
| `trial` | 试验批次/试验项目 | `crop`、`program` | `study`、`trial_contact`、`trial_publication`、`trial_dataset_authorship` |
|
||||
| `season` | 季节/年度区间字典 | 无 | `study_season`、部分 observation |
|
||||
| `study` | 具体研究/试验实施单元 | `crop`、`program`、`trial`、`location` | `study_contact`、`study_season`、`study_data_link`、`study_observation_level` 等 |
|
||||
| `list` | 通用列表 | 可选 `person` | `list_item` |
|
||||
| `list_item` | 列表明细项 | `list` | 无 |
|
||||
|
||||
## 建议录入顺序
|
||||
|
||||
### 1. 录入基础字典
|
||||
|
||||
先录入 `crop` 和 `person`。
|
||||
|
||||
`crop` 是作物维度根数据,后续 `program`、`trial`、`study` 都会挂到它下面。`person` 是人员基础资料,后续会作为项目负责人、试验联系人、研究联系人、列表负责人使用。
|
||||
|
||||
### 2. 录入地点
|
||||
|
||||
录入 `location`,如果地点有坐标,需要先录入 `geojson` 和 `coordinate`。
|
||||
|
||||
地点可以先不绑定 `program/crop`,初始化脚本里就是先插入地点,再在 program 初始化阶段回填 `program_id` 和 `crop_id`。
|
||||
|
||||
### 3. 录入项目 Program
|
||||
|
||||
录入 `program` 时需要已有 `crop`。如果有负责人,需要已有 `person`。
|
||||
|
||||
程序层面 `ProgramEntity.setCrop(...)` 直接绑定作物;后续 trial/study 设置 program 时,会同步继承 program 的 crop。
|
||||
|
||||
### 4. 录入 Trial
|
||||
|
||||
录入 `trial` 时需要已有 `program` 和 `crop`。
|
||||
|
||||
`trial` 还可以同时录入:
|
||||
|
||||
```text
|
||||
trial_contact
|
||||
trial_dataset_authorship
|
||||
trial_publication
|
||||
trial_additional_info
|
||||
trial_external_references
|
||||
```
|
||||
|
||||
其中 `trial_contact` 是 `trial` 和 `person` 的多对多关系表。
|
||||
|
||||
### 5. 录入 Season
|
||||
|
||||
录入 `season`。它本身相对独立,但后续 `study` 会通过 `study_season` 关联多个 season。
|
||||
|
||||
### 6. 录入 Study
|
||||
|
||||
录入 `study` 时通常需要已有:
|
||||
|
||||
```text
|
||||
crop
|
||||
program
|
||||
trial
|
||||
location
|
||||
```
|
||||
|
||||
`study` 是 core 模块向 pheno/geno 数据扩展的关键节点。后续 observation、observation_unit、sample、plate、variantset 等很多模块都会引用 `study`。
|
||||
|
||||
### 7. 录入 Study 附属信息
|
||||
|
||||
录入 study 后,再录入依赖 `study_id` 的附属表:
|
||||
|
||||
```text
|
||||
study_contact
|
||||
study_data_link
|
||||
study_environment_parameter
|
||||
study_experimental_design
|
||||
study_growth_facility
|
||||
study_last_update
|
||||
study_observation_level
|
||||
study_season
|
||||
study_variable
|
||||
study_additional_info
|
||||
study_external_references
|
||||
```
|
||||
|
||||
## Core 数据流图
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["1. crop 作物字典"] --> C["3. program 项目"]
|
||||
B["2. person 人员"] --> C
|
||||
B --> L["list 列表负责人,可选"]
|
||||
L --> LI["list_item 列表项"]
|
||||
|
||||
G["geojson / coordinate 坐标"] --> D["2. location 地点"]
|
||||
A --> D
|
||||
C --> D
|
||||
D --> E["6. study 研究"]
|
||||
|
||||
C --> F["4. trial 试验"]
|
||||
A --> F
|
||||
B --> FC["trial_contact 试验联系人"]
|
||||
F --> FC
|
||||
F --> FP["trial_publication / trial_dataset_authorship"]
|
||||
|
||||
S["5. season 季节"] --> SS["study_season 研究季节"]
|
||||
F --> E
|
||||
C --> E
|
||||
A --> E
|
||||
E --> SS
|
||||
E --> SC["study_contact 研究联系人"]
|
||||
B --> SC
|
||||
E --> SA["study_data_link / environment / design / growth_facility / last_update / observation_level"]
|
||||
|
||||
E --> P1["pheno: observation_unit / observation"]
|
||||
E --> G1["geno: sample / plate / variantset"]
|
||||
```
|
||||
|
||||
## Core ER 关系图
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
crop ||--o{ program : "crop_id"
|
||||
crop ||--o{ location : "crop_id"
|
||||
crop ||--o{ trial : "crop_id"
|
||||
crop ||--o{ study : "crop_id"
|
||||
|
||||
person ||--o{ program : "lead_person_id"
|
||||
person ||--o{ trial_contact : "person_db_id"
|
||||
person ||--o{ study_contact : "person_db_id"
|
||||
person ||--o{ list : "list_owner_person_id"
|
||||
|
||||
program ||--o{ location : "program_id"
|
||||
program ||--o{ trial : "program_id"
|
||||
program ||--o{ study : "program_id"
|
||||
|
||||
location ||--o{ location : "parent_location_id"
|
||||
location ||--o{ study : "location_id"
|
||||
|
||||
trial ||--o{ study : "trial_id"
|
||||
trial ||--o{ trial_contact : "trial_db_id"
|
||||
trial ||--o{ trial_publication : "trial_id"
|
||||
trial ||--o{ trial_dataset_authorship : "trial_id"
|
||||
|
||||
study ||--o{ study_contact : "study_db_id"
|
||||
study ||--o{ study_season : "study_db_id"
|
||||
season ||--o{ study_season : "season_db_id"
|
||||
|
||||
study ||--o{ study_data_link : "study_id"
|
||||
study ||--o{ study_environment_parameter : "study_id"
|
||||
study ||--o{ study_experimental_design : "study_id"
|
||||
study ||--o{ study_growth_facility : "study_id"
|
||||
study ||--o{ study_last_update : "study_id"
|
||||
study ||--o{ study_observation_level : "study_id"
|
||||
|
||||
list ||--o{ list_item : "list_id"
|
||||
```
|
||||
|
||||
## API 与表的对应关系
|
||||
|
||||
| API | 主表 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `GET /brapi/v2/commoncropnames` | `crop` | 查询作物名称列表 |
|
||||
| `GET/POST/PUT /brapi/v2/people` | `person` | 人员查询、新增、修改;无删除接口 |
|
||||
| `GET/POST/PUT /brapi/v2/programs` | `program` | 项目依赖 crop,可关联 lead person |
|
||||
| `GET/POST/PUT /brapi/v2/locations` | `location` | 地点可关联 crop、program、parent location、geojson |
|
||||
| `GET/POST/PUT /brapi/v2/trials` | `trial` | 试验依赖 program/crop,可关联 contacts/publications |
|
||||
| `GET/PUT /brapi/v2/seasons` | `season` | 季节字典 |
|
||||
| `GET/POST/PUT /brapi/v2/studies` | `study` | 研究依赖 trial/program/crop/location |
|
||||
| `GET/POST/PUT /brapi/v2/lists` | `list`、`list_item` | 列表及列表项 |
|
||||
|
||||
## 关键注意点
|
||||
|
||||
1. `crop` 是最重要的根字典之一,许多业务表都有 `crop_id`。
|
||||
2. `program` 是承上启下的业务节点,它依赖 `crop`,并被 `trial`、`study`、`location` 引用。
|
||||
3. `trial` 是 study 的上级试验组织,`study` 是后续表型、基因型数据的核心入口。
|
||||
4. `person` 与 `trial/study` 是多对多关系,通过 `trial_contact`、`study_contact` 连接。
|
||||
5. `study_season` 是 `study` 和 `season` 的多对多关系。
|
||||
6. `additional_info` 和 `external_reference` 是通用扩展表,core 主表通过各自的 `*_additional_info`、`*_external_references` 关联它们。
|
||||
7. 初始化脚本中 `list` 早于 `person` 插入,是因为初始 list 数据主要使用文本 owner 字段;如果业务上要设置 `list_owner_person_id`,应先有 `person`。
|
||||
|
||||
67
docs/dev/01-core/01-crop.md
Normal file
67
docs/dev/01-core/01-crop.md
Normal file
@@ -0,0 +1,67 @@
|
||||
# 01 Core - crop 表录入说明
|
||||
|
||||
来源:`docs/requirements/01-core-data-entry-requirements.md`
|
||||
|
||||
## 录入目标
|
||||
|
||||
`crop` 记录项目涉及的作物范围,是 Core 上下文的起点。后续 program、trial、study、germplasm、observation_variable、genome_map 等数据都需要回到作物维度筛选和解释。
|
||||
|
||||
## 前置依赖和下游引用
|
||||
|
||||
| 类型 | 内容 |
|
||||
| --- | --- |
|
||||
| 表 | `crop` |
|
||||
| 前置依赖 | 无 |
|
||||
| 下游引用 | `program`、`location`、`trial`、`study`、`germplasm`、`observation_variable`、`genome_map` |
|
||||
|
||||
## 页面入口
|
||||
|
||||
| 页面 | 录入要求 |
|
||||
| --- | --- |
|
||||
| Crop 列表页 | 支持关键词查询、新增、编辑、查看详情、停用 |
|
||||
| Crop 新增/编辑页 | 轻量表单,重点录入作物名称 |
|
||||
| Crop 详情页 | 展示该作物下的项目、试验、研究、种质、图谱入口 |
|
||||
|
||||
列表页表格字段:`crop_name`、下游 program 数、下游 study 数、状态。
|
||||
|
||||
## 字段录入
|
||||
|
||||
| 字段 | 类型 | 控件 | 录入说明 | 校验/来源 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `id` | varchar(255) | 隐藏/只读 | 作物主键,新增时系统生成,也可导入时指定 | 必填、唯一 |
|
||||
| `auth_user_id` | varchar(255) | 隐藏 | 数据所属用户 | 登录上下文自动写入 |
|
||||
| `crop_name` | varchar(255) | 文本框 | 作物名称,用于所有下拉选择展示 | 必填、建议唯一 |
|
||||
|
||||
## 校验规则
|
||||
|
||||
1. `crop_name` 必填。
|
||||
2. `crop_name` 建议唯一,新增和编辑时需要做重复提示。
|
||||
3. 已被下游引用的作物不能物理删除,只能停用或提示引用关系。
|
||||
|
||||
## 接口能力
|
||||
|
||||
| 接口 | 用途 |
|
||||
| --- | --- |
|
||||
| `GET /crops` | crop 分页查询 |
|
||||
| `POST /crops` | 新增 crop |
|
||||
| `GET /crops/{id}` | crop 详情 |
|
||||
| `PUT /crops/{id}` | 编辑 crop |
|
||||
| `DELETE /crops/{id}` | 删除或停用 crop,需做引用检查 |
|
||||
| `GET /selectors/crops` | crop 搜索下拉,参数 `keyword` |
|
||||
|
||||
## 导入导出
|
||||
|
||||
1. 支持通过 Core 导入流程导入 `crop`。
|
||||
2. 模板列名使用数据库字段名。
|
||||
3. 可按 `id` 幂等更新;无 `id` 时新增。
|
||||
4. 列表页支持导出当前筛选结果,导出文件应包含 ID 和展示名称。
|
||||
|
||||
## 删除/停用
|
||||
|
||||
当 `crop` 已被 `program`、`location`、`trial`、`study`、`germplasm`、`observation_variable`、`genome_map` 引用时,不允许物理删除。后端需要返回引用对象类型和数量,前端弹窗展示后引导用户停用。
|
||||
|
||||
## 验收点
|
||||
|
||||
1. Crop 列表页支持分页、关键词搜索和基础筛选。
|
||||
2. 外键选择器展示作物名称,提交保存 `crop.id`。
|
||||
3. 被引用的 crop 删除失败时,前端展示引用详情。
|
||||
76
docs/dev/01-core/02-person.md
Normal file
76
docs/dev/01-core/02-person.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# 01 Core - person 表录入说明
|
||||
|
||||
来源:`docs/requirements/01-core-data-entry-requirements.md`
|
||||
|
||||
## 录入目标
|
||||
|
||||
`person` 记录项目负责人、trial 联系人、study 联系人和 list owner。它支撑责任追踪、联系人展示、权限和通知。
|
||||
|
||||
## 前置依赖和下游引用
|
||||
|
||||
| 类型 | 内容 |
|
||||
| --- | --- |
|
||||
| 表 | `person` |
|
||||
| 前置依赖 | 无 |
|
||||
| 下游引用 | `program.lead_person_id`、`trial_contact`、`study_contact`、`list.list_owner_person_id` |
|
||||
|
||||
## 页面入口
|
||||
|
||||
| 页面 | 录入要求 |
|
||||
| --- | --- |
|
||||
| Person 列表页 | 支持姓名、邮箱、机构筛选;新增、编辑、查看详情、停用 |
|
||||
| Person 新增/编辑页 | 使用联系人表单维护人员信息 |
|
||||
| Person 详情页 | 展示负责的 program、参与的 trial/study、拥有的 list |
|
||||
|
||||
列表页表格字段:姓名、邮箱、电话、机构、负责项目数、参与 study 数。
|
||||
|
||||
## 字段录入
|
||||
|
||||
| 字段 | 类型 | 控件 | 录入说明 | 校验/来源 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `id` | varchar(255) | 隐藏/只读 | 人员主键 | 必填、唯一 |
|
||||
| `auth_user_id` | varchar(255) | 隐藏 | 数据所属用户 | 登录上下文自动写入 |
|
||||
| `description` | varchar(255) | 多行文本 | 人员说明、职责补充 | 可选 |
|
||||
| `email_address` | varchar(255) | 邮箱输入框 | 邮箱地址 | 邮箱格式校验,建议唯一 |
|
||||
| `first_name` | varchar(255) | 文本框 | 名 | 与 `last_name` 至少填写一个 |
|
||||
| `institute_name` | varchar(255) | 文本框/选择器 | 所属机构 | 可选 |
|
||||
| `last_name` | varchar(255) | 文本框 | 姓 | 与 `first_name` 至少填写一个 |
|
||||
| `mailing_address` | varchar(255) | 多行文本 | 通讯地址 | 可选 |
|
||||
| `middle_name` | varchar(255) | 文本框 | 中间名 | 可选 |
|
||||
| `phone_number` | varchar(255) | 电话输入框 | 联系电话 | 可选,格式提示 |
|
||||
| `userid` | varchar(255) | 文本框 | 外部用户 ID 或登录名 | 可选,建议唯一 |
|
||||
|
||||
## 校验规则
|
||||
|
||||
1. 姓名必填,`first_name` 与 `last_name` 至少填写一个。
|
||||
2. `email_address` 需要校验邮箱格式。
|
||||
3. 同一邮箱不建议重复录入。
|
||||
4. 作为负责人或联系人被引用时,不允许物理删除。
|
||||
|
||||
## 接口能力
|
||||
|
||||
| 接口 | 用途 |
|
||||
| --- | --- |
|
||||
| `GET /persons` | person 分页查询 |
|
||||
| `POST /persons` | 新增 person |
|
||||
| `GET /persons/{id}` | person 详情 |
|
||||
| `PUT /persons/{id}` | 编辑 person |
|
||||
| `DELETE /persons/{id}` | 删除或停用 person,需做引用检查 |
|
||||
| `GET /selectors/persons` | person 搜索下拉,参数 `keyword`、`instituteName` |
|
||||
|
||||
## 导入导出
|
||||
|
||||
1. 支持通过 Core 导入流程导入 `person`。
|
||||
2. 模板列名使用数据库字段名。
|
||||
3. 可按 `id` 幂等更新;无 `id` 时新增。
|
||||
4. 列表页支持导出当前筛选结果,导出文件应包含 ID 和展示名称。
|
||||
|
||||
## 删除/停用
|
||||
|
||||
当 `person` 已作为 program 负责人、trial 联系人、study 联系人或 list owner 时,不允许物理删除。后端需要返回引用对象类型和数量,前端展示后引导用户停用。
|
||||
|
||||
## 验收点
|
||||
|
||||
1. Person 列表页支持分页、关键词搜索和机构筛选。
|
||||
2. 邮箱格式错误时不能提交。
|
||||
3. 作为负责人或联系人被引用的 person 删除失败时,前端展示引用详情。
|
||||
77
docs/dev/01-core/03-program.md
Normal file
77
docs/dev/01-core/03-program.md
Normal file
@@ -0,0 +1,77 @@
|
||||
# 01 Core - program 表录入说明
|
||||
|
||||
来源:`docs/requirements/01-core-data-entry-requirements.md`
|
||||
|
||||
## 录入目标
|
||||
|
||||
`program` 表示长期育种项目,是多个 trial、study、材料、样本和结果的聚合维度。创建 program 时必须明确所属 crop,可选负责人。
|
||||
|
||||
## 前置依赖和下游引用
|
||||
|
||||
| 类型 | 内容 |
|
||||
| --- | --- |
|
||||
| 表 | `program` |
|
||||
| 前置依赖 | `crop`,可选 `person` |
|
||||
| 下游引用 | `trial`、`study`、`location`、`crossing_project`、`seed_lot`、`plate`、`sample` |
|
||||
|
||||
## 页面入口
|
||||
|
||||
| 页面 | 录入要求 |
|
||||
| --- | --- |
|
||||
| Program 列表页 | 支持 crop、负责人、program_type、关键词筛选;新增、编辑、查看详情、停用 |
|
||||
| Program 新增/编辑页 | 分为“基本信息”和“负责人/作物”两组 |
|
||||
| Program 详情页 | Tab 展示 trial、study、location、seed lot、sample |
|
||||
|
||||
列表页表格字段:`name`、`abbreviation`、crop、负责人、trial 数、study 数。
|
||||
|
||||
## 字段录入
|
||||
|
||||
| 字段 | 类型 | 控件 | 录入说明 | 校验/来源 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `id` | varchar(255) | 隐藏/只读 | 项目主键 | 必填、唯一 |
|
||||
| `auth_user_id` | varchar(255) | 隐藏 | 数据所属用户 | 登录上下文自动写入 |
|
||||
| `abbreviation` | varchar(255) | 文本框 | 项目缩写 | 可选 |
|
||||
| `documentationurl` | varchar(255) | URL 输入框 | 项目文档链接 | URL 格式校验 |
|
||||
| `funding_information` | varchar(255) | 多行文本 | 经费来源说明 | 可选 |
|
||||
| `name` | varchar(255) | 文本框 | 项目名称 | 必填 |
|
||||
| `objective` | varchar(255) | 多行文本 | 项目目标 | 可选 |
|
||||
| `program_type` | integer | 下拉框 | 项目类型枚举 | 可选,按 BrAPI 枚举 |
|
||||
| `crop_id` | varchar(255) | 作物选择器 | 所属作物 | 必选,来源 `crop.id` |
|
||||
| `lead_person_id` | varchar(255) | 人员选择器 | 项目负责人 | 可选,来源 `person.id` |
|
||||
|
||||
## 校验规则
|
||||
|
||||
1. `name` 必填。
|
||||
2. `crop_id` 必选,且必须引用已存在的 `crop.id`。
|
||||
3. 选择负责人时,`lead_person_id` 必须引用已存在的 `person.id`。
|
||||
4. `documentationurl` 需要做 URL 格式校验。
|
||||
|
||||
## 接口能力
|
||||
|
||||
| 接口 | 用途 |
|
||||
| --- | --- |
|
||||
| `GET /programs` | program 分页查询 |
|
||||
| `POST /programs` | 新增 program |
|
||||
| `GET /programs/{id}` | program 详情 |
|
||||
| `PUT /programs/{id}` | 编辑 program |
|
||||
| `DELETE /programs/{id}` | 删除或停用 program,需做引用检查 |
|
||||
| `GET /programs/{id}/trials` | 查询 program 下 trial |
|
||||
| `GET /programs/{id}/studies` | 查询 program 下 study |
|
||||
| `GET /selectors/programs` | program 搜索下拉,参数 `keyword`、`cropId` |
|
||||
|
||||
## 导入导出
|
||||
|
||||
1. 支持通过 Core 导入流程导入 `program`。
|
||||
2. 外键列支持填写 ID;可额外支持名称匹配,但名称重复时必须报错。
|
||||
3. 可按 `id` 幂等更新;无 `id` 时新增。
|
||||
4. 列表页支持导出当前筛选结果,导出文件应包含 ID 和展示名称。
|
||||
|
||||
## 删除/停用
|
||||
|
||||
当 `program` 已有关联 `trial`、`study`、`location`、`crossing_project`、`seed_lot`、`plate`、`sample` 时,不允许物理删除。后端需要返回引用对象类型和数量,前端展示后引导用户停用。
|
||||
|
||||
## 验收点
|
||||
|
||||
1. 创建 program 时必须选择 crop。
|
||||
2. 负责人选择器只能保存已存在的 person ID。
|
||||
3. Program 详情页能进入该项目下的 trial 和 study。
|
||||
86
docs/dev/01-core/04-location.md
Normal file
86
docs/dev/01-core/04-location.md
Normal file
@@ -0,0 +1,86 @@
|
||||
# 01 Core - location 表录入说明
|
||||
|
||||
来源:`docs/requirements/01-core-data-entry-requirements.md`
|
||||
|
||||
## 录入目标
|
||||
|
||||
`location` 记录试验实施地点,可作为公共地点,也可绑定 program 和 crop。study 创建时必须选择实施地点。
|
||||
|
||||
## 前置依赖和下游引用
|
||||
|
||||
| 类型 | 内容 |
|
||||
| --- | --- |
|
||||
| 表 | `location` |
|
||||
| 前置依赖 | 可选 `crop`、`program`、父级 `location`、坐标 |
|
||||
| 下游引用 | `study`、`seed_lot` |
|
||||
|
||||
## 页面入口
|
||||
|
||||
| 页面 | 录入要求 |
|
||||
| --- | --- |
|
||||
| Location 列表页 | 支持 crop、program、location_type、country、关键词筛选;新增、编辑、查看详情、停用、地图查看 |
|
||||
| Location 新增/编辑页 | 包含基本信息、行政区、坐标、父级地点 |
|
||||
| Location 详情页 | 展示该地点下的 study 和 seed lot |
|
||||
|
||||
列表页支持地图/表格两种视图。列表页表格字段:`location_name`、location_type、country、program、crop、父级地点。
|
||||
|
||||
## 字段录入
|
||||
|
||||
| 字段 | 类型 | 控件 | 录入说明 | 校验/来源 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `id` | varchar(255) | 隐藏/只读 | 地点主键 | 必填、唯一 |
|
||||
| `auth_user_id` | varchar(255) | 隐藏 | 数据所属用户 | 登录上下文自动写入 |
|
||||
| `abbreviation` | varchar(255) | 文本框 | 地点缩写 | 可选 |
|
||||
| `coordinate_description` | varchar(255) | 多行文本 | 坐标描述 | 可选 |
|
||||
| `coordinate_uncertainty` | varchar(255) | 文本框 | 坐标不确定性 | 可选 |
|
||||
| `country_code` | varchar(255) | 国家代码选择器 | 国家代码 | 可选,建议 ISO 代码 |
|
||||
| `country_name` | varchar(255) | 文本框 | 国家名称 | 可选,可由国家代码带出 |
|
||||
| `documentationurl` | varchar(255) | URL 输入框 | 地点文档链接 | URL 格式校验 |
|
||||
| `environment_type` | varchar(255) | 下拉框/文本框 | 环境类型 | 可选 |
|
||||
| `exposure` | varchar(255) | 文本框 | 暴露条件 | 可选 |
|
||||
| `institute_address` | varchar(255) | 多行文本 | 机构地址 | 可选 |
|
||||
| `institute_name` | varchar(255) | 文本框 | 机构名称 | 可选 |
|
||||
| `location_name` | varchar(255) | 文本框 | 地点名称 | 必填 |
|
||||
| `location_type` | varchar(255) | 下拉框 | 地点类型,如 field、greenhouse、storage | 可选 |
|
||||
| `site_status` | varchar(255) | 下拉框 | 地点状态 | 可选 |
|
||||
| `slope` | varchar(255) | 文本框 | 坡度 | 可选 |
|
||||
| `topography` | varchar(255) | 文本框 | 地形 | 可选 |
|
||||
| `coordinates_id` | varchar(255) | 坐标选择器/地图取点 | 坐标对象 | 可选,来源 `geojson/coordinate` |
|
||||
| `crop_id` | varchar(255) | 作物选择器 | 关联作物 | 可选,来源 `crop.id` |
|
||||
| `parent_location_id` | varchar(255) | 地点选择器 | 父级地点 | 可选,不能选择自己 |
|
||||
| `program_id` | varchar(255) | 项目选择器 | 所属项目 | 可选,来源 `program.id` |
|
||||
|
||||
## 校验规则
|
||||
|
||||
1. `location_name` 必填。
|
||||
2. 坐标字段格式合法。
|
||||
3. `parent_location_id` 不能选择自己。
|
||||
4. 选择 program 后可自动带出 crop,但允许地点作为公共地点不绑定 program。
|
||||
5. `documentationurl` 需要做 URL 格式校验。
|
||||
|
||||
## 接口能力
|
||||
|
||||
| 接口 | 用途 |
|
||||
| --- | --- |
|
||||
| `GET /locations` | location 分页查询 |
|
||||
| `POST /locations` | 新增 location |
|
||||
| `GET /locations/{id}` | location 详情 |
|
||||
| `PUT /locations/{id}` | 编辑 location |
|
||||
| `GET /selectors/locations` | location 搜索下拉,参数 `keyword`、`programId`、`cropId`、`locationType` |
|
||||
|
||||
## 导入导出
|
||||
|
||||
1. 支持通过 Core 导入流程导入 `location`。
|
||||
2. 导入时如果 `parent_location_id` 指向自己,应报错并指出行号。
|
||||
3. 外键列支持填写 ID;可额外支持名称匹配,但名称重复时必须报错。
|
||||
4. 列表页支持导出当前筛选结果,导出文件应包含 ID 和展示名称。
|
||||
|
||||
## 删除/停用
|
||||
|
||||
当 `location` 已被 `study` 或 `seed_lot` 引用时,不允许物理删除。后端需要返回引用对象类型和数量,前端展示后引导用户停用。
|
||||
|
||||
## 验收点
|
||||
|
||||
1. Location 列表页支持表格视图和地图查看。
|
||||
2. 父级地点不能选择自己。
|
||||
3. Study 新增时 location 选择器可按 program/crop 过滤。
|
||||
78
docs/dev/01-core/05-trial.md
Normal file
78
docs/dev/01-core/05-trial.md
Normal file
@@ -0,0 +1,78 @@
|
||||
# 01 Core - trial 表录入说明
|
||||
|
||||
来源:`docs/requirements/01-core-data-entry-requirements.md`
|
||||
|
||||
## 录入目标
|
||||
|
||||
`trial` 表示某一批试验、区域试验或年度试验,位于 program 与 study 之间。创建 trial 时选择 program,并自动带出或校验 crop。
|
||||
|
||||
## 前置依赖和下游引用
|
||||
|
||||
| 类型 | 内容 |
|
||||
| --- | --- |
|
||||
| 表 | `trial` |
|
||||
| 前置依赖 | `program`、`crop` |
|
||||
| 下游引用 | `study`、`observation_unit`、`observation`、`plate`、`sample` |
|
||||
|
||||
## 页面入口
|
||||
|
||||
| 页面 | 录入要求 |
|
||||
| --- | --- |
|
||||
| Trial 列表页 | 支持 crop、program、active、起止日期、关键词筛选;新增、编辑、查看详情、停用 |
|
||||
| Trial 新增/编辑页 | 包含基本信息、项目作物、联系人、出版物四个区域 |
|
||||
| Trial 详情页 | 展示 study 列表和 phenotyping/genotyping 入口 |
|
||||
|
||||
列表页表格字段:`trial_name`、program、crop、`start_date`、`end_date`、active。
|
||||
|
||||
## 字段录入
|
||||
|
||||
| 字段 | 类型 | 控件 | 录入说明 | 校验/来源 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `id` | varchar(255) | 隐藏/只读 | 试验主键 | 必填、唯一 |
|
||||
| `auth_user_id` | varchar(255) | 隐藏 | 数据所属用户 | 登录上下文自动写入 |
|
||||
| `active` | boolean | 开关 | 是否启用 | 默认启用 |
|
||||
| `documentationurl` | varchar(255) | URL 输入框 | 试验文档链接 | URL 格式校验 |
|
||||
| `end_date` | timestamp | 日期选择器 | 结束日期 | 不早于 `start_date` |
|
||||
| `start_date` | timestamp | 日期选择器 | 开始日期 | 可选 |
|
||||
| `trial_description` | varchar(255) | 多行文本 | 试验描述 | 可选 |
|
||||
| `trial_name` | varchar(255) | 文本框 | 试验名称 | 必填 |
|
||||
| `trialpui` | varchar(255) | 文本框 | 试验永久标识 | 可选,建议唯一 |
|
||||
| `crop_id` | varchar(255) | 作物选择器 | 所属作物 | 必选,来源 `crop.id` |
|
||||
| `program_id` | varchar(255) | 项目选择器 | 所属项目 | 必选,来源 `program.id` |
|
||||
|
||||
## 校验规则
|
||||
|
||||
1. `trial_name` 必填。
|
||||
2. `program_id` 必选且必须存在。
|
||||
3. `crop_id` 必选且必须与 `program.crop_id` 一致。
|
||||
4. `end_date` 不能早于 `start_date`。
|
||||
5. `documentationurl` 需要做 URL 格式校验。
|
||||
|
||||
## 接口能力
|
||||
|
||||
| 接口 | 用途 |
|
||||
| --- | --- |
|
||||
| `GET /trials` | trial 分页查询 |
|
||||
| `POST /trials` | 新增 trial |
|
||||
| `GET /trials/{id}` | trial 详情 |
|
||||
| `PUT /trials/{id}` | 编辑 trial |
|
||||
| `DELETE /trials/{id}` | 删除或停用 trial,需检查 study 引用 |
|
||||
| `GET /trials/{id}/studies` | 查询 trial 下 study |
|
||||
| `GET /selectors/trials` | trial 搜索下拉,参数 `keyword`、`programId`、`cropId`、`active` |
|
||||
|
||||
## 导入导出
|
||||
|
||||
1. 支持通过 Core 导入流程导入 `trial`。
|
||||
2. 外键列支持填写 ID;可额外支持名称匹配,但名称重复时必须报错。
|
||||
3. 导入时必须校验 trial 的 crop 与 program 的 crop 一致。
|
||||
4. 列表页支持导出当前筛选结果,导出文件应包含 ID 和展示名称。
|
||||
|
||||
## 删除/停用
|
||||
|
||||
当 `trial` 已被 `study`、`observation_unit`、`observation`、`plate`、`sample` 引用时,不允许物理删除。后端需要返回引用对象类型和数量,前端展示后引导用户停用。
|
||||
|
||||
## 验收点
|
||||
|
||||
1. 创建 trial 时,选择 program 后自动带出 crop。
|
||||
2. Trial 列表页支持按 crop、program、active、日期筛选。
|
||||
3. 已被 study 引用的 trial 不能直接删除。
|
||||
66
docs/dev/01-core/06-season.md
Normal file
66
docs/dev/01-core/06-season.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# 01 Core - season 表录入说明
|
||||
|
||||
来源:`docs/requirements/01-core-data-entry-requirements.md`
|
||||
|
||||
## 录入目标
|
||||
|
||||
`season` 记录年度和季节,用于 study 的季节绑定,以及部分 observation 的季节维度统计。
|
||||
|
||||
## 前置依赖和下游引用
|
||||
|
||||
| 类型 | 内容 |
|
||||
| --- | --- |
|
||||
| 表 | `season` |
|
||||
| 前置依赖 | 无 |
|
||||
| 下游引用 | `study_season`、部分 `observation` |
|
||||
|
||||
## 页面入口
|
||||
|
||||
| 页面 | 录入要求 |
|
||||
| --- | --- |
|
||||
| Season 列表页 | 支持 year、season 筛选;新增、编辑、查看详情、停用 |
|
||||
| Season 新增/编辑页 | 简单表单 |
|
||||
| Season 详情页 | 展示关联 study |
|
||||
|
||||
列表页表格字段:`year`、`season`、关联 study 数。
|
||||
|
||||
## 字段录入
|
||||
|
||||
| 字段 | 类型 | 控件 | 录入说明 | 校验/来源 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `id` | varchar(255) | 隐藏/只读 | 季节主键 | 必填、唯一 |
|
||||
| `auth_user_id` | varchar(255) | 隐藏 | 数据所属用户 | 登录上下文自动写入 |
|
||||
| `season` | varchar(255) | 文本框/下拉框 | 季节名称,如 Spring、Summer | 必填 |
|
||||
| `year` | integer | 年份选择器 | 年份 | 必填,四位年份 |
|
||||
|
||||
## 校验规则
|
||||
|
||||
1. `season` 必填。
|
||||
2. `year` 必填,使用四位年份。
|
||||
3. 同一年份内 season 名称不建议重复。
|
||||
|
||||
## 接口能力
|
||||
|
||||
| 接口 | 用途 |
|
||||
| --- | --- |
|
||||
| `GET /seasons` | season 查询 |
|
||||
| `POST /seasons` | 新增 season |
|
||||
| `PUT /seasons/{id}` | 编辑 season |
|
||||
| `GET /selectors/seasons` | season 搜索下拉,参数 `year`、`keyword` |
|
||||
|
||||
## 导入导出
|
||||
|
||||
1. 支持通过 Core 导入流程导入 `season`。
|
||||
2. 模板列名使用数据库字段名。
|
||||
3. 可按 `id` 幂等更新;无 `id` 时新增。
|
||||
4. 列表页支持导出当前筛选结果,导出文件应包含 ID 和展示名称。
|
||||
|
||||
## 删除/停用
|
||||
|
||||
当 `season` 已被 `study_season` 或 `observation` 引用时,不允许物理删除。后端需要返回引用对象类型和数量,前端展示后引导用户停用。
|
||||
|
||||
## 验收点
|
||||
|
||||
1. Season 列表页支持按 year 和 season 筛选。
|
||||
2. 同一年份内重复季节名称需要提示。
|
||||
3. Study 新增/编辑页可以多选 season。
|
||||
103
docs/dev/01-core/07-study.md
Normal file
103
docs/dev/01-core/07-study.md
Normal file
@@ -0,0 +1,103 @@
|
||||
# 01 Core - study 表录入说明
|
||||
|
||||
来源:`docs/requirements/01-core-data-entry-requirements.md`
|
||||
|
||||
## 录入目标
|
||||
|
||||
`study` 是一次真正落地执行的试验,是 Core 模块最关键的上下文单元。后续 observation_unit、event、observation、plate、sample、variantset 都会直接或间接挂到 study 上。
|
||||
|
||||
## 前置依赖和下游引用
|
||||
|
||||
| 类型 | 内容 |
|
||||
| --- | --- |
|
||||
| 表 | `study` |
|
||||
| 前置依赖 | `crop`、`program`、`trial`、`location`,可选 `season` |
|
||||
| 下游引用 | `observation_unit`、`event`、`observation`、`plate`、`sample`、`variantset` |
|
||||
|
||||
## 页面入口
|
||||
|
||||
| 页面 | 录入要求 |
|
||||
| --- | --- |
|
||||
| Study 列表页 | 支持 crop、program、trial、location、season、active、study_type、关键词筛选;新增、编辑、查看详情、停用 |
|
||||
| Study 新增/编辑页 | 分组表单:基本信息、上下文、地点季节、实验设计、联系人 |
|
||||
| Study 工作台 | 点击 study 名称或“进入工作台”后进入,展示下游业务入口和聚合数量 |
|
||||
|
||||
Study 列表页表格字段:`study_name`、`study_code`、program、trial、location、`start_date`、`end_date`、active。
|
||||
|
||||
## 字段录入
|
||||
|
||||
| 字段 | 类型 | 控件 | 录入说明 | 校验/来源 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `id` | varchar(255) | 隐藏/只读 | study 主键 | 必填、唯一 |
|
||||
| `auth_user_id` | varchar(255) | 隐藏 | 数据所属用户 | 登录上下文自动写入 |
|
||||
| `active` | boolean | 开关 | 是否启用 | 默认启用 |
|
||||
| `cultural_practices` | varchar(255) | 多行文本 | 栽培管理说明 | 可选 |
|
||||
| `documentationurl` | varchar(255) | URL 输入框 | study 文档链接 | URL 格式校验 |
|
||||
| `end_date` | timestamp | 日期选择器 | 结束日期 | 不早于 `start_date` |
|
||||
| `license` | varchar(255) | 文本框 | 数据许可证 | 可选 |
|
||||
| `observation_units_description` | varchar(255) | 多行文本 | 观测单元说明 | 可选 |
|
||||
| `start_date` | timestamp | 日期选择器 | 开始日期 | 可选 |
|
||||
| `study_code` | varchar(255) | 文本框 | study 编码 | 可选,建议同项目内唯一 |
|
||||
| `study_description` | varchar(255) | 多行文本 | study 描述 | 可选 |
|
||||
| `study_name` | varchar(255) | 文本框 | study 名称 | 必填 |
|
||||
| `studypui` | varchar(255) | 文本框 | study 永久标识 | 可选,建议唯一 |
|
||||
| `study_type` | varchar(255) | 下拉框 | study 类型 | 可选 |
|
||||
| `crop_id` | varchar(255) | 作物选择器 | 所属作物 | 必选,来源 `crop.id` |
|
||||
| `location_id` | varchar(255) | 地点选择器 | 实施地点 | 必选,来源 `location.id` |
|
||||
| `program_id` | varchar(255) | 项目选择器 | 所属项目 | 必选,来源 `program.id` |
|
||||
| `trial_id` | varchar(255) | 试验选择器 | 所属 trial | 必选,来源 `trial.id` |
|
||||
|
||||
## 新增/编辑分区
|
||||
|
||||
| 区域 | 字段/功能 | 要求 |
|
||||
| --- | --- | --- |
|
||||
| 基本信息 | study_name、study_code、study_type、active、start_date、end_date | study_name 必填;结束日期不早于开始日期 |
|
||||
| 上下文 | crop、program、trial、location、season | program、trial、crop、location 必选;program -> trial 联动 |
|
||||
| 说明信息 | study_description、cultural_practices、observation_units_description、license、documentationurl | URL 格式校验 |
|
||||
| 联系人 | study_contact | 多选 person,可增删 |
|
||||
| 季节 | study_season | 可多选 season |
|
||||
| 提交后 | 保存成功进入 Study 工作台 | 新建成功后自动跳转 |
|
||||
|
||||
## 校验规则
|
||||
|
||||
1. `study_name` 必填。
|
||||
2. `program_id`、`trial_id`、`crop_id`、`location_id` 必选且必须存在。
|
||||
3. `trial_id` 必须属于所选 `program_id`。
|
||||
4. 选择 program 后过滤 trial;选择 trial 后自动带出 crop。
|
||||
5. `end_date` 不能早于 `start_date`。
|
||||
6. 编辑 study 时,如果已有 observation_unit、sample、observation,变更 program/trial/crop/location 前必须二次确认并检查一致性。
|
||||
7. study 是下游核心引用,删除必须强提示。
|
||||
|
||||
## 接口能力
|
||||
|
||||
| 接口 | 用途 |
|
||||
| --- | --- |
|
||||
| `GET /studies` | study 分页查询 |
|
||||
| `POST /studies` | 新增 study |
|
||||
| `GET /studies/{id}` | study 详情 |
|
||||
| `PUT /studies/{id}` | 编辑 study |
|
||||
| `DELETE /studies/{id}` | 删除或停用 study,需检查下游引用 |
|
||||
| `GET /studies/{id}/workbench` | Study 工作台聚合信息 |
|
||||
| `GET /studies/{id}/contacts` | study 联系人 |
|
||||
| `GET /studies/{id}/seasons` | study 季节 |
|
||||
| `GET /selectors/studies` | study 搜索下拉,参数 `keyword`、`programId`、`trialId`、`locationId`、`active` |
|
||||
|
||||
## 导入导出
|
||||
|
||||
1. 支持通过 Core 导入流程导入 `study`。
|
||||
2. 导入时必须校验字段完整性、必填、格式、外键是否存在、联动关系是否一致。
|
||||
3. 如果 trial 不属于 program,应报错并指出行号。
|
||||
4. study 导出应支持工作台摘要,包括联系人、season、下游数据数量。
|
||||
5. 大数据量导出时应走异步任务。
|
||||
|
||||
## 删除/停用
|
||||
|
||||
当 `study` 已被 `observation_unit`、`event`、`observation`、`plate`、`sample`、`variantset` 引用时,不允许物理删除。后端需要返回引用对象类型和数量,前端弹窗展示后引导用户停用。
|
||||
|
||||
## 验收点
|
||||
|
||||
1. Study 列表页支持按 crop、program、trial、location、season、active 筛选。
|
||||
2. 点击 study 名称或“进入工作台”按钮后进入 Study 工作台。
|
||||
3. Study 工作台可以看到观测单元、表型、样本、基因型入口。
|
||||
4. study 创建成功后自动进入 Study 工作台。
|
||||
5. 已被 observation_unit、sample、observation 引用的 study 不能直接删除。
|
||||
73
docs/dev/01-core/08-list.md
Normal file
73
docs/dev/01-core/08-list.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# 01 Core - list 表录入说明
|
||||
|
||||
来源:`docs/requirements/01-core-data-entry-requirements.md`
|
||||
|
||||
## 录入目标
|
||||
|
||||
`list` 是通用分组列表的主表,用于维护一组业务对象或文本项。`list` 负责列表基本信息,`list_item` 负责列表明细。
|
||||
|
||||
## 前置依赖和下游引用
|
||||
|
||||
| 类型 | 内容 |
|
||||
| --- | --- |
|
||||
| 表 | `list` |
|
||||
| 前置依赖 | 可选 `person` |
|
||||
| 下游引用 | `list_item`、业务查询和分组 |
|
||||
|
||||
## 页面入口
|
||||
|
||||
| 页面 | 录入要求 |
|
||||
| --- | --- |
|
||||
| List 列表页 | 展示 list,支持查询、新增、编辑 |
|
||||
| List 详情页 | 顶部展示 list 基本信息,下方内嵌 list_item 表格 |
|
||||
|
||||
List 详情页支持新增 item、批量导入 item、删除 item、排序。同一 list 内 item 不重复。
|
||||
|
||||
## 字段录入
|
||||
|
||||
| 字段 | 类型 | 控件 | 录入说明 | 校验/来源 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `id` | varchar(255) | 隐藏/只读 | 列表主键 | 必填、唯一 |
|
||||
| `auth_user_id` | varchar(255) | 隐藏 | 数据所属用户 | 登录上下文自动写入 |
|
||||
| `date_created` | timestamp | 只读日期时间 | 创建时间 | 系统自动写入 |
|
||||
| `date_modified` | timestamp | 只读日期时间 | 修改时间 | 系统自动更新 |
|
||||
| `description` | varchar(255) | 多行文本 | 列表描述 | 可选 |
|
||||
| `list_name` | varchar(255) | 文本框 | 列表名称 | 必填 |
|
||||
| `list_owner_name` | varchar(255) | 文本框 | 列表 owner 名称 | 可选,可由 owner person 带出 |
|
||||
| `list_source` | varchar(255) | 文本框 | 列表来源 | 可选 |
|
||||
| `list_type` | integer | 下拉框 | 列表类型 | 必填,按 BrAPI 枚举 |
|
||||
| `list_owner_person_id` | varchar(255) | 人员选择器 | 列表 owner | 可选,来源 `person.id` |
|
||||
|
||||
## 校验规则
|
||||
|
||||
1. `list_name` 必填。
|
||||
2. `list_type` 必填,按 BrAPI 枚举选择。
|
||||
3. 如果绑定 owner,`list_owner_person_id` 必须引用已存在的 `person.id`。
|
||||
4. `date_created`、`date_modified` 由系统维护,前端只读。
|
||||
|
||||
## 接口能力
|
||||
|
||||
| 接口 | 用途 |
|
||||
| --- | --- |
|
||||
| `GET /lists` | list 查询 |
|
||||
| `POST /lists` | 新增 list |
|
||||
| `PUT /lists/{id}` | 编辑 list |
|
||||
| `POST /lists/{id}/items` | 给 list 添加 item |
|
||||
| `DELETE /lists/{id}/items/{itemId}` | 删除 list item |
|
||||
|
||||
## 导入导出
|
||||
|
||||
1. 支持通过 Core 导入流程导入 `list`。
|
||||
2. 模板列名使用数据库字段名。
|
||||
3. 外键列支持填写 `list_owner_person_id`;可额外支持名称匹配,但名称重复时必须报错。
|
||||
4. 列表页支持导出当前筛选结果,导出文件应包含 ID 和展示名称。
|
||||
|
||||
## 删除/停用
|
||||
|
||||
当 `list` 已有 `list_item` 时,删除前必须提示。可以先清空明细再删除 list。
|
||||
|
||||
## 验收点
|
||||
|
||||
1. List 详情页顶部展示 list 基本信息,下方展示 list_item 表格。
|
||||
2. 同一 list 内 item 不重复。
|
||||
3. 绑定 owner 时只能保存已存在的 person ID。
|
||||
62
docs/dev/01-core/09-list_item.md
Normal file
62
docs/dev/01-core/09-list_item.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# 01 Core - list_item 表录入说明
|
||||
|
||||
来源:`docs/requirements/01-core-data-entry-requirements.md`
|
||||
|
||||
## 录入目标
|
||||
|
||||
`list_item` 是 list 的明细项,是“给 list 添加成员”这个动作留下的记录。它可以保存目标对象 ID 或文本值,用于业务查询和分组。
|
||||
|
||||
## 前置依赖和下游引用
|
||||
|
||||
| 类型 | 内容 |
|
||||
| --- | --- |
|
||||
| 表 | `list_item` |
|
||||
| 前置依赖 | `list` |
|
||||
| 下游引用 | 业务查询和分组 |
|
||||
|
||||
## 页面入口
|
||||
|
||||
| 页面 | 录入要求 |
|
||||
| --- | --- |
|
||||
| List 详情页 | 在 list 基本信息下方内嵌 list_item 表格 |
|
||||
| 批量导入 | 支持给指定 list 批量导入 item |
|
||||
|
||||
新增 item 时可选择目标类型,例如 germplasm、study、sample。
|
||||
|
||||
## 字段录入
|
||||
|
||||
| 字段 | 类型 | 控件 | 录入说明 | 校验/来源 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `id` | varchar(255) | 隐藏/只读 | 列表项主键 | 必填、唯一 |
|
||||
| `item` | varchar(255) | 文本框/对象选择器 | 列表项值,可存目标对象 ID 或文本 | 必填,同一 list 内不重复 |
|
||||
| `list_id` | varchar(255) | List 选择器 | 所属列表 | 必选,来源 `list.id` |
|
||||
|
||||
## 校验规则
|
||||
|
||||
1. `list_id` 必选,且必须引用已存在的 `list.id`。
|
||||
2. `item` 必填。
|
||||
3. 同一 `list_id` 内 `item` 不应重复。
|
||||
|
||||
## 接口能力
|
||||
|
||||
| 接口 | 用途 |
|
||||
| --- | --- |
|
||||
| `POST /lists/{id}/items` | 给 list 添加 item |
|
||||
| `DELETE /lists/{id}/items/{itemId}` | 删除 list item |
|
||||
|
||||
## 导入导出
|
||||
|
||||
1. 支持通过 Core 导入流程导入 `list_item`。
|
||||
2. 导入时需要校验 `list_id` 是否存在。
|
||||
3. 导入时需要校验同一 list 内 item 是否重复。
|
||||
4. 错误报告返回行号、字段名、错误原因、建议修正方式。
|
||||
|
||||
## 删除/停用
|
||||
|
||||
`list_item` 是明细记录,可从 List 详情页删除。删除前需要确认,删除后不影响 list 基本信息。
|
||||
|
||||
## 验收点
|
||||
|
||||
1. List 详情页可新增 item、批量导入 item、删除 item、排序。
|
||||
2. 同一 list 内重复 item 不能提交。
|
||||
3. 批量导入错误能定位到行号和字段名。
|
||||
57
docs/dev/01-core/10-trial_contact.md
Normal file
57
docs/dev/01-core/10-trial_contact.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# 01 Core - trial_contact 表录入说明
|
||||
|
||||
来源:`docs/requirements/01-core-data-entry-requirements.md`
|
||||
|
||||
## 录入目标
|
||||
|
||||
`trial_contact` 是给 trial 添加联系人的关系表,不是独立主数据。它记录 trial 与 person 的多对多关系,用于试验责任追踪、联系人展示、权限和通知。
|
||||
|
||||
## 前置依赖和下游引用
|
||||
|
||||
| 类型 | 内容 |
|
||||
| --- | --- |
|
||||
| 表 | `trial_contact` |
|
||||
| 前置依赖 | `trial`、`person` |
|
||||
| 下游引用 | trial 联系人展示、权限和通知 |
|
||||
|
||||
## 页面入口
|
||||
|
||||
| 页面 | 录入要求 |
|
||||
| --- | --- |
|
||||
| Trial 新增/编辑页 | 联系人区域使用可增删表格选择 `person` |
|
||||
| Trial 详情页 | 展示 trial 联系人 |
|
||||
| 批量导入 | 支持通过 Core 导入流程导入 trial 联系人关系 |
|
||||
|
||||
## 字段录入
|
||||
|
||||
| 字段 | 类型 | 控件 | 录入说明 | 校验/来源 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `trial_db_id` | varchar(255) | Trial 选择器 | 所属 trial | 必选,来源 `trial.id` |
|
||||
| `person_db_id` | varchar(255) | 人员选择器 | 联系人 | 必选,来源 `person.id` |
|
||||
|
||||
## 校验规则
|
||||
|
||||
1. `trial_db_id` 必选,且必须引用已存在的 `trial.id`。
|
||||
2. `person_db_id` 必选,且必须引用已存在的 `person.id`。
|
||||
3. 同一 trial 下不建议重复添加同一个 person。
|
||||
|
||||
## 接口能力
|
||||
|
||||
本文档原始需求未强制限定 trial_contact 的独立 URL。实现时可以随 trial 新增/编辑一起保存,也可以提供 trial 联系人子资源接口;前端需要具备新增、删除、查询 trial 联系人的能力。
|
||||
|
||||
## 导入导出
|
||||
|
||||
1. 支持通过 Core 导入流程导入 `trial_contact`。
|
||||
2. 外键列支持填写 ID;可额外支持名称匹配,但名称重复时必须报错。
|
||||
3. 导入时需要校验 trial 和 person 是否存在。
|
||||
4. 错误报告返回行号、字段名、错误原因、建议修正方式。
|
||||
|
||||
## 删除/停用
|
||||
|
||||
`trial_contact` 是关系记录,可在 Trial 新增/编辑页或详情页移除。删除关系不应删除 `trial` 或 `person` 主数据。
|
||||
|
||||
## 验收点
|
||||
|
||||
1. Trial 新增/编辑页可增删联系人。
|
||||
2. 联系人选择器展示 person 名称,提交保存 `person.id`。
|
||||
3. 删除 trial_contact 后,person 主数据仍保留。
|
||||
56
docs/dev/01-core/11-trial_publication.md
Normal file
56
docs/dev/01-core/11-trial_publication.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# 01 Core - trial_publication 表录入说明
|
||||
|
||||
来源:`docs/requirements/01-core-data-entry-requirements.md`
|
||||
|
||||
## 录入目标
|
||||
|
||||
`trial_publication` 是给 trial 记录出版物、报告或文献引用的痕迹表。它挂在 trial 下,用于说明试验相关的外部文献和永久标识。
|
||||
|
||||
## 前置依赖和下游引用
|
||||
|
||||
| 类型 | 内容 |
|
||||
| --- | --- |
|
||||
| 表 | `trial_publication` |
|
||||
| 前置依赖 | `trial` |
|
||||
| 下游引用 | trial 出版物/报告展示 |
|
||||
|
||||
## 页面入口
|
||||
|
||||
| 页面 | 录入要求 |
|
||||
| --- | --- |
|
||||
| Trial 新增/编辑页 | 出版物区域可增删 publication 记录 |
|
||||
| Trial 详情页 | 展示 trial 的出版物、报告或引用 |
|
||||
|
||||
## 字段录入
|
||||
|
||||
| 字段 | 类型 | 控件 | 录入说明 | 校验/来源 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `id` | varchar(255) | 隐藏/只读 | 出版物记录主键 | 必填、唯一 |
|
||||
| `publicationpui` | varchar(255) | 文本框 | 出版物 PUI | 可选 |
|
||||
| `publication_reference` | varchar(255) | 文本框/URL | 出版物引用 | 可选 |
|
||||
| `trial_id` | varchar(255) | Trial 选择器 | 所属 trial | 必选,来源 `trial.id` |
|
||||
|
||||
## 校验规则
|
||||
|
||||
1. `id` 必填且唯一,新增时可由系统生成。
|
||||
2. `trial_id` 必选,且必须引用已存在的 `trial.id`。
|
||||
3. `publication_reference` 如果录入 URL,需要做 URL 格式校验。
|
||||
4. `publicationpui` 建议唯一。
|
||||
|
||||
## 接口能力
|
||||
|
||||
本文档原始需求未强制限定 trial_publication 的独立 URL。实现时可以随 trial 新增/编辑一起保存,也可以提供 trial 出版物子资源接口;前端需要具备新增、删除、查询 trial 出版物的能力。
|
||||
|
||||
## 导入导出
|
||||
|
||||
原始导入对象清单未单独列出 `trial_publication`,但字段需求中包含该表。若纳入 Core 导入,应遵循通用导入流程:模板列名使用数据库字段名,外键校验 `trial_id`,错误报告返回行号、字段名和错误原因。
|
||||
|
||||
## 删除/停用
|
||||
|
||||
`trial_publication` 是 trial 的明细记录,可在 Trial 新增/编辑页或详情页移除。删除 publication 记录不应删除 `trial` 主数据。
|
||||
|
||||
## 验收点
|
||||
|
||||
1. Trial 新增/编辑页可维护出版物记录。
|
||||
2. `trial_id` 不存在时不能提交。
|
||||
3. 删除 publication 记录后,trial 主数据仍保留。
|
||||
61
docs/dev/01-core/12-study_contact.md
Normal file
61
docs/dev/01-core/12-study_contact.md
Normal file
@@ -0,0 +1,61 @@
|
||||
# 01 Core - study_contact 表录入说明
|
||||
|
||||
来源:`docs/requirements/01-core-data-entry-requirements.md`
|
||||
|
||||
## 录入目标
|
||||
|
||||
`study_contact` 是给 study 添加联系人的关系表,不是独立主数据。它记录 study 与 person 的多对多关系,用于 Study 工作台联系人展示、责任追踪、权限和通知。
|
||||
|
||||
## 前置依赖和下游引用
|
||||
|
||||
| 类型 | 内容 |
|
||||
| --- | --- |
|
||||
| 表 | `study_contact` |
|
||||
| 前置依赖 | `study`、`person` |
|
||||
| 下游引用 | Study 工作台联系人展示、权限和通知 |
|
||||
|
||||
## 页面入口
|
||||
|
||||
| 页面 | 录入要求 |
|
||||
| --- | --- |
|
||||
| Study 新增/编辑页 | 联系人区域多选 person,可增删 |
|
||||
| Study 工作台 | 展示 study 联系人 |
|
||||
| 批量导入 | 支持通过 Core 导入流程导入 study 联系人关系 |
|
||||
|
||||
## 字段录入
|
||||
|
||||
| 字段 | 类型 | 控件 | 录入说明 | 校验/来源 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `study_db_id` | varchar(255) | Study 选择器 | 所属 study | 必选,来源 `study.id` |
|
||||
| `person_db_id` | varchar(255) | 人员选择器 | 联系人 | 必选,来源 `person.id` |
|
||||
|
||||
## 校验规则
|
||||
|
||||
1. `study_db_id` 必选,且必须引用已存在的 `study.id`。
|
||||
2. `person_db_id` 必选,且必须引用已存在的 `person.id`。
|
||||
3. 同一 study 下不建议重复添加同一个 person。
|
||||
|
||||
## 接口能力
|
||||
|
||||
| 接口 | 用途 |
|
||||
| --- | --- |
|
||||
| `GET /studies/{id}/contacts` | study 联系人,返回 person 列表 |
|
||||
|
||||
本文档原始需求未强制限定 study_contact 的写入 URL。实现时可以随 study 新增/编辑一起保存,也可以提供 study 联系人子资源接口;前端需要具备新增、删除、查询 study 联系人的能力。
|
||||
|
||||
## 导入导出
|
||||
|
||||
1. 支持通过 Core 导入流程导入 `study_contact`。
|
||||
2. 外键列支持填写 ID;可额外支持名称匹配,但名称重复时必须报错。
|
||||
3. 导入时需要校验 study 和 person 是否存在。
|
||||
4. 错误报告返回行号、字段名、错误原因、建议修正方式。
|
||||
|
||||
## 删除/停用
|
||||
|
||||
`study_contact` 是关系记录,可在 Study 新增/编辑页或工作台移除。删除关系不应删除 `study` 或 `person` 主数据。
|
||||
|
||||
## 验收点
|
||||
|
||||
1. Study 新增/编辑页可增删联系人。
|
||||
2. Study 工作台能展示联系人列表。
|
||||
3. 删除 study_contact 后,person 主数据仍保留。
|
||||
61
docs/dev/01-core/13-study_season.md
Normal file
61
docs/dev/01-core/13-study_season.md
Normal file
@@ -0,0 +1,61 @@
|
||||
# 01 Core - study_season 表录入说明
|
||||
|
||||
来源:`docs/requirements/01-core-data-entry-requirements.md`
|
||||
|
||||
## 录入目标
|
||||
|
||||
`study_season` 是给 study 绑定 season 的关系表。一个 study 可以关联多个 season,用于多季节筛选、统计和 Study 工作台上下文展示。
|
||||
|
||||
## 前置依赖和下游引用
|
||||
|
||||
| 类型 | 内容 |
|
||||
| --- | --- |
|
||||
| 表 | `study_season` |
|
||||
| 前置依赖 | `study`、`season` |
|
||||
| 下游引用 | Study 工作台季节展示、多季节筛选和统计 |
|
||||
|
||||
## 页面入口
|
||||
|
||||
| 页面 | 录入要求 |
|
||||
| --- | --- |
|
||||
| Study 新增/编辑页 | 季节区域多选 season |
|
||||
| Study 工作台 | 展示 study 关联 season |
|
||||
| 批量导入 | 支持通过 Core 导入流程导入 study 与 season 关系 |
|
||||
|
||||
## 字段录入
|
||||
|
||||
| 字段 | 类型 | 控件 | 录入说明 | 校验/来源 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `study_db_id` | varchar(255) | Study 选择器 | 所属 study | 必选,来源 `study.id` |
|
||||
| `season_db_id` | varchar(255) | Season 选择器 | 关联季节 | 必选,来源 `season.id` |
|
||||
|
||||
## 校验规则
|
||||
|
||||
1. `study_db_id` 必选,且必须引用已存在的 `study.id`。
|
||||
2. `season_db_id` 必选,且必须引用已存在的 `season.id`。
|
||||
3. 同一 study 下不建议重复绑定同一个 season。
|
||||
|
||||
## 接口能力
|
||||
|
||||
| 接口 | 用途 |
|
||||
| --- | --- |
|
||||
| `GET /studies/{id}/seasons` | study 季节,返回 season 列表 |
|
||||
|
||||
本文档原始需求未强制限定 study_season 的写入 URL。实现时可以随 study 新增/编辑一起保存,也可以提供 study season 子资源接口;前端需要具备新增、删除、查询 study season 的能力。
|
||||
|
||||
## 导入导出
|
||||
|
||||
1. 支持通过 Core 导入流程导入 `study_season`。
|
||||
2. 外键列支持填写 ID;可额外支持名称匹配,但名称重复时必须报错。
|
||||
3. 导入时需要校验 study 和 season 是否存在。
|
||||
4. 错误报告返回行号、字段名、错误原因、建议修正方式。
|
||||
|
||||
## 删除/停用
|
||||
|
||||
`study_season` 是关系记录,可在 Study 新增/编辑页或工作台移除。删除关系不应删除 `study` 或 `season` 主数据。
|
||||
|
||||
## 验收点
|
||||
|
||||
1. Study 新增/编辑页可多选 season。
|
||||
2. Study 工作台能展示关联 season。
|
||||
3. 删除 study_season 后,season 主数据仍保留。
|
||||
26
docs/dev/02-germplasm-seed/01-breeding_method.md
Normal file
26
docs/dev/02-germplasm-seed/01-breeding_method.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# 02 Germplasm / Seed - breeding_method 表录入说明
|
||||
|
||||
来源:`docs/requirements/02-germplasm-seed-entry-requirements.md`
|
||||
|
||||
## 录入目标
|
||||
|
||||
`breeding_method` 是育种方法字典,用来说明某个 germplasm 是通过什么方式形成的,例如杂交选育、回交、自交系选育、诱变、转基因、克隆选择等。它不是一次具体杂交动作,而是材料来源方法的分类。
|
||||
|
||||
## 字段录入
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 控件建议 | 校验规则 |
|
||||
| -------------- | -------------------------------------------- | -------------------------------- | --------- | -------------------------------- |
|
||||
| `id` | 育种方法主键,系统内部唯一标识 | 新增时系统生成;导入时可允许指定 | 隐藏/只读 | 必填、唯一;编辑时不允许修改 |
|
||||
| `auth_user_id` | 数据所属用户或租户 | 登录上下文自动写入 | 隐藏 | 不允许前端手填 |
|
||||
| `abbreviation` | 方法缩写,如 MB、BC、DH | 用户录入 | 文本框 | 可选;建议同一用户下唯一 |
|
||||
| `description` | 方法解释,如“回交用于恢复目标基因” | 用户录入 | 多行文本 | 可选,限制长度 |
|
||||
| `name` | 方法名称,如 Male Backcross、Doubled Haploid | 用户录入 | 文本框 | 必填;建议唯一;作为下拉展示名称 |
|
||||
|
||||
## 页面与交互
|
||||
|
||||
- 列表页展示:方法名称、缩写、描述、使用材料数量。
|
||||
- 新增页为简单字典表单。
|
||||
- 删除前检查是否被 `germplasm.breeding_method_id` 引用;已引用时不允许物理删除,只允许停用。
|
||||
|
||||
---
|
||||
|
||||
54
docs/dev/02-germplasm-seed/02-germplasm.md
Normal file
54
docs/dev/02-germplasm-seed/02-germplasm.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# 02 Germplasm / Seed - germplasm 表录入说明
|
||||
|
||||
来源:`docs/requirements/02-germplasm-seed-entry-requirements.md`
|
||||
|
||||
## 录入目标
|
||||
|
||||
`germplasm` 是材料身份证,描述一个品种、品系、亲本、后代材料、遗传资源或研究材料“是谁”。它不表示库存数量,库存数量由 `seed_lot` 表达。
|
||||
|
||||
## 字段录入
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 控件建议 | 校验规则 |
|
||||
| ------------------------------------- | ------------------------------------------------------------ | -------------------------------------- | ----------------- | --------------------------------------------------------- |
|
||||
| `id` | 种质主键,系统内部唯一标识 | 系统生成;导入可指定 | 隐藏/只读 | 必填、唯一;编辑不可随意修改 |
|
||||
| `auth_user_id` | 数据所属用户或租户 | 登录上下文自动写入 | 隐藏 | 不允许手填 |
|
||||
| `accession_number` | 材料在种质库/机构内的 accession 编号,如 PI 113869 | 用户录入或导入 | 文本框 | 可选;建议同一 crop / institution 下唯一 |
|
||||
| `acquisition_date` | 材料进入本系统或本机构的获取日期 | 用户录入 | 日期选择器 | 可选;不得晚于当前日期太多,导入时允许缺月/缺日需统一规则 |
|
||||
| `acquisition_source_code` | 获取来源编码,如采集、引进、交换、繁殖等 | 用户选择 | 下拉框 | 可选;值来自 BrAPI/MCPD 枚举或系统字典 |
|
||||
| `biological_status_of_accession_code` | 材料生物状态,如野生、地方品种、育种材料、改良品种、突变体等 | 用户选择 | 下拉框 | 可选;使用受控枚举,不建议自由输入 |
|
||||
| `collection` | 材料所属集合、群体、panel 或 collection | 用户录入/选择 | 文本框/选择器 | 可选;可用于分组筛选 |
|
||||
| `country_of_origin_code` | 原产国或育成/选育国家代码 | 用户选择 | 国家代码选择器 | 可选;建议使用 ISO 3166-1 三字母代码 |
|
||||
| `default_display_name` | 系统默认展示名,给下拉框、表格、详情标题使用 | 用户录入,可由 germplasm_name 自动带出 | 文本框 | 与 `germplasm_name` 至少填一个;建议必填 |
|
||||
| `documentationurl` | 材料说明文档、外部数据库页面或 DOI 链接 | 用户录入 | URL 输入框 | 可选;校验 URL 格式 |
|
||||
| `genus` | 属名,如 Oryza、Triticum | 用户录入/字典选择 | 文本框/物种选择器 | 可选;建议首字母大写 |
|
||||
| `germplasm_name` | 材料名称,可以是品种名、品系名、后代编号 | 用户录入 | 文本框 | 与 `default_display_name` 至少填一个;不强制全局唯一 |
|
||||
| `germplasmpui` | 永久唯一标识,通常是 DOI、URI 或全局唯一编码 | 用户录入/外部导入 | 文本框/URL 输入框 | 可选;若填写必须唯一;建议用于跨系统交换 |
|
||||
| `germplasm_preprocessing` | 材料用于试验前的统一处理说明,如消毒、催芽、低温处理 | 用户录入 | 文本框/多行文本 | 可选 |
|
||||
| `mls_status` | 多边系统 MLS 状态,涉及植物遗传资源交换协议 | 用户选择 | 下拉框 | 可选;普通业务可隐藏到高级信息 |
|
||||
| `seed_source` | 材料来源标识,如来源机构+accession,或亲本组合描述 | 用户录入 | 文本框 | 可选;注意它不是库存批次,不等于 seed_lot |
|
||||
| `seed_source_description` | 材料来源详细说明 | 用户录入 | 多行文本 | 可选 |
|
||||
| `species` | 种名,如 sativa、aestivum | 用户录入/物种字典 | 文本框 | 可选;建议小写 |
|
||||
| `species_authority` | 种名命名权威,如 L. | 用户录入 | 文本框 | 可选 |
|
||||
| `subtaxa` | 亚种、变种、品种群、line 等更细分类 | 用户录入 | 文本框 | 可选 |
|
||||
| `subtaxa_authority` | 亚种/变种命名权威 | 用户录入 | 文本框 | 可选 |
|
||||
| `breeding_method_id` | 该材料形成所使用的育种方法 | 从 breeding_method 选择 | 搜索选择器 | 可选;必须引用存在的 breeding_method |
|
||||
| `crop_id` | 所属作物 | 从 crop 选择 | 作物选择器 | 必填;后续 trial/study/attribute 应尽量同 crop |
|
||||
|
||||
## 录入建议
|
||||
|
||||
- 新建材料时,第一屏只放核心字段:`crop_id`、`germplasm_name`、`default_display_name`、`germplasmpui`、`accession_number`、`breeding_method_id`。
|
||||
- 分类与来源信息放在“高级信息”或“来源信息”分组。
|
||||
- `germplasmpui`、`accession_number`、`germplasm_name` 三者不要混为一谈:
|
||||
- `germplasm_name` 是人看的名字;
|
||||
- `accession_number` 是机构内编号;
|
||||
- `germplasmpui` 是跨系统长期唯一标识。
|
||||
|
||||
## 验收点
|
||||
|
||||
1. 新增 germplasm 时,必须选择 crop。
|
||||
2. `germplasm_name` 和 `default_display_name` 至少填写一个。
|
||||
3. 下拉选择材料时展示 `default_display_name`,辅助展示 accession number / PUI。
|
||||
4. 如果 germplasm 已被 seed lot、cross parent、observation unit 引用,不允许物理删除。
|
||||
|
||||
---
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
# 02 Germplasm / Seed - germplasm_attribute_definition 表录入说明
|
||||
|
||||
来源:`docs/requirements/02-germplasm-seed-entry-requirements.md`
|
||||
|
||||
## 录入目标
|
||||
|
||||
属性定义描述“材料可以有哪些稳定属性”。这些属性通常不是环境依赖的田间观测值,而是材料自身特征,例如籽粒颜色、抗病基因、硬度、熟期类型、特定 QTL、分子标记结果等。
|
||||
|
||||
## 字段录入
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 控件建议 | 校验规则 |
|
||||
| ---------------------- | ------------------------------------------------------------ | ---------------------------- | ----------------- | ------------------------------------- |
|
||||
| `id` | 属性定义主键 | 系统生成 | 隐藏/只读 | 必填、唯一 |
|
||||
| `auth_user_id` | 数据所属用户或租户 | 登录上下文自动写入 | 隐藏 | 不允许手填 |
|
||||
| `default_value` | 属性默认值 | 用户录入 | 动态输入框 | 可选;按 datatype / scale 校验 |
|
||||
| `documentationurl` | 属性说明文档链接 | 用户录入 | URL 输入框 | 可选;校验 URL |
|
||||
| `growth_stage` | 属性适用生长阶段,如 flowering | 用户录入/选择 | 下拉框/文本框 | 可选 |
|
||||
| `institution` | 提交或维护该属性定义的机构 | 用户录入 | 文本框 | 可选 |
|
||||
| `language` | 定义语言,如 zh、en | 用户选择 | 下拉框 | 可选;建议 ISO 639-1 |
|
||||
| `scientist` | 提交该属性定义的科学家或负责人 | 用户录入/人员选择 | 文本框/人员选择器 | 可选 |
|
||||
| `status` | 属性状态,如 recommended、obsolete、legacy | 用户选择 | 下拉框 | 可选;推荐使用枚举 |
|
||||
| `submission_timestamp` | 属性定义提交时间 | 系统默认当前时间,可手动调整 | 日期时间选择器 | 可选;新增默认当前时间 |
|
||||
| `crop_id` | 适用作物 | 从 crop 选择 | 作物选择器 | 可选;若填写,下游材料应同 crop |
|
||||
| `method_id` | 属性测定方法 | 从 method 选择 | 方法选择器 | 可选;若填写,属性值录入按该方法解释 |
|
||||
| `ontology_id` | 所属本体 | 从 ontology 选择 | 本体选择器 | 可选 |
|
||||
| `scale_id` | 值标尺/单位/有效值范围 | 从 scale 选择 | 标尺选择器 | 可选;若填写,属性值必须按 scale 校验 |
|
||||
| `trait_id` | 关联性状 | 从 trait 选择 | 性状选择器 | 可选 |
|
||||
| `attribute_category` | 属性分类,如 Morphological、Genetic、Quality | 用户选择/录入 | 下拉框/文本框 | 可选;建议字典化 |
|
||||
| `code` | 属性代码,便于导入导出 | 用户录入 | 文本框 | 可选;建议唯一 |
|
||||
| `datatype` | 属性值数据类型,如 text、numeric、date、boolean、categorical | 用户选择 | 下拉框 | 必填 |
|
||||
| `description` | 属性解释 | 用户录入 | 多行文本 | 可选 |
|
||||
| `name` | 属性名称 | 用户录入 | 文本框 | 必填;作为属性选择器展示名称 |
|
||||
| `pui` | 属性永久标识 | 用户录入 | 文本框/URL 输入框 | 可选;建议唯一 |
|
||||
| `uri` | 属性 URI | 用户录入 | URL 输入框 | 可选;校验 URL |
|
||||
|
||||
## 录入建议
|
||||
|
||||
- 属性定义页面本质是“属性字典配置”。
|
||||
- 前端应根据 `datatype` 动态决定属性值录入控件:
|
||||
- numeric:数字输入框;
|
||||
- categorical:下拉框;
|
||||
- date:日期选择器;
|
||||
- boolean:开关;
|
||||
- text:文本框。
|
||||
- 若绑定了 `scale_id`,则优先按 scale 的单位、上下限、有效分类值校验。
|
||||
|
||||
---
|
||||
|
||||
27
docs/dev/02-germplasm-seed/04-germplasm_attribute_value.md
Normal file
27
docs/dev/02-germplasm-seed/04-germplasm_attribute_value.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# 02 Germplasm / Seed - germplasm_attribute_value 表录入说明
|
||||
|
||||
来源:`docs/requirements/02-germplasm-seed-entry-requirements.md`
|
||||
|
||||
## 录入目标
|
||||
|
||||
属性值是“某个 germplasm 在某个属性上的实际取值”。它不是属性定义,也不是 observation。它适合记录材料相对稳定、不强依赖环境的特征。
|
||||
|
||||
## 字段录入
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 控件建议 | 校验规则 |
|
||||
| ----------------- | ------------------------------ | -------------------------------------- | ---------- | ---------------------------------------- |
|
||||
| `id` | 属性值主键 | 系统生成 | 隐藏/只读 | 必填、唯一 |
|
||||
| `auth_user_id` | 数据所属用户或租户 | 登录上下文自动写入 | 隐藏 | 不允许手填 |
|
||||
| `determined_date` | 属性值被测定或确认的日期 | 用户录入 | 日期选择器 | 可选;多次测定时必须填写以区分记录 |
|
||||
| `value` | 某个材料在某个属性上的实际取值 | 用户录入 | 动态控件 | 必填;按 attribute datatype / scale 校验 |
|
||||
| `attribute_id` | 属性定义 | 从 germplasm_attribute_definition 选择 | 属性选择器 | 必选;必须存在 |
|
||||
| `germplasm_id` | 所属材料 | 从 germplasm 选择 | 材料选择器 | 必选;必须存在 |
|
||||
|
||||
## 录入建议
|
||||
|
||||
- 推荐嵌入 Germplasm 详情页的“属性值”Tab。
|
||||
- 支持批量导入,模板列建议为:`germplasm_id/germplasm_name`、`attribute_code/attribute_name`、`value`、`determined_date`。
|
||||
- 同一个 germplasm + attribute 可以允许多次测定,但页面必须显示测定日期、来源和最新值标记。
|
||||
|
||||
---
|
||||
|
||||
25
docs/dev/02-germplasm-seed/05-crossing_project.md
Normal file
25
docs/dev/02-germplasm-seed/05-crossing_project.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# 02 Germplasm / Seed - crossing_project 表录入说明
|
||||
|
||||
来源:`docs/requirements/02-germplasm-seed-entry-requirements.md`
|
||||
|
||||
## 录入目标
|
||||
|
||||
`crossing_project` 是某个育种项目下的一组杂交任务集合。它不是一次杂交,而是一个杂交工作台,例如“2026 抗倒伏杂交项目”。
|
||||
|
||||
## 字段录入
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 控件建议 | 校验规则 |
|
||||
| -------------- | ------------------ | ------------------ | ---------- | ----------------------------- |
|
||||
| `id` | 杂交项目主键 | 系统生成 | 隐藏/只读 | 必填、唯一 |
|
||||
| `auth_user_id` | 数据所属用户或租户 | 登录上下文自动写入 | 隐藏 | 不允许手填 |
|
||||
| `description` | 杂交项目说明 | 用户录入 | 多行文本 | 可选 |
|
||||
| `name` | 杂交项目名称 | 用户录入 | 文本框 | 必填;同一 program 下建议唯一 |
|
||||
| `program_id` | 所属育种项目 | 从 program 选择 | 项目选择器 | 必选;必须存在 |
|
||||
|
||||
## 页面与交互
|
||||
|
||||
- 详情页应展示计划杂交、实际杂交、潜在亲本、后代材料、产生的 seed lot。
|
||||
- 创建 cross 时应自动带入 crossing_project_id。
|
||||
|
||||
---
|
||||
|
||||
30
docs/dev/02-germplasm-seed/06-cross_entity.md
Normal file
30
docs/dev/02-germplasm-seed/06-cross_entity.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# 02 Germplasm / Seed - cross_entity 表录入说明
|
||||
|
||||
来源:`docs/requirements/02-germplasm-seed-entry-requirements.md`
|
||||
|
||||
## 录入目标
|
||||
|
||||
`cross_entity` 统一承载计划杂交和实际杂交。通过 `planned` 字段区分计划与实际,通过 `planned_cross_id` 指向来源计划。
|
||||
|
||||
## 字段录入
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 控件建议 | 校验规则 |
|
||||
| --------------------- | ------------------------------------------- | ------------------------ | -------------- | --------------------------------------- |
|
||||
| `id` | cross 主键 | 系统生成 | 隐藏/只读 | 必填、唯一 |
|
||||
| `auth_user_id` | 数据所属用户或租户 | 登录上下文自动写入 | 隐藏 | 不允许手填 |
|
||||
| `cross_type` | 杂交类型,如 biparental、self、backcross 等 | 用户选择 | 下拉框 | 可选;值来自枚举字典 |
|
||||
| `name` | cross 名称,如 A × B、A/B、Cross-2026-001 | 用户录入或自动生成 | 文本框 | 必填;同一 crossing project 下建议唯一 |
|
||||
| `planned` | 是否为计划杂交 | 页面根据入口自动设置 | 开关/分段控件 | 必填;计划杂交为 true,实际杂交为 false |
|
||||
| `status` | 状态,如 TODO、DONE、SKIPPED、FAILED | 用户选择/系统更新 | 下拉框 | 可选;计划杂交常用 TODO/DONE/SKIPPED |
|
||||
| `crossing_project_id` | 所属杂交项目 | 从 crossing_project 选择 | 杂交项目选择器 | 必选 |
|
||||
| `planned_cross_id` | 实际杂交来源的计划杂交 | 从 cross_entity 选择 | Cross 选择器 | 可选;不能选择自己;实际杂交建议填写 |
|
||||
|
||||
## 录入建议
|
||||
|
||||
- 页面上分成“计划杂交”和“实际杂交”两个入口,但后端都保存到 `cross_entity`。
|
||||
- 创建计划杂交时:`planned=true`,`planned_cross_id=null`。
|
||||
- 完成实际杂交时:`planned=false`,`planned_cross_id=原计划杂交 id`。
|
||||
- 亲本不要直接塞在 cross 主表字段中,应通过 `cross_parent` 维护,便于支持多亲本和 observation_unit 亲本来源。
|
||||
|
||||
---
|
||||
|
||||
26
docs/dev/02-germplasm-seed/07-cross_parent.md
Normal file
26
docs/dev/02-germplasm-seed/07-cross_parent.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# 02 Germplasm / Seed - cross_parent 表录入说明
|
||||
|
||||
来源:`docs/requirements/02-germplasm-seed-entry-requirements.md`
|
||||
|
||||
## 录入目标
|
||||
|
||||
`cross_parent` 表示某个 cross 的亲本。亲本可以来自 `germplasm`,也可以来自某个 `observation_unit`,例如田间某一株实际被选作父本/母本。
|
||||
|
||||
## 字段录入
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 控件建议 | 校验规则 |
|
||||
| --------------------- | --------------------------------------------------- | ---------------------------------- | ----------------- | ------------------------------------- |
|
||||
| `id` | 亲本记录主键 | 系统生成 | 隐藏/只读 | 必填、唯一 |
|
||||
| `parent_type` | 亲本角色,如 MALE、FEMALE、SELF、POPULATION、CLONAL | 用户选择 | 下拉框 | 必填;使用枚举 |
|
||||
| `cross_id` | 所属 cross | 从 cross_entity 选择或由详情页带入 | Cross 选择器/隐藏 | 必选 |
|
||||
| `crossing_project_id` | 所属 crossing project | 由 cross 自动带出 | 只读/隐藏 | 可选;如填写必须与 cross 一致 |
|
||||
| `germplasm_id` | 亲本材料 | 从 germplasm 选择 | 材料选择器 | 与 `observation_unit_id` 至少一个必填 |
|
||||
| `observation_unit_id` | 亲本观测单元 | 从 observation_unit 选择 | 观测单元选择器 | 与 `germplasm_id` 至少一个必填 |
|
||||
|
||||
## 录入建议
|
||||
|
||||
- 在 Cross 详情页内嵌“亲本列表”。
|
||||
- 常见快捷录入:Parent1 / Parent2。
|
||||
- 对于田间选株杂交,优先记录 observation_unit_id,同时可带出 germplasm_id,保证可追溯到具体植株。
|
||||
|
||||
---
|
||||
28
docs/dev/02-germplasm-seed/08-pedigree_node.md
Normal file
28
docs/dev/02-germplasm-seed/08-pedigree_node.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# 02 Germplasm / Seed - pedigree_node 表录入说明
|
||||
|
||||
来源:`docs/requirements/02-germplasm-seed-entry-requirements.md`
|
||||
|
||||
## 录入目标
|
||||
|
||||
`pedigree_node` 是系谱图中的节点,通常对应一个 germplasm。它用于描述材料在系谱树中的位置,不等同于一次杂交记录。
|
||||
|
||||
## 字段录入
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 控件建议 | 校验规则 |
|
||||
| --------------------- | --------------------- | ------------------------ | -------------- | ----------------------------------------- |
|
||||
| `id` | 系谱节点主键 | 系统生成 | 隐藏/只读 | 必填、唯一 |
|
||||
| `auth_user_id` | 数据所属用户或租户 | 登录上下文自动写入 | 隐藏 | 不允许手填 |
|
||||
| `crossing_year` | 亲本最初杂交年份 | 用户录入 | 年份选择器 | 可选;四位年份 |
|
||||
| `family_code` | 家系编号 | 用户录入 | 文本框 | 可选;同一 crossing_project 下建议唯一 |
|
||||
| `pedigree_string` | 系谱字符串,如 A/B//C | 用户录入/系统生成 | 文本框 | 可选;建议支持 Purdy notation |
|
||||
| `crossing_project_id` | 产生该节点的杂交项目 | 从 crossing_project 选择 | 杂交项目选择器 | 可选 |
|
||||
| `germplasm_id` | 该系谱节点对应的材料 | 从 germplasm 选择 | 材料选择器 | 建议必填;同一 germplasm 不建议重复建节点 |
|
||||
|
||||
## 录入建议
|
||||
|
||||
- Germplasm 详情页提供“系谱”Tab。
|
||||
- 支持两种维护方式:树图拖拽维护、表格维护节点和边。
|
||||
- 如果 cross 完成后产生后代 germplasm,应自动或半自动创建 pedigree_node。
|
||||
|
||||
---
|
||||
|
||||
27
docs/dev/02-germplasm-seed/09-pedigree_edge.md
Normal file
27
docs/dev/02-germplasm-seed/09-pedigree_edge.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# 02 Germplasm / Seed - pedigree_edge 表录入说明
|
||||
|
||||
来源:`docs/requirements/02-germplasm-seed-entry-requirements.md`
|
||||
|
||||
## 录入目标
|
||||
|
||||
`pedigree_edge` 是系谱图中的边,描述节点之间的父子、同胞等关系。
|
||||
|
||||
## 字段录入
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 控件建议 | 校验规则 |
|
||||
| ------------------- | ------------------------------------------ | --------------------- | ---------- | ------------------------------------ |
|
||||
| `id` | 系谱边主键 | 系统生成 | 隐藏/只读 | 必填、唯一 |
|
||||
| `auth_user_id` | 数据所属用户或租户 | 登录上下文自动写入 | 隐藏 | 不允许手填 |
|
||||
| `edge_type` | 边类型,如 parent、child、sibling | 用户选择 | 下拉框 | 必填 |
|
||||
| `parent_type` | 如果是亲本关系,标识 MALE、FEMALE、SELF 等 | 用户选择 | 下拉框 | 可选;当 edge_type=parent 时建议必填 |
|
||||
| `connceted_node_id` | 被连接节点 | 从 pedigree_node 选择 | 节点选择器 | 必选 |
|
||||
| `this_node_id` | 当前节点 | 从 pedigree_node 选择 | 节点选择器 | 必选;不能等于 connected node |
|
||||
|
||||
## 录入建议
|
||||
|
||||
- 前端展示时不要暴露“this_node_id / connected_node_id”这种技术词,应该显示为“当前材料”和“关联材料”。
|
||||
- 添加父本/母本时,系统自动创建 edge_type=parent。
|
||||
- 需要避免明显循环,例如 A 是 B 的父本,同时 B 又是 A 的父本。
|
||||
|
||||
---
|
||||
|
||||
33
docs/dev/02-germplasm-seed/10-seed_lot.md
Normal file
33
docs/dev/02-germplasm-seed/10-seed_lot.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# 02 Germplasm / Seed - seed_lot 表录入说明
|
||||
|
||||
来源:`docs/requirements/02-germplasm-seed-entry-requirements.md`
|
||||
|
||||
## 录入目标
|
||||
|
||||
`seed_lot` 是实物库存批次,描述某一批种子当前有多少、放在哪里、属于哪个项目。它不是 germplasm 身份;同一个 germplasm 可以有多个 seed_lot。
|
||||
|
||||
## 字段录入
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 控件建议 | 校验规则 |
|
||||
| ------------------- | ---------------------------------------------------- | --------------------- | ------------------- | ----------------------------- |
|
||||
| `id` | SeedLot 主键 | 系统生成 | 隐藏/只读 | 必填、唯一 |
|
||||
| `auth_user_id` | 数据所属用户或租户 | 登录上下文自动写入 | 隐藏 | 不允许手填 |
|
||||
| `amount` | 当前库存数量,可以是粒数、重量、株数等 | 用户录入/交易自动更新 | 数字输入框 | 必填;非负;交易后自动更新 |
|
||||
| `created_date` | 批次创建时间 | 系统默认,可导入 | 日期时间选择器/只读 | 默认当前时间 |
|
||||
| `description` | 批次说明 | 用户录入 | 多行文本 | 可选 |
|
||||
| `last_updated` | 最后更新时间,包含交易变化 | 系统自动更新 | 只读 | 不允许手动改 |
|
||||
| `name` | 批次名称,如 华占-2026-荆门-扩繁批 | 用户录入或自动生成 | 文本框 | 必填;同一 program 下建议唯一 |
|
||||
| `source_collection` | 原始来源 collection,如野外采集、nursery、种质库集合 | 用户录入 | 文本框 | 可选 |
|
||||
| `storage_location` | 具体库位描述,如 冰箱A-2层-盒03 | 用户录入 | 文本框 | 可选 |
|
||||
| `units` | 数量单位,如 seeds、g、kg、plants | 用户选择 | 下拉框/文本框 | 必填;交易单位需一致或可换算 |
|
||||
| `location_id` | 库存所在地点 | 从 location 选择 | 地点选择器 | 可选 |
|
||||
| `program_id` | 所属项目 | 从 program 选择 | 项目选择器 | 可选;用于项目库存筛选 |
|
||||
|
||||
## 录入建议
|
||||
|
||||
- 创建 seed_lot 后必须进入“批次组成”Tab,至少录入一条 `seed_lot_content_mixture`。
|
||||
- 普通用户不要直接编辑 amount;amount 应通过入库、出库、转移、分装等交易动作更新。
|
||||
- 支持库存状态:充足、低库存、耗尽,可由 amount 和阈值计算。
|
||||
|
||||
---
|
||||
|
||||
26
docs/dev/02-germplasm-seed/11-seed_lot_content_mixture.md
Normal file
26
docs/dev/02-germplasm-seed/11-seed_lot_content_mixture.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# 02 Germplasm / Seed - seed_lot_content_mixture 表录入说明
|
||||
|
||||
来源:`docs/requirements/02-germplasm-seed-entry-requirements.md`
|
||||
|
||||
## 录入目标
|
||||
|
||||
`seed_lot_content_mixture` 描述一个 seed_lot 由哪些材料或 cross 组成。单一材料批次也需要有一条组成记录,比例为 100%。混合批次则多条记录占比合计为 100%。
|
||||
|
||||
## 字段录入
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 控件建议 | 校验规则 |
|
||||
| -------------------- | ----------------------------- | -------------------- | ------------------- | ---------------------------------------- |
|
||||
| `id` | 批次组成主键 | 系统生成 | 隐藏/只读 | 必填、唯一 |
|
||||
| `mixture_percentage` | 该材料或 cross 在批次中的占比 | 用户录入 | 百分比输入框 | 0 到 100;同一 seed lot 总和建议等于 100 |
|
||||
| `cross_id` | 来源 cross | 从 cross_entity 选择 | Cross 选择器 | 与 `germplasm_id` 至少一个必填 |
|
||||
| `germplasm_id` | 来源 germplasm | 从 germplasm 选择 | 材料选择器 | 与 `cross_id` 至少一个必填 |
|
||||
| `seed_lot_id` | 所属 seed lot | 由详情页带入或选择 | SeedLot 选择器/隐藏 | 必选 |
|
||||
|
||||
## 录入建议
|
||||
|
||||
- 新建 seed_lot 时,如果用户选择了单个 germplasm,系统自动生成一条 mixture:`germplasm_id=所选材料`,`mixture_percentage=100`。
|
||||
- 如果来源是某次杂交产生的种子,优先填写 `cross_id`。
|
||||
- 如果既能追溯 cross 又能追溯 germplasm,可按系统设计决定是否允许同时填写;若允许,同时展示“来源杂交”和“当前材料身份”。
|
||||
|
||||
---
|
||||
|
||||
42
docs/dev/02-germplasm-seed/12-seed_lot_transaction.md
Normal file
42
docs/dev/02-germplasm-seed/12-seed_lot_transaction.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# 02 Germplasm / Seed - seed_lot_transaction 表录入说明
|
||||
|
||||
来源:`docs/requirements/02-germplasm-seed-entry-requirements.md`
|
||||
|
||||
## 录入目标
|
||||
|
||||
`seed_lot_transaction` 记录库存变化。它不应该由用户像普通表单一样手动维护,而应该由“入库、出库、转移、分装、合并、消耗”等业务动作自动生成。
|
||||
|
||||
## 字段录入
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 控件建议 | 校验规则 |
|
||||
| ------------------ | -------------------------------------------- | ----------------------- | ------------------- | ------------------------------------------------- |
|
||||
| `id` | 流转记录主键 | 系统生成 | 隐藏/只读 | 必填、唯一 |
|
||||
| `auth_user_id` | 数据所属用户或租户 | 登录上下文自动写入 | 隐藏 | 不允许手填 |
|
||||
| `amount` | 流转数量 | 用户在业务动作中录入 | 数字输入框 | 必填;大于 0 |
|
||||
| `description` | 流转说明,如用于某 study、分装原因、报废原因 | 用户录入 | 多行文本 | 可选;出库/报废建议必填 |
|
||||
| `timestamp` | 流转发生时间 | 默认当前时间,可调整 | 日期时间选择器 | 必填 |
|
||||
| `units` | 流转单位 | 默认继承 seed_lot.units | 下拉框/只读 | 必填;需与 seed_lot 单位一致或有换算关系 |
|
||||
| `from_seed_lot_id` | 来源批次 | 按动作自动设置 | SeedLot 选择器/隐藏 | 与 `to_seed_lot_id` 至少一个存在 |
|
||||
| `to_seed_lot_id` | 目标批次 | 按动作自动设置 | SeedLot 选择器/隐藏 | 与 `from_seed_lot_id` 至少一个存在;不能等于 from |
|
||||
|
||||
## 业务动作映射
|
||||
|
||||
| 动作 | from_seed_lot_id | to_seed_lot_id | amount 对库存影响 |
|
||||
| --------- | ---------------- | -------------- | -------------------------------------------- |
|
||||
| 入库 | 空 | 目标批次 | 目标批次增加 |
|
||||
| 出库 | 来源批次 | 空 | 来源批次减少 |
|
||||
| 转移 | 来源批次 | 目标批次 | 来源减少,目标增加 |
|
||||
| 分装 | 原批次 | 新批次 | 原批次减少,新批次增加 |
|
||||
| 合并 | 多个来源批次 | 目标批次 | 来源减少,目标增加;可能生成多条 transaction |
|
||||
| 消耗/报废 | 来源批次 | 空 | 来源减少,并记录原因 |
|
||||
|
||||
## 验收点
|
||||
|
||||
1. amount 必须大于 0。
|
||||
2. 出库/消耗时,amount 不得超过来源批次当前库存。
|
||||
3. from 和 to 不能相同。
|
||||
4. transaction 创建后应自动更新 seed_lot.amount 和 last_updated。
|
||||
5. 已生成的 transaction 原则上不允许随意修改;如需纠错,应通过反向交易或更正记录处理。
|
||||
|
||||
---
|
||||
|
||||
48
docs/dev/03-genotyping/01-plate.md
Normal file
48
docs/dev/03-genotyping/01-plate.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# 03 Genotyping - plate 表录入说明
|
||||
|
||||
来源:`docs/architecture/03-genotyping-data-flow.md`
|
||||
|
||||
## 录入目标
|
||||
|
||||
`plate` 表示样本板,是批量送检和测序前的样本组织单元。一个 plate 可以包含多个 `sample`,也可以关联 vendor submission。
|
||||
|
||||
## 上下游关系
|
||||
|
||||
| 类型 | 内容 |
|
||||
| --- | --- |
|
||||
| 表 | `plate` |
|
||||
| Java 实体 | `PlateEntity` |
|
||||
| 前置依赖 | `program`、`trial`、`study`,可选 `plate_submission` |
|
||||
| 下游引用 | `sample` |
|
||||
| API | `/brapi/v2/plates` |
|
||||
|
||||
## 字段录入
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 校验规则 |
|
||||
| --- | --- | --- | --- |
|
||||
| `id` | plate 主键 | 系统生成;导入时可指定 | 必填、唯一 |
|
||||
| `auth_user_id` | 数据所属用户 | 登录上下文自动写入 | 不允许前端手填 |
|
||||
| `plate_name` | 样本板名称 | 用户录入 | 建议必填,同一 study 下建议唯一 |
|
||||
| `plate_barcode` | 样本板条码 | 扫码/手填 | 建议唯一 |
|
||||
| `client_plate_db_id` | 客户侧 plate ID | 用户录入/导入 | 可选 |
|
||||
| `client_plate_barcode` | 客户侧 plate 条码 | 用户录入/导入 | 可选 |
|
||||
| `plate_format` | plate 规格,如 96/384 孔板 | 枚举选择 | 可选;与样本孔位校验联动 |
|
||||
| `sample_type` | plate 中样本类型 | 枚举选择 | 可选 |
|
||||
| `sample_submission_format` | 提交给 vendor 的样本板格式 | 枚举选择 | 可选 |
|
||||
| `status_time_stamp` | 状态时间 | 系统写入或导入 | 可选 |
|
||||
| `program_id` | 所属项目 | 项目选择器 | 可选;若填需存在 |
|
||||
| `trial_id` | 所属 trial | Trial 选择器 | 可选;若填需存在 |
|
||||
| `study_id` | 所属 study | Study 选择器 | 可选;若填需存在 |
|
||||
| `submission_id` | vendor plate submission | Vendor submission 选择器 | 可选 |
|
||||
|
||||
## 页面与交互
|
||||
|
||||
- Plate 列表页支持按 program、trial、study、barcode、plateName 查询。
|
||||
- 详情页展示 plate 基本信息和下属 sample 列表。
|
||||
- 新增 sample 时如果从 plate 详情进入,默认带出 `plate_id`。
|
||||
|
||||
## 关键校验
|
||||
|
||||
1. 如果录入 `program_id/trial_id/study_id`,需要校验 Core 上下文一致性。
|
||||
2. 如果使用 `plate_format`,样本 `plate_row/plate_column/well` 不能超出规格。
|
||||
3. 已有关联 `sample` 的 plate 删除前必须提示,通常只允许停用或先迁移样本。
|
||||
56
docs/dev/03-genotyping/02-sample.md
Normal file
56
docs/dev/03-genotyping/02-sample.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# 03 Genotyping - sample 表录入说明
|
||||
|
||||
来源:`docs/architecture/03-genotyping-data-flow.md`
|
||||
|
||||
## 录入目标
|
||||
|
||||
`sample` 是 Genotyping 流程的样本入口,表示送检样本或测序样本。它可以挂到 `plate`,也可以直接关联 `observation_unit`,并冗余保存 program/trial/study 方便查询。
|
||||
|
||||
## 上下游关系
|
||||
|
||||
| 类型 | 内容 |
|
||||
| --- | --- |
|
||||
| 表 | `sample` |
|
||||
| Java 实体 | `SampleEntity` |
|
||||
| 前置依赖 | `plate`、`observation_unit`、`program`、`trial`、`study`,可选 `germplasm_taxon` |
|
||||
| 下游引用 | `callset` |
|
||||
| API | `/brapi/v2/samples` |
|
||||
|
||||
## 字段录入
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 校验规则 |
|
||||
| --- | --- | --- | --- |
|
||||
| `id` | sample 主键 | 系统生成;导入时可指定 | 必填、唯一 |
|
||||
| `auth_user_id` | 数据所属用户 | 登录上下文自动写入 | 不允许前端手填 |
|
||||
| `sample_name` | 样本名称 | 用户录入/批量导入 | 建议必填 |
|
||||
| `sample_barcode` | 样本条码 | 扫码/手填 | 建议唯一 |
|
||||
| `samplepui` | 样本永久标识 | 用户录入/导入 | 可选,建议唯一 |
|
||||
| `sample_description` | 样本说明 | 用户录入 | 可选 |
|
||||
| `sample_type` | 样本类型 | 下拉/文本 | 可选 |
|
||||
| `tissue_type` | 组织类型 | 下拉/文本 | 可选 |
|
||||
| `sample_timestamp` | 取样时间 | 日期时间选择器 | 可选 |
|
||||
| `taken_by` | 取样人 | 文本/人员选择 | 可选 |
|
||||
| `sample_group_db_id` | 样本分组 ID | 文本/导入 | 可选 |
|
||||
| `concentration` | 样本浓度 | 文本/数值 | 可选,建议带单位 |
|
||||
| `volume` | 样本体积 | 文本/数值 | 可选,建议带单位 |
|
||||
| `plate_id` | 所在样本板 | Plate 选择器 | 可选,若填需存在 |
|
||||
| `plate_row` | 板行 | 文本 | 与 plate format 联动校验 |
|
||||
| `plate_column` | 板列 | 数字输入 | 与 plate format 联动校验 |
|
||||
| `well` | 孔位,如 A01 | 文本/自动生成 | 同一 plate 内建议唯一 |
|
||||
| `observation_unit_id` | 对应观测单元 | ObservationUnit 选择器 | 可选,若填需存在 |
|
||||
| `program_id` | 所属项目 | 项目选择器 | 可选,若填需存在 |
|
||||
| `trial_id` | 所属 trial | Trial 选择器 | 可选,若填需存在 |
|
||||
| `study_id` | 所属 study | Study 选择器 | 可选,若填需存在 |
|
||||
| `taxon_id_id` | germplasm taxon | Taxon 选择器 | 可选 |
|
||||
|
||||
## 页面与交互
|
||||
|
||||
- Sample 列表页支持按 sampleName、barcode、plate、study、trial、program、observationUnit 查询。
|
||||
- 从 Study 工作台创建 sample 时默认继承 `study_id/trial_id/program_id`。
|
||||
- 从 ObservationUnit 详情创建 sample 时默认带出 `observation_unit_id`。
|
||||
|
||||
## 关键校验
|
||||
|
||||
1. `sample` 若关联 `observation_unit`,需要检查 observation unit 所属 study 与 sample 的 study 一致。
|
||||
2. 同一 plate 内 `well` 不应重复。
|
||||
3. 删除 sample 前必须检查是否已有 `callset`。
|
||||
44
docs/dev/03-genotyping/03-reference_set.md
Normal file
44
docs/dev/03-genotyping/03-reference_set.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# 03 Genotyping - reference_set 表录入说明
|
||||
|
||||
来源:`docs/architecture/03-genotyping-data-flow.md`
|
||||
|
||||
## 录入目标
|
||||
|
||||
`reference_set` 表示参考基因组集合,是 reference、variantset、variant 的参考侧上游。它可以关联来源 germplasm。
|
||||
|
||||
## 上下游关系
|
||||
|
||||
| 类型 | 内容 |
|
||||
| --- | --- |
|
||||
| 表 | `reference_set` |
|
||||
| Java 实体 | `ReferenceSetEntity` |
|
||||
| 前置依赖 | 可选 `germplasm` |
|
||||
| 下游引用 | `reference`、`variantset`、`variant` |
|
||||
| API | `/brapi/v2/referencesets` |
|
||||
|
||||
## 字段录入
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 校验规则 |
|
||||
| --- | --- | --- | --- |
|
||||
| `id` | reference set 主键 | 系统生成;导入时可指定 | 必填、唯一 |
|
||||
| `auth_user_id` | 数据所属用户 | 登录上下文自动写入 | 不允许前端手填 |
|
||||
| `reference_set_name` | 参考集合名称 | 用户录入 | 建议必填 |
|
||||
| `assemblypui` | assembly 永久标识 | 用户录入/导入 | 可选,建议唯一 |
|
||||
| `description` | 参考集合说明 | 多行文本 | 可选 |
|
||||
| `is_derived` | 是否派生参考 | 开关 | 可选 |
|
||||
| `md5checksum` | 校验值 | 文本 | 可选,格式提示 |
|
||||
| `sourceuri` | 来源 URI | URL 输入 | 可选,URL 格式校验 |
|
||||
| `species_ontology_term` | 物种本体 term | 文本/本体选择器 | 可选 |
|
||||
| `species_ontology_termuri` | 物种本体 URI | URL 输入 | 可选,URL 格式校验 |
|
||||
| `source_germplasm_id` | 来源 germplasm | Germplasm 选择器 | 可选,若填需存在 |
|
||||
|
||||
## 页面与交互
|
||||
|
||||
- 列表页展示 referenceSetName、assemblyPUI、species、sourceGermplasm、reference 数、variantset 数。
|
||||
- 详情页展示 reference、variantset、variant 入口。
|
||||
|
||||
## 关键校验
|
||||
|
||||
1. 删除 reference_set 前检查 `reference`、`variantset`、`variant` 引用。
|
||||
2. 若填写 `source_germplasm_id`,必须引用已存在 germplasm。
|
||||
3. md5 checksum 建议做格式提示,不强行阻断历史数据。
|
||||
40
docs/dev/03-genotyping/04-reference.md
Normal file
40
docs/dev/03-genotyping/04-reference.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# 03 Genotyping - reference 表录入说明
|
||||
|
||||
来源:`docs/architecture/03-genotyping-data-flow.md`
|
||||
|
||||
## 录入目标
|
||||
|
||||
`reference` 表示具体参考序列,例如 chromosome、contig 或 scaffold。它属于一个 `reference_set`,下游可以分页保存序列片段。
|
||||
|
||||
## 上下游关系
|
||||
|
||||
| 类型 | 内容 |
|
||||
| --- | --- |
|
||||
| 表 | `reference` |
|
||||
| Java 实体 | `ReferenceEntity` |
|
||||
| 前置依赖 | `reference_set` |
|
||||
| 下游引用 | `reference_bases` |
|
||||
| API | `/brapi/v2/references` |
|
||||
|
||||
## 字段录入
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 校验规则 |
|
||||
| --- | --- | --- | --- |
|
||||
| `id` | reference 主键 | 系统生成;导入时可指定 | 必填、唯一 |
|
||||
| `auth_user_id` | 数据所属用户 | 登录上下文自动写入 | 不允许前端手填 |
|
||||
| `reference_name` | 参考序列名称 | 用户录入 | 建议必填 |
|
||||
| `reference_set_id` | 所属 reference_set | ReferenceSet 选择器 | 必选,必须存在 |
|
||||
| `length` | 序列长度 | 数字输入/导入 | 可选,非负 |
|
||||
| `md5checksum` | 序列校验值 | 文本 | 可选 |
|
||||
| `source_divergence` | 与来源差异度 | 数字输入 | 可选 |
|
||||
|
||||
## 页面与交互
|
||||
|
||||
- Reference 列表页支持按 referenceSet、referenceName 查询。
|
||||
- Reference 详情页展示 reference_bases 分页和长度、checksum 信息。
|
||||
|
||||
## 关键校验
|
||||
|
||||
1. `reference_set_id` 必须存在。
|
||||
2. 删除 reference 前检查 `reference_bases`。
|
||||
3. 若维护 `reference_bases`,建议校验分页总长度与 `length` 的一致性。
|
||||
37
docs/dev/03-genotyping/05-reference_bases.md
Normal file
37
docs/dev/03-genotyping/05-reference_bases.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# 03 Genotyping - reference_bases 表录入说明
|
||||
|
||||
来源:`docs/architecture/03-genotyping-data-flow.md`
|
||||
|
||||
## 录入目标
|
||||
|
||||
`reference_bases` 保存 reference 的序列片段或分页内容。它是参考序列的明细表,通常通过导入或后端任务写入。
|
||||
|
||||
## 上下游关系
|
||||
|
||||
| 类型 | 内容 |
|
||||
| --- | --- |
|
||||
| 表 | `reference_bases` |
|
||||
| Java 实体 | `ReferenceBasesPageEntity` |
|
||||
| 前置依赖 | `reference` |
|
||||
| 下游引用 | 无 |
|
||||
|
||||
## 字段录入
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 校验规则 |
|
||||
| --- | --- | --- | --- |
|
||||
| `id` | reference bases 主键 | 系统生成;导入时可指定 | 必填、唯一 |
|
||||
| `auth_user_id` | 数据所属用户 | 登录上下文自动写入 | 不允许前端手填 |
|
||||
| `reference_id` | 所属 reference | Reference 选择器/导入 | 必选,必须存在 |
|
||||
| `page_number` | 分页序号 | 数字输入/导入 | 同一 reference 内建议唯一 |
|
||||
| `bases` | 碱基序列片段 | 文本/文件导入 | 最大 2048 字符;建议仅允许 A/C/G/T/N 等字符 |
|
||||
|
||||
## 页面与交互
|
||||
|
||||
- 一般不单独提供复杂 CRUD,可在 Reference 详情页查看或导入。
|
||||
- 长序列建议走文件导入或异步任务,不建议手工逐页录入。
|
||||
|
||||
## 关键校验
|
||||
|
||||
1. `reference_id` 必须存在。
|
||||
2. `bases` 长度不能超过数据库字段限制。
|
||||
3. 同一 reference 下 `page_number` 不应重复。
|
||||
39
docs/dev/03-genotyping/06-variantset.md
Normal file
39
docs/dev/03-genotyping/06-variantset.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# 03 Genotyping - variantset 表录入说明
|
||||
|
||||
来源:`docs/architecture/03-genotyping-data-flow.md`
|
||||
|
||||
## 录入目标
|
||||
|
||||
`variantset` 表示一批 variant 的集合,通常对应一次测序、芯片、DArTSeq 或某个 study 下的位点集合。
|
||||
|
||||
## 上下游关系
|
||||
|
||||
| 类型 | 内容 |
|
||||
| --- | --- |
|
||||
| 表 | `variantset` |
|
||||
| Java 实体 | `VariantSetEntity` |
|
||||
| 前置依赖 | `reference_set`、`study` |
|
||||
| 下游引用 | `variant`、`callset_variant_sets`、`variantset_analysis`、`variantset_format` |
|
||||
| API | `/brapi/v2/variantsets` |
|
||||
|
||||
## 字段录入
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 校验规则 |
|
||||
| --- | --- | --- | --- |
|
||||
| `id` | variantset 主键 | 系统生成;导入时可指定 | 必填、唯一 |
|
||||
| `auth_user_id` | 数据所属用户 | 登录上下文自动写入 | 不允许前端手填 |
|
||||
| `variant_set_name` | 变异集合名称 | 用户录入/导入 | 建议必填 |
|
||||
| `reference_set_id` | 参考基因组集合 | ReferenceSet 选择器 | 建议必选,若填需存在 |
|
||||
| `study_id` | 所属 study | Study 选择器 | 可选,若填需存在 |
|
||||
|
||||
## 页面与交互
|
||||
|
||||
- VariantSet 列表页支持按 referenceSet、study、variantSetName 查询。
|
||||
- 详情页展示 variants、callsets、analysis、available formats。
|
||||
- 从 Study 工作台创建时默认带出 `study_id`。
|
||||
|
||||
## 关键校验
|
||||
|
||||
1. `reference_set_id` 与下属 `variant.reference_set_id` 应保持一致。
|
||||
2. 删除 variantset 前检查 `variant`、`callset_variant_sets`、`variantset_analysis`、`variantset_format`。
|
||||
3. 导入大型 variantset 时建议先建 variantset,再异步导入 variants 和 calls。
|
||||
57
docs/dev/03-genotyping/07-variant.md
Normal file
57
docs/dev/03-genotyping/07-variant.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# 03 Genotyping - variant 表录入说明
|
||||
|
||||
来源:`docs/architecture/03-genotyping-data-flow.md`
|
||||
|
||||
## 录入目标
|
||||
|
||||
`variant` 表示具体变异位点,例如 SNP、Indel 或结构变异。它是位点定义,不是某个样本的结果;样本上的 genotype 结果写入 `allele_call`。
|
||||
|
||||
## 上下游关系
|
||||
|
||||
| 类型 | 内容 |
|
||||
| --- | --- |
|
||||
| 表 | `variant` |
|
||||
| Java 实体 | `VariantEntity` |
|
||||
| 前置依赖 | `reference_set`、`variantset` |
|
||||
| 下游引用 | `allele_call`、`marker_position` |
|
||||
| API | `/brapi/v2/variants` |
|
||||
|
||||
## 字段录入
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 校验规则 |
|
||||
| --- | --- | --- | --- |
|
||||
| `id` | variant 主键 | 系统生成;导入时可指定 | 必填、唯一 |
|
||||
| `auth_user_id` | 数据所属用户 | 登录上下文自动写入 | 不允许前端手填 |
|
||||
| `variant_name` | 位点名称/marker 名称 | 用户录入/导入 | 建议必填 |
|
||||
| `variant_type` | 位点类型,如 SNP/INDEL/SV | 下拉/导入 | 可选,建议枚举 |
|
||||
| `reference_set_id` | 参考集合 | ReferenceSet 选择器 | 可选,若填需存在 |
|
||||
| `variant_set_id` | 所属 variantset | VariantSet 选择器 | 建议必选,若填需存在 |
|
||||
| `reference_bases` | 参考碱基 | 文本/导入 | 可选 |
|
||||
| `variant_start` | 起始位置 | 数字输入/导入 | 可选,非负 |
|
||||
| `variant_end` | 结束位置 | 数字输入/导入 | 可选,不能小于 start |
|
||||
| `svlen` | 结构变异长度 | 数字输入/导入 | 可选 |
|
||||
| `filters_applied` | 是否做过过滤 | 开关/导入 | 可选 |
|
||||
| `filters_passed` | 是否通过过滤 | 开关/导入 | 可选 |
|
||||
| `created` | 创建时间 | 系统写入/导入 | 可选 |
|
||||
| `updated` | 更新时间 | 系统写入/导入 | 可选 |
|
||||
|
||||
## 附属集合字段
|
||||
|
||||
| 附属表 | 内容 |
|
||||
| --- | --- |
|
||||
| `variant_entity_alternate_bases` | alternateBases 列表 |
|
||||
| `variant_entity_ciend` | CIEND 区间 |
|
||||
| `variant_entity_cipos` | CIPOS 区间 |
|
||||
| `variant_entity_filters_failed` | 未通过的 filter 列表 |
|
||||
|
||||
## 页面与交互
|
||||
|
||||
- Variant 列表页支持按 variantSet、referenceSet、variantName、variantType 查询。
|
||||
- 大批量位点建议通过文件导入,不建议普通表单逐条录入。
|
||||
- 详情页展示 allele_call 数量和 marker_position 入口。
|
||||
|
||||
## 关键校验
|
||||
|
||||
1. `variant` 是位点定义,不能把样本 genotype 写在本表。
|
||||
2. `variant_set_id` 和 `reference_set_id` 应与所属 variantset 保持一致。
|
||||
3. 删除 variant 前检查 `allele_call` 和 `marker_position` 引用。
|
||||
40
docs/dev/03-genotyping/08-callset.md
Normal file
40
docs/dev/03-genotyping/08-callset.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# 03 Genotyping - callset 表录入说明
|
||||
|
||||
来源:`docs/architecture/03-genotyping-data-flow.md`
|
||||
|
||||
## 录入目标
|
||||
|
||||
`callset` 表示某个 sample 的一组 genotype calls,通常对应一个样本在一个或多个 variantset 上的调用集合。
|
||||
|
||||
## 上下游关系
|
||||
|
||||
| 类型 | 内容 |
|
||||
| --- | --- |
|
||||
| 表 | `callset` |
|
||||
| Java 实体 | `CallSetEntity` |
|
||||
| 前置依赖 | `sample` |
|
||||
| 下游引用 | `allele_call`、`callset_variant_sets` |
|
||||
| API | `/brapi/v2/callsets` |
|
||||
|
||||
## 字段录入
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 校验规则 |
|
||||
| --- | --- | --- | --- |
|
||||
| `id` | callset 主键 | 系统生成;导入时可指定 | 必填、唯一 |
|
||||
| `auth_user_id` | 数据所属用户 | 登录上下文自动写入 | 不允许前端手填 |
|
||||
| `call_set_name` | 调用集合名称 | 用户录入/导入 | 建议必填,同一 sample 下建议唯一 |
|
||||
| `sample_id` | 所属 sample | Sample 选择器/导入 | 必选,必须存在 |
|
||||
| `created` | 创建时间 | 系统写入/导入 | 可选 |
|
||||
| `updated` | 更新时间 | 系统写入/导入 | 可选 |
|
||||
|
||||
## 页面与交互
|
||||
|
||||
- CallSet 列表页支持按 sample、variantSet、callSetName 查询。
|
||||
- 从 Sample 详情创建时默认带出 `sample_id`。
|
||||
- 需要通过 `callset_variant_sets` 绑定参与的 variantset。
|
||||
|
||||
## 关键校验
|
||||
|
||||
1. `sample_id` 必须存在。
|
||||
2. 删除 callset 前检查 `allele_call` 和 `callset_variant_sets`。
|
||||
3. 如果 callset 绑定多个 variantset,查询和导出时要明确当前 variantset 范围。
|
||||
42
docs/dev/03-genotyping/09-allele_call.md
Normal file
42
docs/dev/03-genotyping/09-allele_call.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# 03 Genotyping - allele_call 表录入说明
|
||||
|
||||
来源:`docs/architecture/03-genotyping-data-flow.md`
|
||||
|
||||
## 录入目标
|
||||
|
||||
`allele_call` 是最终 genotype 调用结果。业务上它就是 Call:一条记录表示某个 sample/callset 在某个 variant 上的 genotype、read depth、likelihood 等结果。
|
||||
|
||||
## 上下游关系
|
||||
|
||||
| 类型 | 内容 |
|
||||
| --- | --- |
|
||||
| 表 | `allele_call` |
|
||||
| Java 实体 | `CallEntity` |
|
||||
| 前置依赖 | `callset`、`variant` |
|
||||
| 下游引用 | 无 |
|
||||
| API | `/brapi/v2/calls` |
|
||||
|
||||
## 字段录入
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 校验规则 |
|
||||
| --- | --- | --- | --- |
|
||||
| `id` | call 主键 | 系统生成;导入时可指定 | 必填、唯一 |
|
||||
| `auth_user_id` | 数据所属用户 | 登录上下文自动写入 | 不允许前端手填 |
|
||||
| `call_set_id` | 所属 callset | CallSet 选择器/导入 | 必选,必须存在 |
|
||||
| `variant_id` | 对应 variant | Variant 选择器/导入 | 必选,必须存在 |
|
||||
| `genotype` | genotype 值,如 0/1、A/T | 文本/导入 | 建议必填 |
|
||||
| `read_depth` | 测序深度 | 数字输入/导入 | 可选,非负 |
|
||||
| `genotype_likelihood` | genotype likelihood | 数字输入/导入 | 可选 |
|
||||
| `phase_set` | phase set | 文本/导入 | 可选 |
|
||||
|
||||
## 页面与交互
|
||||
|
||||
- 通常不做逐条手工录入,主要通过 VCF/HapMap/矩阵文件导入。
|
||||
- 列表页支持按 callset、sample、variantset、variant 查询。
|
||||
- 在 Variant 详情页可查看该位点下的 calls,在 Sample/CallSet 详情页可查看该样本的 calls。
|
||||
|
||||
## 关键校验
|
||||
|
||||
1. `call_set_id` 和 `variant_id` 必须存在。
|
||||
2. 同一 callset 对同一 variant 不应重复。
|
||||
3. `allele_call` 不应直接承担 variant 定义字段;位点信息应来自 `variant`。
|
||||
45
docs/dev/03-genotyping/10-genome_map.md
Normal file
45
docs/dev/03-genotyping/10-genome_map.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# 03 Genotyping - genome_map 表录入说明
|
||||
|
||||
来源:`docs/architecture/03-genotyping-data-flow.md`
|
||||
|
||||
## 录入目标
|
||||
|
||||
`genome_map` 表示遗传图谱,用于组织 linkage group,并通过 marker_position 将 variant 放到图谱坐标上。
|
||||
|
||||
## 上下游关系
|
||||
|
||||
| 类型 | 内容 |
|
||||
| --- | --- |
|
||||
| 表 | `genome_map` |
|
||||
| Java 实体 | `GenomeMapEntity` |
|
||||
| 前置依赖 | `crop`,可关联 `study` |
|
||||
| 下游引用 | `linkageGroup` / `linkage_group` |
|
||||
| API | `/brapi/v2/maps` |
|
||||
|
||||
## 字段录入
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 校验规则 |
|
||||
| --- | --- | --- | --- |
|
||||
| `id` | map 主键 | 系统生成;导入时可指定 | 必填、唯一 |
|
||||
| `auth_user_id` | 数据所属用户 | 登录上下文自动写入 | 不允许前端手填 |
|
||||
| `map_name` | 图谱名称 | 用户录入 | 建议必填 |
|
||||
| `mappui` | map 永久标识 | 用户录入/导入 | 可选,建议唯一 |
|
||||
| `crop_id` | 所属作物 | Crop 选择器 | 可选,若填需存在 |
|
||||
| `scientific_name` | 学名 | 文本 | 可选 |
|
||||
| `type` | 图谱类型 | 下拉/文本 | 可选 |
|
||||
| `unit` | 图谱单位,如 cM | 文本/下拉 | 可选 |
|
||||
| `published_date` | 发表日期 | 日期选择器 | 可选 |
|
||||
| `documentationurl` | 文档链接 | URL 输入 | 可选,URL 格式校验 |
|
||||
| `comments` | 备注 | 多行文本 | 可选 |
|
||||
|
||||
## 页面与交互
|
||||
|
||||
- GenomeMap 列表页支持按 crop、mapName、type 查询。
|
||||
- 详情页展示 linkage groups 和 marker positions。
|
||||
- 可通过图谱详情批量导入 linkage group 与 marker position。
|
||||
|
||||
## 关键校验
|
||||
|
||||
1. 删除 genome_map 前检查 linkage group 引用。
|
||||
2. 若关联 study,应检查 study 的 crop 与 map 的 crop 一致。
|
||||
3. `unit` 应与 marker_position 的 position 语义保持一致。
|
||||
42
docs/dev/03-genotyping/11-linkage_group.md
Normal file
42
docs/dev/03-genotyping/11-linkage_group.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# 03 Genotyping - linkageGroup / linkage_group 表录入说明
|
||||
|
||||
来源:`docs/architecture/03-genotyping-data-flow.md`
|
||||
|
||||
## 命名注意
|
||||
|
||||
架构文档标注 `LinkageGroupEntity` 对应表名是驼峰 `linkageGroup`。当前初始 schema DDL 中实际表名为 `linkage_group`。做 Atlas/Flyway 迁移时,必须先确认目标数据库中的真实表名,避免生成大小写或命名不一致的重复表。
|
||||
|
||||
## 录入目标
|
||||
|
||||
连锁群是 genome_map 下的分组,例如 chromosome 或 linkage group。`marker_position` 会把 variant 放到具体 linkage group 的位置上。
|
||||
|
||||
## 上下游关系
|
||||
|
||||
| 类型 | 内容 |
|
||||
| --- | --- |
|
||||
| Java 实体 | `LinkageGroupEntity` |
|
||||
| 表 | 架构文档:`linkageGroup`;当前 DDL:`linkage_group` |
|
||||
| 前置依赖 | `genome_map` |
|
||||
| 下游引用 | `marker_position` |
|
||||
|
||||
## 字段录入
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 校验规则 |
|
||||
| --- | --- | --- | --- |
|
||||
| `id` | linkage group 主键 | 系统生成;导入时可指定 | 必填、唯一 |
|
||||
| `auth_user_id` | 数据所属用户 | 登录上下文自动写入 | 不允许前端手填 |
|
||||
| `genome_map_id` | 所属 genome_map | GenomeMap 选择器 | 必选,必须存在 |
|
||||
| `linkage_group_name` | 连锁群名称 | 用户录入/导入 | 建议必填,同一 map 下唯一 |
|
||||
| `max_marker_position` | 最大 marker 位置 | 数字输入/导入 | 可选,非负 |
|
||||
|
||||
## 页面与交互
|
||||
|
||||
- 通常作为 GenomeMap 详情页的子表维护。
|
||||
- 支持批量导入 linkage group。
|
||||
- 详情页展示该 linkage group 下的 marker_position 列表。
|
||||
|
||||
## 关键校验
|
||||
|
||||
1. `genome_map_id` 必须存在。
|
||||
2. 同一 genome_map 下 `linkage_group_name` 不应重复。
|
||||
3. 删除 linkage group 前检查 `marker_position` 引用。
|
||||
39
docs/dev/03-genotyping/12-marker_position.md
Normal file
39
docs/dev/03-genotyping/12-marker_position.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# 03 Genotyping - marker_position 表录入说明
|
||||
|
||||
来源:`docs/architecture/03-genotyping-data-flow.md`
|
||||
|
||||
## 录入目标
|
||||
|
||||
`marker_position` 记录 marker/variant 在 linkage group 上的位置,是遗传图谱定位侧与 variant 位点侧的连接表。
|
||||
|
||||
## 上下游关系
|
||||
|
||||
| 类型 | 内容 |
|
||||
| --- | --- |
|
||||
| 表 | `marker_position` |
|
||||
| Java 实体 | `MarkerPositionEntity` |
|
||||
| 前置依赖 | `linkageGroup` / `linkage_group`、`variant` |
|
||||
| 下游引用 | 无 |
|
||||
| API | `/brapi/v2/markerpositions` |
|
||||
|
||||
## 字段录入
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 校验规则 |
|
||||
| --- | --- | --- | --- |
|
||||
| `id` | marker position 主键 | 系统生成;导入时可指定 | 必填、唯一 |
|
||||
| `auth_user_id` | 数据所属用户 | 登录上下文自动写入 | 不允许前端手填 |
|
||||
| `linkage_group_id` | 所属 linkage group | LinkageGroup 选择器/导入 | 必选,必须存在 |
|
||||
| `variant_id` | 对应 variant | Variant 选择器/导入 | 必选,必须存在 |
|
||||
| `position` | 图谱位置 | 数字输入/导入 | 必填,非负 |
|
||||
|
||||
## 页面与交互
|
||||
|
||||
- 可在 GenomeMap/LinkageGroup 详情页批量维护。
|
||||
- 列表页支持按 map、linkageGroup、variant 查询。
|
||||
- Variant 详情页可展示 marker_position 信息。
|
||||
|
||||
## 关键校验
|
||||
|
||||
1. `linkage_group_id` 和 `variant_id` 必须存在。
|
||||
2. 同一 linkage group 下同一 variant 不应重复。
|
||||
3. `position` 不应超过 linkage group 的 `max_marker_position`。
|
||||
34
docs/dev/03-genotyping/13-callset_variant_sets.md
Normal file
34
docs/dev/03-genotyping/13-callset_variant_sets.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# 03 Genotyping - callset_variant_sets 表录入说明
|
||||
|
||||
来源:`docs/architecture/03-genotyping-data-flow.md`
|
||||
|
||||
## 录入目标
|
||||
|
||||
`callset_variant_sets` 是 `callset` 与 `variantset` 的多对多关系表,表示某个样本调用集合覆盖了哪些 variantset。
|
||||
|
||||
## 上下游关系
|
||||
|
||||
| 类型 | 内容 |
|
||||
| --- | --- |
|
||||
| 表 | `callset_variant_sets` |
|
||||
| 前置依赖 | `callset`、`variantset` |
|
||||
| 下游引用 | 查询、导出和 allele matrix 范围过滤 |
|
||||
|
||||
## 字段录入
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 校验规则 |
|
||||
| --- | --- | --- | --- |
|
||||
| `call_sets_id` | callset 主键 | CallSet 选择器/导入 | 必选,必须存在 |
|
||||
| `variant_sets_id` | variantset 主键 | VariantSet 选择器/导入 | 必选,必须存在 |
|
||||
|
||||
## 页面与交互
|
||||
|
||||
- 通常在 CallSet 详情页或 VariantSet 详情页维护,不建议独立做主菜单。
|
||||
- 创建 callset 后可选择一个或多个 variantset。
|
||||
- 大批量导入 calls 时可以由导入任务自动创建关系。
|
||||
|
||||
## 关键校验
|
||||
|
||||
1. `call_sets_id` 和 `variant_sets_id` 必须存在。
|
||||
2. 同一 callset 与 variantset 关系不应重复。
|
||||
3. 删除关系不应删除 callset 或 variantset 主数据。
|
||||
45
docs/dev/03-genotyping/14-variantset_analysis.md
Normal file
45
docs/dev/03-genotyping/14-variantset_analysis.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# 03 Genotyping - variantset_analysis 表录入说明
|
||||
|
||||
来源:`docs/architecture/03-genotyping-data-flow.md`
|
||||
|
||||
## 录入目标
|
||||
|
||||
`variantset_analysis` 记录 variantset 的分析或 QC 信息,例如分析名称、软件、类型、描述和时间。它是 variantset 的附属明细表。
|
||||
|
||||
## 上下游关系
|
||||
|
||||
| 类型 | 内容 |
|
||||
| --- | --- |
|
||||
| 表 | `variantset_analysis` |
|
||||
| Java 实体 | `VariantSetAnalysisEntity` |
|
||||
| 前置依赖 | `variantset` |
|
||||
| 下游引用 | 分析信息展示 |
|
||||
|
||||
## 字段录入
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 校验规则 |
|
||||
| --- | --- | --- | --- |
|
||||
| `id` | analysis 主键 | 系统生成;导入时可指定 | 必填、唯一 |
|
||||
| `variant_set_id` | 所属 variantset | VariantSet 选择器/导入 | 必选,必须存在 |
|
||||
| `analysis_name` | 分析名称 | 用户录入 | 建议必填 |
|
||||
| `description` | 分析说明 | 多行文本 | 可选 |
|
||||
| `type` | 分析类型,如 QC | 下拉/文本 | 可选 |
|
||||
| `created` | 创建时间 | 日期时间 | 可选 |
|
||||
| `updated` | 更新时间 | 日期时间 | 可选 |
|
||||
|
||||
## 附属集合字段
|
||||
|
||||
| 附属表 | 内容 |
|
||||
| --- | --- |
|
||||
| `variant_set_analysis_entity_software` | software 列表,如软件名称、版本或 URL |
|
||||
|
||||
## 页面与交互
|
||||
|
||||
- 在 VariantSet 详情页以 Analysis Tab 维护。
|
||||
- 支持添加多条分析记录,每条可维护多个 software。
|
||||
|
||||
## 关键校验
|
||||
|
||||
1. `variant_set_id` 必须存在。
|
||||
2. 删除 variantset 时需要先处理或级联处理 analysis。
|
||||
3. `software` 如果是 URL,前端可做 URL 格式提示。
|
||||
42
docs/dev/03-genotyping/15-variantset_format.md
Normal file
42
docs/dev/03-genotyping/15-variantset_format.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# 03 Genotyping - variantset_format 表录入说明
|
||||
|
||||
来源:`docs/architecture/03-genotyping-data-flow.md`
|
||||
|
||||
## 录入目标
|
||||
|
||||
`variantset_format` 记录 variantset 可用的数据格式和文件地址,例如 allele matrix、VCF、HapMap 或 CSV。它用于下载、导出和矩阵读取。
|
||||
|
||||
## 上下游关系
|
||||
|
||||
| 类型 | 内容 |
|
||||
| --- | --- |
|
||||
| 表 | `variantset_format` |
|
||||
| Java 实体 | `VariantSetAvailableFormatEntity` |
|
||||
| 前置依赖 | `variantset` |
|
||||
| 下游引用 | 文件下载、allele matrix 展示、导出 |
|
||||
|
||||
## 字段录入
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 校验规则 |
|
||||
| --- | --- | --- | --- |
|
||||
| `id` | format 主键 | 系统生成;导入时可指定 | 必填、唯一 |
|
||||
| `variant_set_id` | 所属 variantset | VariantSet 选择器/导入 | 必选,必须存在 |
|
||||
| `data_format` | 数据格式,如 VCF/HapMap/矩阵 | 枚举选择 | 建议必填 |
|
||||
| `file_format` | MIME/文件格式 | 枚举选择 | 建议必填 |
|
||||
| `fileurl` | 文件 URL | URL 输入/导入 | 可选,URL 格式校验 |
|
||||
| `expand_homozygotes` | 是否展开纯合位点 | 开关 | 可选 |
|
||||
| `sep_phased` | phased genotype 分隔符 | 文本 | 可选 |
|
||||
| `sep_unphased` | unphased genotype 分隔符 | 文本 | 可选 |
|
||||
| `unknown_string` | 缺失值字符串 | 文本 | 可选 |
|
||||
|
||||
## 页面与交互
|
||||
|
||||
- 在 VariantSet 详情页以 Formats Tab 维护。
|
||||
- 支持一组 variantset 配置多个可下载格式。
|
||||
- 点击 `fileurl` 可下载或预览,对 allele matrix URL 可进入矩阵查看。
|
||||
|
||||
## 关键校验
|
||||
|
||||
1. `variant_set_id` 必须存在。
|
||||
2. `fileurl` 如填写需通过 URL 格式校验。
|
||||
3. 对矩阵格式,`sep_phased/sep_unphased/unknown_string` 会影响解析,应在导入预览时展示。
|
||||
45
docs/dev/03-genotyping/README.md
Normal file
45
docs/dev/03-genotyping/README.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# 03 Genotyping 开发录入说明
|
||||
|
||||
来源:`docs/architecture/03-genotyping-data-flow.md`
|
||||
|
||||
本目录按 Genotyping 模块的数据录入顺序拆分开发说明。主线是:
|
||||
|
||||
```text
|
||||
Core/Phenotyping 上游数据 -> plate / sample
|
||||
reference_set -> reference -> reference_bases
|
||||
reference_set + study -> variantset -> variant
|
||||
sample -> callset
|
||||
callset + variantset -> callset_variant_sets
|
||||
callset + variant -> allele_call
|
||||
genome_map -> linkageGroup/linkage_group -> marker_position -> variant
|
||||
```
|
||||
|
||||
## 文档清单
|
||||
|
||||
| 顺序 | 文档 | 表 | 作用 |
|
||||
| --- | --- | --- | --- |
|
||||
| 01 | `01-plate.md` | `plate` | 样本板 |
|
||||
| 02 | `02-sample.md` | `sample` | 送检样本/测序样本 |
|
||||
| 03 | `03-reference_set.md` | `reference_set` | 参考基因组集合 |
|
||||
| 04 | `04-reference.md` | `reference` | 参考序列 |
|
||||
| 05 | `05-reference_bases.md` | `reference_bases` | 参考序列片段/分页 |
|
||||
| 06 | `06-variantset.md` | `variantset` | 变异集合 |
|
||||
| 07 | `07-variant.md` | `variant` | 变异位点 |
|
||||
| 08 | `08-callset.md` | `callset` | 样本调用集合 |
|
||||
| 09 | `09-allele_call.md` | `allele_call` | genotype 调用结果 |
|
||||
| 10 | `10-genome_map.md` | `genome_map` | 遗传图谱 |
|
||||
| 11 | `11-linkage_group.md` | `linkageGroup` / `linkage_group` | 连锁群 |
|
||||
| 12 | `12-marker_position.md` | `marker_position` | marker/variant 图谱位置 |
|
||||
| 13 | `13-callset_variant_sets.md` | `callset_variant_sets` | callset 与 variantset 关系 |
|
||||
| 14 | `14-variantset_analysis.md` | `variantset_analysis` | variantset 分析信息 |
|
||||
| 15 | `15-variantset_format.md` | `variantset_format` | variantset 可用文件/矩阵格式 |
|
||||
|
||||
## 全局注意点
|
||||
|
||||
1. `CallEntity` 对应真实业务表是 `allele_call`,不要新建 `call` 表。
|
||||
2. `CallSetEntity` 对应 `callset`,不是 `call_set`。
|
||||
3. `VariantSetEntity` 对应 `variantset`,不是 `variant_set`。
|
||||
4. 架构文档标注 `LinkageGroupEntity` 的表名为 `linkageGroup`;当前初始 DDL 中表名为 `linkage_group`。做迁移前必须以实际数据库 catalog 为准。
|
||||
5. `sample` 是 Genotyping 样本入口,向上关联 `plate/observation_unit/study/trial/program`。
|
||||
6. `variant` 是位点定义,`allele_call` 是某个样本/callset 在位点上的结果。
|
||||
7. `reference_set/reference/reference_bases` 是参考基因组侧;`variantset/variant/callset/allele_call` 是变异和结果侧。
|
||||
37
docs/dev/04-germplasm/01-breeding_method.md
Normal file
37
docs/dev/04-germplasm/01-breeding_method.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# 04 Germplasm / Seed - breeding_method 表录入说明
|
||||
|
||||
来源:`docs/architecture/04-germplasm-seed-data-flow.md`
|
||||
|
||||
## 录入目标
|
||||
|
||||
`breeding_method` 是育种方法字典,用来说明 germplasm 是通过什么方式形成的,例如杂交选育、回交、自交系选育、诱变、DH、克隆选择等。
|
||||
|
||||
## 上下游关系
|
||||
|
||||
| 类型 | 内容 |
|
||||
| --- | --- |
|
||||
| 表 | `breeding_method` |
|
||||
| Java 实体 | `BreedingMethodEntity` |
|
||||
| 前置依赖 | 无 |
|
||||
| 下游引用 | `germplasm.breeding_method_id` |
|
||||
| API | `/brapi/v2/breedingmethods` |
|
||||
|
||||
## 字段录入
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 校验规则 |
|
||||
| --- | --- | --- | --- |
|
||||
| `id` | 育种方法主键 | 系统生成;导入可指定 | 必填、唯一 |
|
||||
| `auth_user_id` | 数据所属用户 | 登录上下文自动写入 | 不允许前端手填 |
|
||||
| `name` | 方法名称 | 用户录入 | 必填,建议唯一 |
|
||||
| `abbreviation` | 方法缩写 | 用户录入 | 可选,建议唯一 |
|
||||
| `description` | 方法说明 | 多行文本 | 可选 |
|
||||
|
||||
## 页面与交互
|
||||
|
||||
- 列表页展示 name、abbreviation、description、引用 germplasm 数。
|
||||
- 新增/编辑页为简单字典表单。
|
||||
|
||||
## 关键校验
|
||||
|
||||
1. `name` 必填。
|
||||
2. 已被 `germplasm` 引用时不允许物理删除,只允许停用或提示引用关系。
|
||||
68
docs/dev/04-germplasm/02-germplasm.md
Normal file
68
docs/dev/04-germplasm/02-germplasm.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# 04 Germplasm / Seed - germplasm 表录入说明
|
||||
|
||||
来源:`docs/architecture/04-germplasm-seed-data-flow.md`
|
||||
|
||||
## 录入目标
|
||||
|
||||
`germplasm` 是种质主表,保存 accession、PUI、分类、采集、来源、育种方法和展示名称等身份信息。它不是库存批次;库存批次由 `seed_lot` 表达。
|
||||
|
||||
## 上下游关系
|
||||
|
||||
| 类型 | 内容 |
|
||||
| --- | --- |
|
||||
| 表 | `germplasm` |
|
||||
| Java 实体 | `GermplasmEntity` |
|
||||
| 前置依赖 | `crop`,可选 `breeding_method` |
|
||||
| 下游引用 | `germplasm_attribute_value`、`germplasm_donor`、`germplasm_institute`、`germplasm_origin`、`germplasm_synonym`、`germplasm_taxon`、`pedigree_node`、`cross_parent`、`seed_lot_content_mixture` |
|
||||
| API | `/brapi/v2/germplasm` |
|
||||
|
||||
## 字段录入
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 校验规则 |
|
||||
| --- | --- | --- | --- |
|
||||
| `id` | germplasm 主键 | 系统生成;导入可指定 | 必填、唯一 |
|
||||
| `auth_user_id` | 数据所属用户 | 登录上下文自动写入 | 不允许前端手填 |
|
||||
| `germplasm_name` | 种质名称 | 用户录入/导入 | 建议必填 |
|
||||
| `default_display_name` | 默认展示名称 | 用户录入/自动生成 | 可选,缺省用 germplasm_name |
|
||||
| `germplasmpui` | 种质永久标识 | 用户录入/导入 | 可选,建议唯一 |
|
||||
| `accession_number` | accession 编号 | 用户录入/导入 | 可选,建议唯一 |
|
||||
| `crop_id` | 所属作物 | Crop 选择器 | 建议必选,若填需存在 |
|
||||
| `breeding_method_id` | 育种方法 | BreedingMethod 选择器 | 可选,若填需存在 |
|
||||
| `genus` | 属 | 用户录入 | 可选 |
|
||||
| `species` | 种 | 用户录入 | 可选 |
|
||||
| `species_authority` | 种命名人 | 用户录入 | 可选 |
|
||||
| `subtaxa` | 亚种/变种 | 用户录入 | 可选 |
|
||||
| `subtaxa_authority` | 亚种命名人 | 用户录入 | 可选 |
|
||||
| `country_of_origin_code` | 来源国家代码 | 国家选择器 | 可选 |
|
||||
| `collection` | collection | 用户录入 | 可选 |
|
||||
| `acquisition_date` | 获取日期 | 日期选择器 | 可选 |
|
||||
| `acquisition_source_code` | 获取来源编码 | 枚举选择 | 可选 |
|
||||
| `biological_status_of_accession_code` | 生物状态编码 | 枚举选择 | 可选 |
|
||||
| `mls_status` | MLS 状态 | 枚举选择 | 可选 |
|
||||
| `germplasm_preprocessing` | 种质预处理说明 | 文本 | 可选 |
|
||||
| `seed_source` | 种子来源描述字段 | 文本 | 可选,不等于 seed_lot |
|
||||
| `seed_source_description` | 种子来源补充说明 | 多行文本 | 可选 |
|
||||
| `documentationurl` | 文档链接 | URL 输入 | 可选,URL 格式校验 |
|
||||
|
||||
## 附属信息
|
||||
|
||||
| 附属表 | 内容 |
|
||||
| --- | --- |
|
||||
| `germplasm_donor` | donor accession、donor institute |
|
||||
| `germplasm_institute` | host、donor、breeding、collecting、redundant 等机构 |
|
||||
| `germplasm_origin` | 来源坐标和坐标不确定性 |
|
||||
| `germplasm_synonym` | 别名及别名类型 |
|
||||
| `germplasm_taxon` | taxon 来源和值 |
|
||||
| `germplasm_entity_type_of_germplasm_storage_code` | storage type code 列表 |
|
||||
|
||||
## 页面与交互
|
||||
|
||||
- Germplasm 列表页支持按 crop、germplasmName、accessionNumber、PUI、synonym 查询。
|
||||
- 详情页以 Tab 展示 attributes、donors、institutes、origin、synonyms、taxon、pedigree、seed lots、cross parent 记录。
|
||||
- 从 Study/ObservationUnit 或 SeedLot 进入时应能回到 germplasm 详情。
|
||||
|
||||
## 关键校验
|
||||
|
||||
1. `germplasm_name` 建议必填,`accession_number` 和 `germplasmpui` 建议唯一。
|
||||
2. 删除 germplasm 前必须检查属性值、seed lot 组成、cross parent、pedigree、sample/taxon 等引用。
|
||||
3. 不要用 `seed_source` 表达库存;库存必须走 `seed_lot`。
|
||||
39
docs/dev/04-germplasm/03-germplasm_donor.md
Normal file
39
docs/dev/04-germplasm/03-germplasm_donor.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# 04 Germplasm / Seed - germplasm_donor 表录入说明
|
||||
|
||||
来源:`docs/architecture/04-germplasm-seed-data-flow.md`
|
||||
|
||||
## 录入目标
|
||||
|
||||
`germplasm_donor` 记录某个 germplasm 的 donor accession 和 donor institute 信息,是 germplasm 的附属明细。
|
||||
|
||||
## 上下游关系
|
||||
|
||||
| 类型 | 内容 |
|
||||
| --- | --- |
|
||||
| 表 | `germplasm_donor` |
|
||||
| Java 实体 | `DonorEntity` |
|
||||
| 前置依赖 | `germplasm` |
|
||||
| 下游引用 | MCPD 展示、来源追踪 |
|
||||
|
||||
## 字段录入
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 校验规则 |
|
||||
| --- | --- | --- | --- |
|
||||
| `id` | donor 记录主键 | 系统生成;导入可指定 | 必填、唯一 |
|
||||
| `auth_user_id` | 数据所属用户 | 登录上下文自动写入 | 不允许前端手填 |
|
||||
| `germplasm_id` | 所属 germplasm | Germplasm 选择器/详情页带出 | 必选,必须存在 |
|
||||
| `donor_accession_number` | donor accession 编号 | 用户录入 | 可选 |
|
||||
| `donor_institute_code` | donor 机构代码 | 用户录入/选择 | 可选 |
|
||||
| `donor_institute_name` | donor 机构名称 | 用户录入/选择 | 可选 |
|
||||
| `germplasmpui` | donor germplasm PUI | 用户录入 | 可选 |
|
||||
|
||||
## 页面与交互
|
||||
|
||||
- 在 Germplasm 详情页 Donor Tab 内维护。
|
||||
- 支持一条 germplasm 维护多个 donor 记录。
|
||||
|
||||
## 关键校验
|
||||
|
||||
1. `germplasm_id` 必须存在。
|
||||
2. 同一 germplasm 下 donor accession + institute code 不建议重复。
|
||||
3. 删除 donor 记录不应删除 germplasm 主数据。
|
||||
38
docs/dev/04-germplasm/04-germplasm_institute.md
Normal file
38
docs/dev/04-germplasm/04-germplasm_institute.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# 04 Germplasm / Seed - germplasm_institute 表录入说明
|
||||
|
||||
来源:`docs/architecture/04-germplasm-seed-data-flow.md`
|
||||
|
||||
## 录入目标
|
||||
|
||||
`germplasm_institute` 记录 germplasm 相关机构,包括 host、donor、breeding、collecting、redundant 等类型。
|
||||
|
||||
## 上下游关系
|
||||
|
||||
| 类型 | 内容 |
|
||||
| --- | --- |
|
||||
| 表 | `germplasm_institute` |
|
||||
| Java 实体 | `GermplasmInstituteEntity` |
|
||||
| 前置依赖 | `germplasm` |
|
||||
| 下游引用 | Germplasm MCPD、机构筛选 |
|
||||
|
||||
## 字段录入
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 校验规则 |
|
||||
| --- | --- | --- | --- |
|
||||
| `id` | 机构记录主键 | 系统生成 | 必填、唯一 |
|
||||
| `germplasm_id` | 所属 germplasm | Germplasm 选择器/详情页带出 | 必选,必须存在 |
|
||||
| `institute_type` | 机构类型 | 枚举选择 | HOST、DONOR、BREEDING、COLLECTING、REDUNDANT |
|
||||
| `institute_code` | 机构代码 | 用户录入 | 可选 |
|
||||
| `institute_name` | 机构名称 | 用户录入 | 建议必填 |
|
||||
| `institute_address` | 机构地址 | 多行文本 | 可选 |
|
||||
|
||||
## 页面与交互
|
||||
|
||||
- 在 Germplasm 详情页 Institute Tab 内维护。
|
||||
- 可把 HOST institute 作为 germplasm 主信息摘要展示。
|
||||
|
||||
## 关键校验
|
||||
|
||||
1. `germplasm_id` 必须存在。
|
||||
2. 同一 germplasm 下同类型、同 code 的机构不建议重复。
|
||||
3. 删除 institute 记录不应删除 germplasm 主数据。
|
||||
36
docs/dev/04-germplasm/05-germplasm_origin.md
Normal file
36
docs/dev/04-germplasm/05-germplasm_origin.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# 04 Germplasm / Seed - germplasm_origin 表录入说明
|
||||
|
||||
来源:`docs/architecture/04-germplasm-seed-data-flow.md`
|
||||
|
||||
## 录入目标
|
||||
|
||||
`germplasm_origin` 记录 germplasm 的来源地坐标和坐标不确定性,适合表达采集地点或原产地的空间信息。
|
||||
|
||||
## 上下游关系
|
||||
|
||||
| 类型 | 内容 |
|
||||
| --- | --- |
|
||||
| 表 | `germplasm_origin` |
|
||||
| Java 实体 | `GermplasmOriginEntity` |
|
||||
| 前置依赖 | `germplasm`,可选 `geojson` |
|
||||
| 下游引用 | 地理来源展示、MCPD |
|
||||
|
||||
## 字段录入
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 校验规则 |
|
||||
| --- | --- | --- | --- |
|
||||
| `id` | origin 记录主键 | 系统生成 | 必填、唯一 |
|
||||
| `germplasm_id` | 所属 germplasm | Germplasm 选择器/详情页带出 | 必选,必须存在 |
|
||||
| `coordinate_uncertainty` | 坐标不确定性 | 文本/数字 | 可选 |
|
||||
| `coordinates_id` | GeoJSON 坐标对象 | 地图取点/GeoJSON 导入 | 可选,若填需存在 |
|
||||
|
||||
## 页面与交互
|
||||
|
||||
- 在 Germplasm 详情页 Origin Tab 内维护。
|
||||
- 支持地图取点和 GeoJSON 查看。
|
||||
|
||||
## 关键校验
|
||||
|
||||
1. `germplasm_id` 必须存在。
|
||||
2. 坐标格式需要合法。
|
||||
3. 删除 origin 记录不应删除 germplasm 主数据。
|
||||
36
docs/dev/04-germplasm/06-germplasm_synonym.md
Normal file
36
docs/dev/04-germplasm/06-germplasm_synonym.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# 04 Germplasm / Seed - germplasm_synonym 表录入说明
|
||||
|
||||
来源:`docs/architecture/04-germplasm-seed-data-flow.md`
|
||||
|
||||
## 录入目标
|
||||
|
||||
`germplasm_synonym` 记录 germplasm 的别名、旧名、商品名或本地名,用于检索和展示。
|
||||
|
||||
## 上下游关系
|
||||
|
||||
| 类型 | 内容 |
|
||||
| --- | --- |
|
||||
| 表 | `germplasm_synonym` |
|
||||
| Java 实体 | `GermplasmSynonymEntity` |
|
||||
| 前置依赖 | `germplasm` |
|
||||
| 下游引用 | Germplasm 搜索、详情展示 |
|
||||
|
||||
## 字段录入
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 校验规则 |
|
||||
| --- | --- | --- | --- |
|
||||
| `id` | synonym 记录主键 | 系统生成 | 必填、唯一 |
|
||||
| `germplasm_id` | 所属 germplasm | Germplasm 选择器/详情页带出 | 必选,必须存在 |
|
||||
| `synonym` | 别名 | 用户录入 | 必填 |
|
||||
| `type` | 别名类型 | 下拉/文本 | 可选,如 local、commercial、old name |
|
||||
|
||||
## 页面与交互
|
||||
|
||||
- 在 Germplasm 详情页 Synonym Tab 内维护。
|
||||
- Germplasm 列表搜索应支持 synonym 命中。
|
||||
|
||||
## 关键校验
|
||||
|
||||
1. `germplasm_id` 必须存在。
|
||||
2. 同一 germplasm 下同一个 synonym 不应重复。
|
||||
3. 删除 synonym 不应删除 germplasm 主数据。
|
||||
36
docs/dev/04-germplasm/07-germplasm_taxon.md
Normal file
36
docs/dev/04-germplasm/07-germplasm_taxon.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# 04 Germplasm / Seed - germplasm_taxon 表录入说明
|
||||
|
||||
来源:`docs/architecture/04-germplasm-seed-data-flow.md`
|
||||
|
||||
## 录入目标
|
||||
|
||||
`germplasm_taxon` 记录外部 taxon 标识和来源,用于把 germplasm 连接到分类数据库或外部生物分类体系。该表由 `TaxonEntity` 映射。
|
||||
|
||||
## 上下游关系
|
||||
|
||||
| 类型 | 内容 |
|
||||
| --- | --- |
|
||||
| 表 | `germplasm_taxon` |
|
||||
| Java 实体 | `TaxonEntity` |
|
||||
| 前置依赖 | `germplasm` |
|
||||
| 下游引用 | `sample.taxon_id_id`、分类展示 |
|
||||
|
||||
## 字段录入
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 校验规则 |
|
||||
| --- | --- | --- | --- |
|
||||
| `id` | taxon 记录主键 | 系统生成 | 必填、唯一 |
|
||||
| `germplasm_id` | 所属 germplasm | Germplasm 选择器/详情页带出 | 必选,必须存在 |
|
||||
| `source_name` | taxon 来源名称 | 文本/下拉 | 可选,如 NCBI Taxonomy |
|
||||
| `taxon_id` | taxon 标识 | 用户录入 | 必填 |
|
||||
|
||||
## 页面与交互
|
||||
|
||||
- 在 Germplasm 详情页 Taxon Tab 内维护。
|
||||
- Sample 表可引用 taxon 记录。
|
||||
|
||||
## 关键校验
|
||||
|
||||
1. `germplasm_id` 必须存在。
|
||||
2. 同一 source 下 taxon_id 不建议重复。
|
||||
3. 删除 taxon 前检查 sample 引用。
|
||||
55
docs/dev/04-germplasm/08-germplasm_attribute_definition.md
Normal file
55
docs/dev/04-germplasm/08-germplasm_attribute_definition.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# 04 Germplasm / Seed - germplasm_attribute_definition 表录入说明
|
||||
|
||||
来源:`docs/architecture/04-germplasm-seed-data-flow.md`
|
||||
|
||||
## 录入目标
|
||||
|
||||
`germplasm_attribute_definition` 定义 germplasm 可维护的属性。它继承变量定义体系,可关联 crop、trait、method、scale、ontology。
|
||||
|
||||
## 上下游关系
|
||||
|
||||
| 类型 | 内容 |
|
||||
| --- | --- |
|
||||
| 表 | `germplasm_attribute_definition` |
|
||||
| Java 实体 | `GermplasmAttributeDefinitionEntity` |
|
||||
| 前置依赖 | `crop`、`trait`、`method`、`scale`、`ontology` |
|
||||
| 下游引用 | `germplasm_attribute_value` |
|
||||
| API | `/brapi/v2/attributes` |
|
||||
|
||||
## 字段录入
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 校验规则 |
|
||||
| --- | --- | --- | --- |
|
||||
| `id` | 属性定义主键 | 系统生成;导入可指定 | 必填、唯一 |
|
||||
| `auth_user_id` | 数据所属用户 | 登录上下文自动写入 | 不允许前端手填 |
|
||||
| `name` | 属性名称 | 用户录入 | 必填 |
|
||||
| `code` | 属性编码 | 用户录入 | 可选,建议唯一 |
|
||||
| `pui` | 永久标识 | 用户录入 | 可选,建议唯一 |
|
||||
| `attribute_category` | 属性分类 | 下拉/文本 | 可选 |
|
||||
| `datatype` | 数据类型 | 下拉 | 建议必填 |
|
||||
| `description` | 属性说明 | 多行文本 | 可选 |
|
||||
| `uri` | 外部 URI | URL 输入 | 可选,URL 格式校验 |
|
||||
| `crop_id` | 作物 | Crop 选择器 | 可选 |
|
||||
| `trait_id` | 性状 | Trait 选择器 | 可选 |
|
||||
| `method_id` | 方法 | Method 选择器 | 可选 |
|
||||
| `scale_id` | 标尺 | Scale 选择器 | 可选 |
|
||||
| `ontology_id` | 本体 | Ontology 选择器 | 可选 |
|
||||
| `documentationurl` | 文档链接 | URL 输入 | 可选 |
|
||||
| `status` | 状态 | 下拉/文本 | 可选 |
|
||||
| `default_value` | 默认值 | 文本 | 可选 |
|
||||
| `growth_stage` | 生育期 | 文本 | 可选 |
|
||||
| `institution` | 机构 | 文本 | 可选 |
|
||||
| `language` | 语言 | 文本 | 可选 |
|
||||
| `scientist` | 科学家/维护人 | 文本 | 可选 |
|
||||
| `submission_timestamp` | 提交时间 | 日期时间 | 可选 |
|
||||
|
||||
## 页面与交互
|
||||
|
||||
- Attribute Definition 列表页支持按 crop、category、datatype、keyword 查询。
|
||||
- 创建 attribute value 前必须先有 definition。
|
||||
|
||||
## 关键校验
|
||||
|
||||
1. `name` 必填。
|
||||
2. 已被 `germplasm_attribute_value` 引用时不允许物理删除。
|
||||
3. `datatype` 要与 value 输入控件联动。
|
||||
39
docs/dev/04-germplasm/09-germplasm_attribute_value.md
Normal file
39
docs/dev/04-germplasm/09-germplasm_attribute_value.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# 04 Germplasm / Seed - germplasm_attribute_value 表录入说明
|
||||
|
||||
来源:`docs/architecture/04-germplasm-seed-data-flow.md`
|
||||
|
||||
## 录入目标
|
||||
|
||||
`germplasm_attribute_value` 保存某个 germplasm 在某个属性定义上的实际取值。
|
||||
|
||||
## 上下游关系
|
||||
|
||||
| 类型 | 内容 |
|
||||
| --- | --- |
|
||||
| 表 | `germplasm_attribute_value` |
|
||||
| Java 实体 | `GermplasmAttributeValueEntity` |
|
||||
| 前置依赖 | `germplasm`、`germplasm_attribute_definition` |
|
||||
| 下游引用 | 属性查询、详情展示 |
|
||||
| API | `/brapi/v2/attributevalues` |
|
||||
|
||||
## 字段录入
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 校验规则 |
|
||||
| --- | --- | --- | --- |
|
||||
| `id` | 属性值主键 | 系统生成;导入可指定 | 必填、唯一 |
|
||||
| `auth_user_id` | 数据所属用户 | 登录上下文自动写入 | 不允许前端手填 |
|
||||
| `germplasm_id` | 所属 germplasm | Germplasm 选择器/详情页带出 | 必选,必须存在 |
|
||||
| `attribute_id` | 属性定义 | Attribute 选择器 | 必选,必须存在 |
|
||||
| `value` | 属性值 | 根据 definition.datatype 选择控件 | 必填或按业务要求 |
|
||||
| `determined_date` | 测定日期 | 日期选择器 | 可选 |
|
||||
|
||||
## 页面与交互
|
||||
|
||||
- 在 Germplasm 详情页 Attributes Tab 维护。
|
||||
- 批量导入时按 germplasm + attribute 组成矩阵录入更高效。
|
||||
|
||||
## 关键校验
|
||||
|
||||
1. `germplasm_id` 与 `attribute_id` 必须存在。
|
||||
2. 同一 germplasm 下同一 attribute 不建议重复,若允许多次测定,需要用 determined_date 区分。
|
||||
3. `value` 必须符合 attribute definition 的 datatype。
|
||||
38
docs/dev/04-germplasm/10-crossing_project.md
Normal file
38
docs/dev/04-germplasm/10-crossing_project.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# 04 Germplasm / Seed - crossing_project 表录入说明
|
||||
|
||||
来源:`docs/architecture/04-germplasm-seed-data-flow.md`
|
||||
|
||||
## 录入目标
|
||||
|
||||
`crossing_project` 表示杂交项目,是 cross、planned cross、cross parent 和 pedigree node 的上游组织维度,通常挂在 Core 的 `program` 下。
|
||||
|
||||
## 上下游关系
|
||||
|
||||
| 类型 | 内容 |
|
||||
| --- | --- |
|
||||
| 表 | `crossing_project` |
|
||||
| Java 实体 | `CrossingProjectEntity` |
|
||||
| 前置依赖 | `program` |
|
||||
| 下游引用 | `cross_entity`、`cross_parent`、`pedigree_node` |
|
||||
| API | `/brapi/v2/crossingprojects` |
|
||||
|
||||
## 字段录入
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 校验规则 |
|
||||
| --- | --- | --- | --- |
|
||||
| `id` | crossing project 主键 | 系统生成;导入可指定 | 必填、唯一 |
|
||||
| `auth_user_id` | 数据所属用户 | 登录上下文自动写入 | 不允许前端手填 |
|
||||
| `name` | 杂交项目名称 | 用户录入 | 必填,建议同一 program 下唯一 |
|
||||
| `description` | 项目说明 | 多行文本 | 可选 |
|
||||
| `program_id` | 所属 program | Program 选择器 | 必选,必须存在 |
|
||||
|
||||
## 页面与交互
|
||||
|
||||
- 列表页支持按 program、keyword 查询。
|
||||
- 详情页展示 crosses、planned crosses、potential parents、pedigree nodes。
|
||||
|
||||
## 关键校验
|
||||
|
||||
1. `program_id` 必须存在。
|
||||
2. 删除 crossing_project 前检查 cross、cross_parent、pedigree_node 引用。
|
||||
3. 如果 program 有 crop,创建 cross 时亲本 germplasm 建议与 program crop 一致。
|
||||
50
docs/dev/04-germplasm/11-cross_entity.md
Normal file
50
docs/dev/04-germplasm/11-cross_entity.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# 04 Germplasm / Seed - cross_entity 表录入说明
|
||||
|
||||
来源:`docs/architecture/04-germplasm-seed-data-flow.md`
|
||||
|
||||
## 录入目标
|
||||
|
||||
`cross_entity` 是 Cross 和 PlannedCross 的统一落库表。`plannedcross` 不是独立数据库表,计划杂交通过 `planned=true` 表达;实际杂交可以通过 `planned_cross_id` 关联计划杂交。
|
||||
|
||||
## 上下游关系
|
||||
|
||||
| 类型 | 内容 |
|
||||
| --- | --- |
|
||||
| 表 | `cross_entity` |
|
||||
| Java 实体 | `CrossEntity` |
|
||||
| 前置依赖 | `crossing_project`,可选自关联 `cross_entity` |
|
||||
| 下游引用 | `cross_parent`、`cross_pollination_event`、`seed_lot_content_mixture` |
|
||||
| API | `/brapi/v2/crosses`、`/brapi/v2/plannedcrosses` |
|
||||
|
||||
## 字段录入
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 校验规则 |
|
||||
| --- | --- | --- | --- |
|
||||
| `id` | cross 主键 | 系统生成;导入可指定 | 必填、唯一 |
|
||||
| `auth_user_id` | 数据所属用户 | 登录上下文自动写入 | 不允许前端手填 |
|
||||
| `name` | cross 名称 | 用户录入/自动生成 | 建议必填 |
|
||||
| `cross_type` | 杂交类型 | 枚举选择 | 可选 |
|
||||
| `crossing_project_id` | 所属 crossing project | CrossingProject 选择器 | 建议必选,若填需存在 |
|
||||
| `status` | planned cross 状态 | 枚举选择 | 计划杂交常用 |
|
||||
| `planned` | 是否为计划杂交 | 开关 | 必填,默认 false |
|
||||
| `planned_cross_id` | 关联计划杂交 | Cross 选择器 | 可选,必须指向 `planned=true` 的 cross |
|
||||
|
||||
## 附属集合字段
|
||||
|
||||
| 附属表 | 内容 |
|
||||
| --- | --- |
|
||||
| `cross_entity_cross_attributes` | crossAttributes 列表 |
|
||||
| `cross_parent` | cross 的亲本 |
|
||||
| `cross_pollination_event` | 授粉事件 |
|
||||
|
||||
## 页面与交互
|
||||
|
||||
- Cross 列表页展示实际杂交;PlannedCross 列表页展示 `planned=true` 数据。
|
||||
- 新建实际 cross 时可选择 planned cross,并继承 planned cross 的亲本作为初始值。
|
||||
- 详情页展示 parents、pollination events 和 seed lot 组成入口。
|
||||
|
||||
## 关键校验
|
||||
|
||||
1. `plannedcross` 不新建表,所有 planned cross 走 `cross_entity`。
|
||||
2. `planned_cross_id` 不能指向自己。
|
||||
3. 删除 cross 前检查 `cross_parent`、`cross_pollination_event`、`seed_lot_content_mixture` 引用。
|
||||
39
docs/dev/04-germplasm/12-cross_parent.md
Normal file
39
docs/dev/04-germplasm/12-cross_parent.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# 04 Germplasm / Seed - cross_parent 表录入说明
|
||||
|
||||
来源:`docs/architecture/04-germplasm-seed-data-flow.md`
|
||||
|
||||
## 录入目标
|
||||
|
||||
`cross_parent` 连接 cross 与亲本来源。亲本可以是 germplasm,也可以是 observation_unit,用来表达实际田间单株或 plot 来源。
|
||||
|
||||
## 上下游关系
|
||||
|
||||
| 类型 | 内容 |
|
||||
| --- | --- |
|
||||
| 表 | `cross_parent` |
|
||||
| Java 实体 | `CrossParentEntity` |
|
||||
| 前置依赖 | `cross_entity`、`crossing_project`、`germplasm` 或 `observation_unit` |
|
||||
| 下游引用 | 杂交亲本展示和校验 |
|
||||
|
||||
## 字段录入
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 校验规则 |
|
||||
| --- | --- | --- | --- |
|
||||
| `id` | cross parent 主键 | 系统生成 | 必填、唯一 |
|
||||
| `cross_id` | 所属 cross | Cross 选择器/详情页带出 | 必选,必须存在 |
|
||||
| `crossing_project_id` | 所属 crossing project | 自动带出/选择器 | 建议与 cross 一致 |
|
||||
| `germplasm_id` | 亲本 germplasm | Germplasm 选择器 | 与 observation_unit 至少填一个 |
|
||||
| `observation_unit_id` | 亲本 observation unit | ObservationUnit 选择器 | 与 germplasm 至少填一个 |
|
||||
| `parent_type` | 亲本类型 | 枚举选择 | FEMALE、MALE、SELF、POPULATION、CLONAL 等 |
|
||||
|
||||
## 页面与交互
|
||||
|
||||
- 在 Cross 或 PlannedCross 详情页 Parents Tab 内维护。
|
||||
- 创建 cross 时至少添加一个亲本;常见杂交需要 FEMALE 和 MALE。
|
||||
|
||||
## 关键校验
|
||||
|
||||
1. `cross_id` 必须存在。
|
||||
2. `germplasm_id` 和 `observation_unit_id` 至少填写一个,不建议同时为空。
|
||||
3. 同一 cross 下相同 parentType + germplasm/observationUnit 不应重复。
|
||||
4. 如果填 `crossing_project_id`,应与 `cross.crossing_project_id` 一致。
|
||||
37
docs/dev/04-germplasm/13-cross_pollination_event.md
Normal file
37
docs/dev/04-germplasm/13-cross_pollination_event.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# 04 Germplasm / Seed - cross_pollination_event 表录入说明
|
||||
|
||||
来源:`docs/architecture/04-germplasm-seed-data-flow.md`
|
||||
|
||||
## 录入目标
|
||||
|
||||
`cross_pollination_event` 记录实际杂交过程中的授粉事件,包括授粉编号、时间和是否成功。
|
||||
|
||||
## 上下游关系
|
||||
|
||||
| 类型 | 内容 |
|
||||
| --- | --- |
|
||||
| 表 | `cross_pollination_event` |
|
||||
| Java 实体 | `CrossPollinationEventEntity` |
|
||||
| 前置依赖 | `cross_entity` |
|
||||
| 下游引用 | Cross 过程追踪 |
|
||||
|
||||
## 字段录入
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 校验规则 |
|
||||
| --- | --- | --- | --- |
|
||||
| `id` | 授粉事件主键 | 系统生成 | 必填、唯一 |
|
||||
| `cross_id` | 所属 cross | Cross 选择器/详情页带出 | 必选,必须存在 |
|
||||
| `pollination_number` | 授粉编号 | 用户录入/自动生成 | 可选,建议同一 cross 下唯一 |
|
||||
| `pollination_successful` | 是否授粉成功 | 开关 | 可选 |
|
||||
| `pollination_time_stamp` | 授粉时间 | 日期时间选择器 | 可选 |
|
||||
|
||||
## 页面与交互
|
||||
|
||||
- 在 Cross 详情页 Pollination Events Tab 内维护。
|
||||
- 可以按授粉时间排序展示。
|
||||
|
||||
## 关键校验
|
||||
|
||||
1. `cross_id` 必须存在。
|
||||
2. 同一 cross 下 `pollination_number` 不建议重复。
|
||||
3. 删除授粉事件不应删除 cross 主数据。
|
||||
40
docs/dev/04-germplasm/14-pedigree_node.md
Normal file
40
docs/dev/04-germplasm/14-pedigree_node.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# 04 Germplasm / Seed - pedigree_node 表录入说明
|
||||
|
||||
来源:`docs/architecture/04-germplasm-seed-data-flow.md`
|
||||
|
||||
## 录入目标
|
||||
|
||||
`pedigree_node` 表示系谱中的一个节点,一个节点通常关联一个 germplasm,并可归属 crossing_project。节点之间的 parent/child/sibling 关系由 `pedigree_edge` 记录。
|
||||
|
||||
## 上下游关系
|
||||
|
||||
| 类型 | 内容 |
|
||||
| --- | --- |
|
||||
| 表 | `pedigree_node` |
|
||||
| Java 实体 | `PedigreeNodeEntity` |
|
||||
| 前置依赖 | `germplasm`、可选 `crossing_project` |
|
||||
| 下游引用 | `pedigree_edge` |
|
||||
| API | `/brapi/v2/pedigree` |
|
||||
|
||||
## 字段录入
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 校验规则 |
|
||||
| --- | --- | --- | --- |
|
||||
| `id` | pedigree node 主键 | 系统生成;导入可指定 | 必填、唯一 |
|
||||
| `auth_user_id` | 数据所属用户 | 登录上下文自动写入 | 不允许前端手填 |
|
||||
| `germplasm_id` | 对应 germplasm | Germplasm 选择器 | 建议必选,若填需存在 |
|
||||
| `crossing_project_id` | 所属 crossing project | CrossingProject 选择器 | 可选 |
|
||||
| `crossing_year` | 杂交年份 | 年份输入 | 可选 |
|
||||
| `family_code` | 家系编号 | 文本 | 可选 |
|
||||
| `pedigree_string` | 系谱字符串 | 文本 | 可选 |
|
||||
|
||||
## 页面与交互
|
||||
|
||||
- 可从 Germplasm 详情页进入 Pedigree Tab。
|
||||
- 支持图谱视图和表格视图。
|
||||
|
||||
## 关键校验
|
||||
|
||||
1. 同一 germplasm 通常只应有一个 pedigree node。
|
||||
2. 删除 pedigree_node 前检查 `pedigree_edge` 中 this_node 和 connceted_node 引用。
|
||||
3. 导入 pedigree 时需要先创建所有节点,再创建边。
|
||||
43
docs/dev/04-germplasm/15-pedigree_edge.md
Normal file
43
docs/dev/04-germplasm/15-pedigree_edge.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# 04 Germplasm / Seed - pedigree_edge 表录入说明
|
||||
|
||||
来源:`docs/architecture/04-germplasm-seed-data-flow.md`
|
||||
|
||||
## 录入目标
|
||||
|
||||
`pedigree_edge` 描述两个 pedigree_node 之间的 parent、child 或 sibling 关系,并标注亲本类型。
|
||||
|
||||
## 上下游关系
|
||||
|
||||
| 类型 | 内容 |
|
||||
| --- | --- |
|
||||
| 表 | `pedigree_edge` |
|
||||
| Java 实体 | `PedigreeEdgeEntity` |
|
||||
| 前置依赖 | `pedigree_node` |
|
||||
| 下游引用 | 系谱查询、亲本/后代展示 |
|
||||
| API | `/brapi/v2/pedigree` |
|
||||
|
||||
## 字段录入
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 校验规则 |
|
||||
| --- | --- | --- | --- |
|
||||
| `id` | pedigree edge 主键 | 系统生成;导入可指定 | 必填、唯一 |
|
||||
| `auth_user_id` | 数据所属用户 | 登录上下文自动写入 | 不允许前端手填 |
|
||||
| `this_node_id` | 当前节点 | PedigreeNode 选择器 | 必选,必须存在 |
|
||||
| `connceted_node_id` | 关联节点 | PedigreeNode 选择器 | 必选,必须存在 |
|
||||
| `edge_type` | 关系类型 | 枚举选择 | parent、child、sibling |
|
||||
| `parent_type` | 亲本类型 | 枚举选择 | 可选,如 FEMALE、MALE |
|
||||
|
||||
## 命名注意
|
||||
|
||||
实体字段是 `conncetedNode`,拼写与 `connected` 不同;开发 DTO、SQL、迁移时要留意当前代码和数据库列名。
|
||||
|
||||
## 页面与交互
|
||||
|
||||
- 在 Germplasm/Pedigree 图谱视图中维护。
|
||||
- 创建 parent 关系时可自动补齐反向 child 关系,具体以业务实现为准。
|
||||
|
||||
## 关键校验
|
||||
|
||||
1. `this_node_id` 和 `connceted_node_id` 必须存在。
|
||||
2. 两个节点不能相同。
|
||||
3. 同一节点之间同一种 edge_type 不应重复。
|
||||
46
docs/dev/04-germplasm/16-seed_lot.md
Normal file
46
docs/dev/04-germplasm/16-seed_lot.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# 04 Germplasm / Seed - seed_lot 表录入说明
|
||||
|
||||
来源:`docs/architecture/04-germplasm-seed-data-flow.md`
|
||||
|
||||
## 录入目标
|
||||
|
||||
`seed_lot` 是真实库存批次,描述某一批种子当前数量、单位、库位和项目归属。同一个 germplasm 可以有多个 seed lot。
|
||||
|
||||
## 上下游关系
|
||||
|
||||
| 类型 | 内容 |
|
||||
| --- | --- |
|
||||
| 表 | `seed_lot` |
|
||||
| Java 实体 | `SeedLotEntity` |
|
||||
| 前置依赖 | 可选 `location`、`program` |
|
||||
| 下游引用 | `seed_lot_content_mixture`、`seed_lot_transaction` |
|
||||
| API | `/brapi/v2/seedlots` |
|
||||
|
||||
## 字段录入
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 校验规则 |
|
||||
| --- | --- | --- | --- |
|
||||
| `id` | seed lot 主键 | 系统生成;导入可指定 | 必填、唯一 |
|
||||
| `auth_user_id` | 数据所属用户 | 登录上下文自动写入 | 不允许前端手填 |
|
||||
| `name` | 批次名称 | 用户录入/自动生成 | 必填,建议同一 program 下唯一 |
|
||||
| `amount` | 当前库存数量 | 交易自动更新/用户录入初始值 | 非负 |
|
||||
| `units` | 数量单位 | 下拉/文本 | 建议必填 |
|
||||
| `created_date` | 创建时间 | 系统写入/导入 | 可选 |
|
||||
| `last_updated` | 最后更新时间 | 系统更新 | 不建议手填 |
|
||||
| `description` | 批次说明 | 多行文本 | 可选 |
|
||||
| `source_collection` | 来源 collection | 文本 | 可选 |
|
||||
| `storage_location` | 具体库位 | 文本 | 可选 |
|
||||
| `location_id` | 所在地点/库位 | Location 选择器 | 可选,若填需存在 |
|
||||
| `program_id` | 所属项目 | Program 选择器 | 可选,若填需存在 |
|
||||
|
||||
## 页面与交互
|
||||
|
||||
- SeedLot 列表页支持按 program、location、name、库存状态查询。
|
||||
- 详情页展示 content mixture 和 transactions。
|
||||
- 创建 seed lot 后建议至少维护一条 `seed_lot_content_mixture`。
|
||||
|
||||
## 关键校验
|
||||
|
||||
1. `amount` 不允许为负。
|
||||
2. 普通用户不应直接编辑 amount,库存变化应通过 `seed_lot_transaction`。
|
||||
3. 删除 seed lot 前检查组成明细和交易引用。
|
||||
38
docs/dev/04-germplasm/17-seed_lot_content_mixture.md
Normal file
38
docs/dev/04-germplasm/17-seed_lot_content_mixture.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# 04 Germplasm / Seed - seed_lot_content_mixture 表录入说明
|
||||
|
||||
来源:`docs/architecture/04-germplasm-seed-data-flow.md`
|
||||
|
||||
## 录入目标
|
||||
|
||||
`seed_lot_content_mixture` 表示 seed lot 的组成明细。它可以把 seed lot 连接到 germplasm,也可以连接到 cross_entity,适合表达混合批次或杂交产生的批次。
|
||||
|
||||
## 上下游关系
|
||||
|
||||
| 类型 | 内容 |
|
||||
| --- | --- |
|
||||
| 表 | `seed_lot_content_mixture` |
|
||||
| Java 实体 | `SeedLotContentMixtureEntity` |
|
||||
| 前置依赖 | `seed_lot`、可选 `germplasm`、可选 `cross_entity` |
|
||||
| 下游引用 | Seed lot 组成展示 |
|
||||
|
||||
## 字段录入
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 校验规则 |
|
||||
| --- | --- | --- | --- |
|
||||
| `id` | 组成明细主键 | 系统生成 | 必填、唯一 |
|
||||
| `seed_lot_id` | 所属 seed lot | SeedLot 选择器/详情页带出 | 必选,必须存在 |
|
||||
| `germplasm_id` | 组成 germplasm | Germplasm 选择器 | 与 cross_id 至少填一个 |
|
||||
| `cross_id` | 组成来源 cross | Cross 选择器 | 与 germplasm_id 至少填一个 |
|
||||
| `mixture_percentage` | 组成比例 | 数字输入 | 0-100 |
|
||||
|
||||
## 页面与交互
|
||||
|
||||
- 在 SeedLot 详情页 Content Mixture Tab 内维护。
|
||||
- 支持多个组成明细,合计比例可展示为进度或校验提示。
|
||||
|
||||
## 关键校验
|
||||
|
||||
1. `seed_lot_id` 必须存在。
|
||||
2. `germplasm_id` 与 `cross_id` 至少填写一个。
|
||||
3. 同一 seed lot 下 mixture_percentage 合计建议为 100。
|
||||
4. 删除组成明细不应删除 seed lot、germplasm 或 cross 主数据。
|
||||
43
docs/dev/04-germplasm/18-seed_lot_transaction.md
Normal file
43
docs/dev/04-germplasm/18-seed_lot_transaction.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# 04 Germplasm / Seed - seed_lot_transaction 表录入说明
|
||||
|
||||
来源:`docs/architecture/04-germplasm-seed-data-flow.md`
|
||||
|
||||
## 录入目标
|
||||
|
||||
`seed_lot_transaction` 记录 seed lot 之间的库存流转,例如入库、出库、转移、分装、合并或消耗。它表达 from seed lot 到 to seed lot 的数量变化。
|
||||
|
||||
## 上下游关系
|
||||
|
||||
| 类型 | 内容 |
|
||||
| --- | --- |
|
||||
| 表 | `seed_lot_transaction` |
|
||||
| Java 实体 | `SeedLotTransactionEntity` |
|
||||
| 前置依赖 | `from_seed_lot`、`to_seed_lot` |
|
||||
| 下游引用 | 库存追踪、库存数量更新 |
|
||||
| API | `/brapi/v2/seedlots/transactions`、`/brapi/v2/seedlots/{seedLotDbId}/transactions` |
|
||||
|
||||
## 字段录入
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 校验规则 |
|
||||
| --- | --- | --- | --- |
|
||||
| `id` | transaction 主键 | 系统生成;导入可指定 | 必填、唯一 |
|
||||
| `auth_user_id` | 数据所属用户 | 登录上下文自动写入 | 不允许前端手填 |
|
||||
| `from_seed_lot_id` | 来源 seed lot | SeedLot 选择器 | 入库场景可为空,若填需存在 |
|
||||
| `to_seed_lot_id` | 目标 seed lot | SeedLot 选择器 | 出库/消耗场景可为空,若填需存在 |
|
||||
| `amount` | 流转数量 | 数字输入 | 必填,正数 |
|
||||
| `units` | 数量单位 | 下拉/文本 | 必填,应与批次单位一致或可换算 |
|
||||
| `timestamp` | 流转时间 | 日期时间选择器 | 默认当前时间 |
|
||||
| `description` | 流转说明 | 多行文本 | 可选 |
|
||||
|
||||
## 页面与交互
|
||||
|
||||
- 在 SeedLot 详情页 Transactions Tab 展示相关流转。
|
||||
- 新增交易后应更新 from/to seed lot 的 amount 和 last_updated。
|
||||
- 支持按时间、from/to seed lot、program、location 查询。
|
||||
|
||||
## 关键校验
|
||||
|
||||
1. `from_seed_lot_id` 和 `to_seed_lot_id` 至少填写一个。
|
||||
2. `from_seed_lot_id` 不能等于 `to_seed_lot_id`。
|
||||
3. 出库或转移时,来源批次数量不能被扣成负数。
|
||||
4. transaction 是业务动作痕迹,原则上不允许随意物理删除。
|
||||
46
docs/dev/04-germplasm/README.md
Normal file
46
docs/dev/04-germplasm/README.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# 04 Germplasm / Seed 开发录入说明
|
||||
|
||||
来源:`docs/architecture/04-germplasm-seed-data-flow.md`
|
||||
|
||||
本目录按 Germplasm / Seed 模块的数据录入顺序拆分开发说明。主线是:
|
||||
|
||||
```text
|
||||
breeding_method -> germplasm
|
||||
germplasm_attribute_definition -> germplasm_attribute_value
|
||||
program -> crossing_project -> cross_entity -> cross_parent
|
||||
germplasm / cross_entity -> seed_lot_content_mixture -> seed_lot
|
||||
seed_lot -> seed_lot_transaction
|
||||
germplasm -> pedigree_node -> pedigree_edge
|
||||
```
|
||||
|
||||
## 文档清单
|
||||
|
||||
| 顺序 | 文档 | 表 | 作用 |
|
||||
| --- | --- | --- | --- |
|
||||
| 01 | `01-breeding_method.md` | `breeding_method` | 育种方法字典 |
|
||||
| 02 | `02-germplasm.md` | `germplasm` | 种质主表 |
|
||||
| 03 | `03-germplasm_donor.md` | `germplasm_donor` | donor 信息 |
|
||||
| 04 | `04-germplasm_institute.md` | `germplasm_institute` | 机构信息 |
|
||||
| 05 | `05-germplasm_origin.md` | `germplasm_origin` | 来源地/坐标 |
|
||||
| 06 | `06-germplasm_synonym.md` | `germplasm_synonym` | 别名 |
|
||||
| 07 | `07-germplasm_taxon.md` | `germplasm_taxon` | taxon 标识 |
|
||||
| 08 | `08-germplasm_attribute_definition.md` | `germplasm_attribute_definition` | 属性定义 |
|
||||
| 09 | `09-germplasm_attribute_value.md` | `germplasm_attribute_value` | 属性值 |
|
||||
| 10 | `10-crossing_project.md` | `crossing_project` | 杂交项目 |
|
||||
| 11 | `11-cross_entity.md` | `cross_entity` | Cross / PlannedCross |
|
||||
| 12 | `12-cross_parent.md` | `cross_parent` | 杂交亲本 |
|
||||
| 13 | `13-cross_pollination_event.md` | `cross_pollination_event` | 授粉事件 |
|
||||
| 14 | `14-pedigree_node.md` | `pedigree_node` | 系谱节点 |
|
||||
| 15 | `15-pedigree_edge.md` | `pedigree_edge` | 系谱边 |
|
||||
| 16 | `16-seed_lot.md` | `seed_lot` | 种子库存批次 |
|
||||
| 17 | `17-seed_lot_content_mixture.md` | `seed_lot_content_mixture` | 批次组成 |
|
||||
| 18 | `18-seed_lot_transaction.md` | `seed_lot_transaction` | 批次流转 |
|
||||
|
||||
## 全局注意点
|
||||
|
||||
1. `plannedcross` 没有独立数据库表,统一落在 `cross_entity`,通过 `planned` 和 `planned_cross_id` 自关联表达。
|
||||
2. `germplasm.seedSource` / `seedSourceDescription` 是种质来源描述,不等于真实库存批次。
|
||||
3. 真正表达库存的是 `seed_lot`,批次与种质或杂交来源的关系在 `seed_lot_content_mixture`。
|
||||
4. `seed_lot_transaction` 表达 seed lot 到 seed lot 的流转,不直接表达 seed lot 到 germplasm。
|
||||
5. 属性定义和值要分开:`germplasm_attribute_definition` 定义“可填什么”,`germplasm_attribute_value` 记录“某个 germplasm 填了什么”。
|
||||
6. 系谱关系用 `pedigree_node` / `pedigree_edge`;杂交流程用 `cross_entity` / `cross_parent`,两条线都可以回到 `germplasm`。
|
||||
533
docs/dev/backend/auth.md
Normal file
533
docs/dev/backend/auth.md
Normal file
@@ -0,0 +1,533 @@
|
||||
# 后端登录注册接口说明
|
||||
|
||||
本文档描述 `common-br-api` 项目中用户认证(注册、登录、Token 校验)的后端实现,便于前后端联调与部署排查。
|
||||
|
||||
---
|
||||
|
||||
## 1. 总览
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| 路由前缀 | `/auth` |
|
||||
| 框架 | FastAPI |
|
||||
| 用户表 | `sys_users` |
|
||||
| 会话表 | `user_sessions` |
|
||||
| 密码算法 | PBKDF2-HMAC-SHA256(210000 次迭代) |
|
||||
| Token 形式 | 自实现 JWT 风格 Bearer Token(HS256 签名) |
|
||||
| 默认有效期 | 24 小时(1440 分钟) |
|
||||
|
||||
**当前已实现的 HTTP 接口:**
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| `POST` | `/auth/register` | 注册并自动登录 |
|
||||
| `POST` | `/auth/login` | 账号密码登录 |
|
||||
|
||||
**尚未实现的接口(前端 UI 可能已预留):**
|
||||
|
||||
- `/auth/logout`(登出 / 服务端吊销会话)
|
||||
- `/auth/refresh`(刷新 Token)
|
||||
- 手机号 / 短信验证码登录
|
||||
|
||||
---
|
||||
|
||||
## 2. 代码结构
|
||||
|
||||
```
|
||||
app/
|
||||
├── api/
|
||||
│ └── auth.py # 注册、登录、get_current_user 依赖
|
||||
├── core/
|
||||
│ └── security.py # 密码哈希、Token 签发、Token 哈希
|
||||
├── models/
|
||||
│ └── auth.py # User、UserSession ORM 模型
|
||||
├── config/
|
||||
│ └── settings.py # JWT 密钥、过期时间等配置
|
||||
└── main.py # 挂载 auth 路由
|
||||
|
||||
migrations/versions/
|
||||
└── 7c91a64f2b2e_create_auth_tables.py # 建表迁移
|
||||
```
|
||||
|
||||
路由挂载(`app/main.py`):
|
||||
|
||||
```python
|
||||
app.include_router(auth_router) # 无前缀叠加,router 自身 prefix="/auth"
|
||||
```
|
||||
|
||||
因此完整 URL 为:
|
||||
|
||||
```text
|
||||
http://<host>:8000/auth/register
|
||||
http://<host>:8000/auth/login
|
||||
```
|
||||
|
||||
经 Next.js 代理后(开发环境)也可访问:
|
||||
|
||||
```text
|
||||
http://localhost:3000/auth/register
|
||||
http://localhost:3000/auth/login
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 认证流程
|
||||
|
||||
### 3.1 注册 / 登录发 Token
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client as 客户端
|
||||
participant API as /auth/*
|
||||
participant DB as PostgreSQL
|
||||
|
||||
Client->>API: POST /auth/register 或 /auth/login
|
||||
API->>DB: 查重 / 校验密码
|
||||
API->>API: create_access_token(sub, account, jti)
|
||||
API->>DB: INSERT user_sessions(jti, token_hash, expires_at)
|
||||
API-->>Client: TokenResponse(access_token, expires_at, user)
|
||||
```
|
||||
|
||||
### 3.2 受保护接口校验 Token
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client as 客户端
|
||||
participant API as 业务接口
|
||||
participant DB as PostgreSQL
|
||||
|
||||
Client->>API: Authorization: Bearer <token>
|
||||
API->>API: 校验 JWT 签名与 exp
|
||||
API->>DB: JOIN sys_users + user_sessions<br/>匹配 user_id, jti, token_hash
|
||||
alt 会话有效
|
||||
API-->>Client: 200 + 业务数据
|
||||
else 无效或过期
|
||||
API-->>Client: 401 / 403
|
||||
end
|
||||
```
|
||||
|
||||
**要点:**
|
||||
|
||||
1. Token 不仅校验 JWT 签名和过期时间,还必须在 `user_sessions` 表中有对应且未吊销的记录。
|
||||
2. 数据库存的是 Token 的 **SHA256 哈希**,不是明文 Token。
|
||||
3. 每个 Token 有唯一 `jti`(JWT ID),用于会话追踪。
|
||||
|
||||
---
|
||||
|
||||
## 4. 数据库设计
|
||||
|
||||
### 4.1 `sys_users`
|
||||
|
||||
| 字段 | 类型 | 约束 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `id` | UUID | PK | 用户 ID |
|
||||
| `account` | VARCHAR(100) | UNIQUE, NOT NULL | 登录账号 |
|
||||
| `password_hash` | TEXT | NOT NULL | PBKDF2 哈希串 |
|
||||
| `name` | VARCHAR(100) | NOT NULL | 显示名称 |
|
||||
| `email` | VARCHAR(255) | UNIQUE, NOT NULL | 邮箱(注册时转小写) |
|
||||
| `phone` | VARCHAR(50) | NULL | 手机号(可选) |
|
||||
| `created_at` | TIMESTAMPTZ | NOT NULL | 创建时间 |
|
||||
| `updated_at` | TIMESTAMPTZ | NOT NULL | 更新时间 |
|
||||
|
||||
索引:
|
||||
|
||||
- `ux_users_account`(account 唯一)
|
||||
- `ux_users_email`(email 唯一)
|
||||
|
||||
### 4.2 `user_sessions`
|
||||
|
||||
| 字段 | 类型 | 约束 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `id` | UUID | PK | 会话记录 ID |
|
||||
| `user_id` | UUID | FK → sys_users.id, CASCADE | 所属用户 |
|
||||
| `jti` | VARCHAR(64) | UNIQUE, NOT NULL | Token 会话 ID |
|
||||
| `token_hash` | TEXT | NOT NULL | SHA256(access_token) |
|
||||
| `expires_at` | TIMESTAMPTZ | NOT NULL | 过期时间 |
|
||||
| `revoked_at` | TIMESTAMPTZ | NULL | 吊销时间(预留,当前无 logout 接口写入) |
|
||||
| `created_at` | TIMESTAMPTZ | NOT NULL | 创建时间 |
|
||||
| `updated_at` | TIMESTAMPTZ | NOT NULL | 更新时间 |
|
||||
|
||||
---
|
||||
|
||||
## 5. 环境配置
|
||||
|
||||
配置类:`app/config/settings.py`
|
||||
|
||||
| 环境变量 | 默认值 | 说明 |
|
||||
|----------|--------|------|
|
||||
| `JWT_SECRET_KEY` | `change-me-in-production` | HMAC 签名密钥,**生产必须修改** |
|
||||
| `JWT_ALGORITHM` | `HS256` | 写入 JWT Header(实际签名为 HMAC-SHA256) |
|
||||
| `ACCESS_TOKEN_EXPIRE_MINUTES` | `1440`(24h) | Token 有效期(分钟) |
|
||||
| `DATABASE_URL` | 见 `.env.example` | PostgreSQL 连接串 |
|
||||
|
||||
示例(`.env`):
|
||||
|
||||
```env
|
||||
JWT_SECRET_KEY=your-long-random-secret
|
||||
DATABASE_URL=postgresql+asyncpg://user:pass@postgres:5432/brapi-python
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 密码安全
|
||||
|
||||
实现文件:`app/core/security.py`
|
||||
|
||||
### 6.1 哈希格式
|
||||
|
||||
```
|
||||
pbkdf2_sha256$210000$<salt_hex>$<digest_hex>
|
||||
```
|
||||
|
||||
- 算法:`PBKDF2-HMAC-SHA256`
|
||||
- 迭代次数:`210000`
|
||||
- Salt:16 字节随机 hex(`secrets.token_hex(16)`)
|
||||
|
||||
### 6.2 注册时
|
||||
|
||||
```python
|
||||
password_hash=hash_password(payload.password)
|
||||
```
|
||||
|
||||
### 6.3 登录时
|
||||
|
||||
```python
|
||||
verify_password(payload.password, user.password_hash)
|
||||
```
|
||||
|
||||
校验失败统一返回 `401 Invalid account or password`,不区分「账号不存在」与「密码错误」。
|
||||
|
||||
---
|
||||
|
||||
## 7. Access Token 结构
|
||||
|
||||
Token 由 `create_access_token()` 生成,格式为三段式 Base64URL:
|
||||
|
||||
```text
|
||||
<base64url(header)>.<base64url(payload)>.<base64url(signature)>
|
||||
```
|
||||
|
||||
### 7.1 Header
|
||||
|
||||
```json
|
||||
{"alg": "HS256", "typ": "JWT"}
|
||||
```
|
||||
|
||||
### 7.2 Payload
|
||||
|
||||
```json
|
||||
{
|
||||
"sub": "<user_uuid>",
|
||||
"account": "<login_account>",
|
||||
"jti": "<32_hex_chars>",
|
||||
"iat": 1710000000,
|
||||
"exp": 1710086400
|
||||
}
|
||||
```
|
||||
|
||||
| Claim | 含义 |
|
||||
|-------|------|
|
||||
| `sub` | 用户 UUID 字符串 |
|
||||
| `account` | 登录账号 |
|
||||
| `jti` | 会话唯一 ID |
|
||||
| `iat` | 签发时间(Unix 秒) |
|
||||
| `exp` | 过期时间(Unix 秒) |
|
||||
|
||||
### 7.3 签名
|
||||
|
||||
```python
|
||||
HMAC-SHA256(key=JWT_SECRET_KEY, msg=f"{header_b64}.{payload_b64}")
|
||||
```
|
||||
|
||||
> 注意:项目使用**自实现**的 JWT 编解码(`app/api/auth.py` + `app/core/security.py`),未依赖 PyJWT 库。
|
||||
|
||||
---
|
||||
|
||||
## 8. HTTP 接口详情
|
||||
|
||||
### 8.1 注册 `POST /auth/register`
|
||||
|
||||
**请求体 `RegisterRequest`:**
|
||||
|
||||
| 字段 | 类型 | 必填 | 校验 |
|
||||
|------|------|------|------|
|
||||
| `account` | string | 是 | 长度 3–100 |
|
||||
| `password` | string | 是 | 长度 6–128 |
|
||||
| `name` | string | 是 | 长度 1–100 |
|
||||
| `email` | string | 是 | 长度 3–255,入库前转小写 |
|
||||
| `phone` | string | 否 | 最大 50 |
|
||||
|
||||
**业务逻辑:**
|
||||
|
||||
1. `account`、`email` 去空格 / 小写处理
|
||||
2. 查询 `sys_users`,若 `account` 或 `email` 已存在 → `409 Conflict`
|
||||
3. 创建用户并 `flush`
|
||||
4. 调用 `_issue_token()` 写入 `user_sessions` 并返回 Token
|
||||
|
||||
**成功响应:** `201 Created`,Body 为 `TokenResponse`
|
||||
|
||||
**curl 示例:**
|
||||
|
||||
```bash
|
||||
curl -X POST http://127.0.0.1:8000/auth/register \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"account": "demo_user",
|
||||
"password": "demo123456",
|
||||
"name": "演示用户",
|
||||
"email": "demo@example.com",
|
||||
"phone": "13800138000"
|
||||
}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 8.2 登录 `POST /auth/login`
|
||||
|
||||
**请求体 `LoginRequest`:**
|
||||
|
||||
| 字段 | 类型 | 必填 | 校验 |
|
||||
|------|------|------|------|
|
||||
| `account` | string | 是 | 长度 1–100 |
|
||||
| `password` | string | 是 | 长度 1–128 |
|
||||
|
||||
**业务逻辑:**
|
||||
|
||||
1. 按 `account` 查用户
|
||||
2. `verify_password` 失败 → `401 Unauthorized`
|
||||
3. 成功则 `_issue_token()` 并返回
|
||||
|
||||
**成功响应:** `200 OK`,Body 为 `TokenResponse`
|
||||
|
||||
**curl 示例:**
|
||||
|
||||
```bash
|
||||
curl -X POST http://127.0.0.1:8000/auth/login \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"account": "demo_user",
|
||||
"password": "demo123456"
|
||||
}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 8.3 统一成功响应 `TokenResponse`
|
||||
|
||||
```json
|
||||
{
|
||||
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9....",
|
||||
"token_type": "bearer",
|
||||
"expires_at": "2026-05-27T10:00:00+00:00",
|
||||
"user": {
|
||||
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"account": "demo_user",
|
||||
"name": "演示用户",
|
||||
"email": "demo@example.com",
|
||||
"phone": "13800138000"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| `access_token` | Bearer Token 字符串 |
|
||||
| `token_type` | 固定 `"bearer"` |
|
||||
| `expires_at` | ISO8601 UTC 过期时间 |
|
||||
| `user` | 当前用户公开信息(不含密码) |
|
||||
|
||||
---
|
||||
|
||||
### 8.4 错误响应格式
|
||||
|
||||
认证相关错误通过 `ApiError` 抛出,由 `app/main.py` 统一处理:
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "错误摘要",
|
||||
"detail": "错误摘要或详细信息"
|
||||
}
|
||||
```
|
||||
|
||||
| HTTP 状态码 | 场景 | message 示例 |
|
||||
|-------------|------|----------------|
|
||||
| `401` | 登录失败 | `Invalid account or password` |
|
||||
| `401` | 缺少 Token | `Missing bearer token` |
|
||||
| `401` | Token 无效 | `Invalid token signature` / `Session invalid or expired` |
|
||||
| `403` | Token 过期 | `Token expired` |
|
||||
| `409` | 注册冲突 | `Account or email already exists` |
|
||||
| `422` | 参数校验失败 | `Validation failed`(detail 为字段错误列表) |
|
||||
|
||||
---
|
||||
|
||||
## 9. 受保护接口:`get_current_user`
|
||||
|
||||
定义位置:`app/api/auth.py`
|
||||
|
||||
```python
|
||||
async def get_current_user(
|
||||
credentials: HTTPAuthorizationCredentials | None = Depends(bearer_scheme),
|
||||
session: AsyncSession = Depends(get_db_session),
|
||||
) -> User:
|
||||
...
|
||||
```
|
||||
|
||||
### 9.1 客户端如何携带 Token
|
||||
|
||||
```http
|
||||
Authorization: Bearer <access_token>
|
||||
```
|
||||
|
||||
### 9.2 校验步骤
|
||||
|
||||
1. 检查 `Authorization` 头是否存在且 scheme 为 `bearer`
|
||||
2. `_decode_access_token()`:验证格式、HMAC 签名、`exp` 未过期
|
||||
3. 从 payload 读取 `sub`(user_id)、`jti`
|
||||
4. 数据库查询:
|
||||
|
||||
```sql
|
||||
SELECT sys_users.*
|
||||
FROM sys_users
|
||||
JOIN user_sessions ON user_sessions.user_id = sys_users.id
|
||||
WHERE sys_users.id = :user_id
|
||||
AND user_sessions.jti = :jti
|
||||
AND user_sessions.token_hash = sha256(:token)
|
||||
AND user_sessions.revoked_at IS NULL
|
||||
AND user_sessions.expires_at > now()
|
||||
```
|
||||
|
||||
5. 查不到用户 → `401 Session invalid or expired`
|
||||
|
||||
### 9.3 哪些接口使用了 `get_current_user`
|
||||
|
||||
#### `/auth` 模块
|
||||
|
||||
无(register/login 本身不需要 Token)
|
||||
|
||||
#### `/api/dictionaries` 模块(`app/api/dictionaries.py`)
|
||||
|
||||
| 接口 | 是否需要登录 |
|
||||
|------|-------------|
|
||||
| `GET /api/dictionaries/crops` | 否 |
|
||||
| `POST /api/dictionaries/crops` | **是** |
|
||||
| `GET /api/dictionaries/persons` | 否 |
|
||||
| `POST /api/dictionaries/persons` | 否 |
|
||||
| `POST /api/dictionaries/countries` | **是** |
|
||||
| `PUT /api/dictionaries/countries/{code}` | **是** |
|
||||
|
||||
#### `/brapi/v2` 模块(`app/api/router.py`)
|
||||
|
||||
- 多数 **GET** 接口:接收 `Authorization` 头但**不强制校验**(BrAPI 兼容占位)
|
||||
- 多数 **POST / PUT / DELETE** 写操作:通过 `Depends(get_current_user)` **强制登录**
|
||||
|
||||
写操作成功后会将 `current_user.id` 写入业务表的 `auth_user_id` 字段,用于记录数据创建者。
|
||||
|
||||
> 上传接口 `POST /brapi/v2/upload/image` 当前**未**接入 `get_current_user`,前端可选择性携带 Token。
|
||||
|
||||
---
|
||||
|
||||
## 10. `_issue_token` 内部流程
|
||||
|
||||
```python
|
||||
async def _issue_token(session: AsyncSession, user: User) -> TokenResponse:
|
||||
jti = uuid4().hex
|
||||
token, expires_at = create_access_token(
|
||||
subject=str(user.id),
|
||||
account=user.account,
|
||||
jti=jti,
|
||||
)
|
||||
session.add(UserSession(
|
||||
user_id=user.id,
|
||||
jti=jti,
|
||||
token_hash=hash_token(token),
|
||||
expires_at=expires_at,
|
||||
))
|
||||
await session.commit()
|
||||
return TokenResponse(...)
|
||||
```
|
||||
|
||||
每次登录/注册都会**新增一条** `user_sessions` 记录;旧会话不会自动吊销(除非手动清理数据库或未来实现 logout)。
|
||||
|
||||
---
|
||||
|
||||
## 11. 前端对接(简要)
|
||||
|
||||
| 文件 | 作用 |
|
||||
|------|------|
|
||||
| `frontend/src/services/authService.ts` | 封装 `loginWithPassword`、`registerAccount` |
|
||||
| `frontend/src/lib/api/sdk.gen.ts` | OpenAPI 生成的 `/auth/login`、`/auth/register` 客户端 |
|
||||
| `frontend/src/stores/modules/auth.ts` | Zustand 持久化 Token 与用户信息 |
|
||||
| `frontend/src/utils/token.ts` | 从 `localStorage` 读取 `auth_token` |
|
||||
| `frontend/src/lib/client.ts` | 请求拦截器自动附加 `Authorization: Bearer ...` |
|
||||
|
||||
登录成功后前端会:
|
||||
|
||||
1. 调用 `setSession(session)` 写入 Zustand + `localStorage.auth_token`
|
||||
2. 跳转到 `/central-config/user/employee` 或 `redirect` 参数指定页
|
||||
|
||||
登出:仅前端 `logout()` 清除本地存储,**不会**通知后端吊销会话。
|
||||
|
||||
---
|
||||
|
||||
## 12. 本地调试
|
||||
|
||||
### 12.1 启动后端
|
||||
|
||||
```bash
|
||||
uv run uvicorn app.main:app --host 127.0.0.1 --port 8000 --reload
|
||||
```
|
||||
|
||||
### 12.2 Swagger 文档
|
||||
|
||||
```text
|
||||
http://127.0.0.1:8000/docs
|
||||
```
|
||||
|
||||
在 Swagger 中可找到 `auth` 分组下的 `register`、`login`。
|
||||
|
||||
### 12.3 用 Token 访问受保护接口
|
||||
|
||||
```bash
|
||||
TOKEN="<access_token>"
|
||||
|
||||
curl http://127.0.0.1:8000/api/dictionaries/crops \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"crop_name": "maize"}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 13. 部署注意事项
|
||||
|
||||
1. **必须修改** `JWT_SECRET_KEY`,不要使用默认值 `change-me-in-production`。
|
||||
2. 首次部署需执行数据库迁移:`uv run alembic upgrade head`(包含 `sys_users`、`user_sessions` 建表)。
|
||||
3. Docker / K8s 环境中,容器内 `API_BASE_URL=http://127.0.0.1:8000` 供 Next.js 代理;客户端浏览器仍通过同源 `/auth/*` 访问。
|
||||
4. 当前无服务端 logout:Token 在过期前始终有效(只要 `user_sessions` 记录存在)。如需强制下线,需扩展 logout 接口并设置 `revoked_at`。
|
||||
|
||||
---
|
||||
|
||||
## 14. 后续可扩展项
|
||||
|
||||
| 功能 | 建议实现 |
|
||||
|------|----------|
|
||||
| 登出 | `POST /auth/logout`,将当前 `jti` 的 `revoked_at` 设为 now |
|
||||
| 刷新 Token | `POST /auth/refresh`,验证旧 Token 后签发新 Token |
|
||||
| 当前用户 | `GET /auth/me`,返回 `UserResponse` |
|
||||
| 修改密码 | `POST /auth/change-password` |
|
||||
| 手机号登录 | 独立短信验证码表 + 新 login 分支 |
|
||||
| 上传鉴权 | `upload.py` 增加 `Depends(get_current_user)` |
|
||||
|
||||
---
|
||||
|
||||
## 15. 相关源码索引
|
||||
|
||||
| 内容 | 路径 |
|
||||
|------|------|
|
||||
| 路由与 DTO | `app/api/auth.py` |
|
||||
| 密码 / Token 工具 | `app/core/security.py` |
|
||||
| ORM 模型 | `app/models/auth.py` |
|
||||
| 配置 | `app/config/settings.py` |
|
||||
| 建表迁移 | `migrations/versions/7c91a64f2b2e_create_auth_tables.py` |
|
||||
| 应用入口 | `app/main.py` |
|
||||
| 前端封装 | `frontend/src/services/authService.ts` |
|
||||
89
docs/requirements/00-data-entry-requirements-overview.md
Normal file
89
docs/requirements/00-data-entry-requirements-overview.md
Normal file
@@ -0,0 +1,89 @@
|
||||
# 数据录入需求总览
|
||||
|
||||
本文档基于 `docs/architecture` 下 5 份架构文档拆解,目标是把 BrAPI Test Server 的数据录入需求按上游到下游梳理清楚:
|
||||
|
||||
```text
|
||||
Core 基础数据 -> Germplasm/Seed 种质种子 -> Phenotyping 表型 -> Genotyping 基因型
|
||||
```
|
||||
|
||||
## 文档索引
|
||||
|
||||
| 顺序 | 文档 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| 1 | `01-core-data-entry-requirements.md` | crop、person、program、location、trial、season、study、list |
|
||||
| 2 | `02-germplasm-seed-entry-requirements.md` | breeding_method、germplasm、attribute、cross、seed_lot、pedigree |
|
||||
| 3 | `03-phenotyping-entry-requirements.md` | ontology、trait、method、scale、observation_variable、observation_unit、event、image、observation |
|
||||
| 4 | `04-genotyping-entry-requirements.md` | plate、sample、reference、variantset、variant、callset、allele_call、genome_map |
|
||||
|
||||
## 总体录入链路
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
CROP["crop 作物"] --> PROGRAM["program 项目"]
|
||||
PERSON["person 人员"] --> PROGRAM
|
||||
PROGRAM --> TRIAL["trial 试验批次"]
|
||||
PROGRAM --> LOCATION["location 地点"]
|
||||
TRIAL --> STUDY["study 研究/试验单元"]
|
||||
LOCATION --> STUDY
|
||||
|
||||
CROP --> BM["breeding_method 育种方法"]
|
||||
BM --> G["germplasm 种质"]
|
||||
G --> GAV["germplasm_attribute_value 种质属性值"]
|
||||
PROGRAM --> CP["crossing_project 杂交项目"]
|
||||
CP --> CROSS["cross_entity Cross/PlannedCross"]
|
||||
G --> CROSSP["cross_parent 亲本"]
|
||||
CROSS --> CROSSP
|
||||
G --> MIX["seed_lot_content_mixture 批次组成"]
|
||||
CROSS --> MIX
|
||||
MIX --> SL["seed_lot 种子批次"]
|
||||
|
||||
STUDY --> OU["observation_unit 观测单元"]
|
||||
G --> OU
|
||||
SL --> OU
|
||||
CROSS --> OU
|
||||
OU --> OBS["observation 观测值"]
|
||||
|
||||
OU --> SAMPLE["sample 样本"]
|
||||
STUDY --> SAMPLE
|
||||
SAMPLE --> CALLSET["callset 调用集合"]
|
||||
VARIANT["variant 变异位点"] --> CALL["allele_call 基因型结果"]
|
||||
CALLSET --> CALL
|
||||
```
|
||||
|
||||
## 全局界面原则
|
||||
|
||||
| 页面类型 | 用途 | 建议界面 |
|
||||
| --- | --- | --- |
|
||||
| 列表页 | 查询、筛选、分页、批量操作 | 顶部筛选区 + 表格 + 新增按钮 + 行内查看/编辑 |
|
||||
| 新增/编辑页 | 单表主数据录入 | 分组表单,必填字段靠前,外键使用搜索选择器 |
|
||||
| 详情页 | 查看主表及下游数据 | 顶部摘要 + Tab:基本信息、关联数据、扩展信息、外部引用 |
|
||||
| 关系选择器 | 选择上游依赖 | 支持按 ID、名称、作物、项目、study 搜索 |
|
||||
| 批量导入 | 初始化和大批量数据 | 上传 CSV/Excel + 字段映射 + 预校验 + 导入结果 |
|
||||
|
||||
## 全局字段规则
|
||||
|
||||
| 字段类型 | 录入规则 |
|
||||
| --- | --- |
|
||||
| `id` / `DbId` | 系统生成或用户提供;必须唯一;编辑时不可随意修改 |
|
||||
| 名称字段 | 必填或强建议必填;用于下拉选择展示 |
|
||||
| 外键字段 | 页面展示名称,提交保存实际 ID |
|
||||
| 枚举字段 | 使用下拉框或单选控件 |
|
||||
| 日期时间 | 使用日期/时间选择器,保存前统一格式 |
|
||||
| `additional_info` | 作为高级配置,用键值编辑器录入 |
|
||||
| `external_references` | 作为外部系统引用,用可增删表格录入 |
|
||||
|
||||
## 总体录入顺序
|
||||
|
||||
1. 录入 Core:`crop`、`person`、`program`、`location`、`trial`、`season`、`study`、`list`。
|
||||
2. 录入 Germplasm/Seed:`breeding_method`、`germplasm_attribute_definition`、`germplasm`、`germplasm_attribute_value`、`crossing_project`、`cross_entity`、`cross_parent`、`pedigree_node`、`pedigree_edge`、`seed_lot`、`seed_lot_content_mixture`、`seed_lot_transaction`。
|
||||
3. 录入 Phenotyping:`ontology`、`trait`、`method`、`scale`、`observation_variable`、`observation_unit`、`event`、`image`、`observation`。
|
||||
4. 录入 Genotyping:`plate`、`sample`、`reference_set`、`reference`、`reference_bases`、`variantset`、`variant`、`callset`、`allele_call`、`genome_map`、`linkageGroup`、`marker_position`。
|
||||
|
||||
## 通用验收标准
|
||||
|
||||
1. 每张表都有列表、新增、编辑、详情能力。
|
||||
2. 每个外键字段都能通过名称搜索选择,而不是要求用户手填数据库 ID。
|
||||
3. 下游表新增时必须校验上游数据是否存在。
|
||||
4. 删除或禁用上游数据时,需要提示被哪些下游表引用。
|
||||
5. 支持按模块导入 CSV/Excel,并在导入前给出错误行和错误原因。
|
||||
6. 详情页能看到关键下游关联,例如 `study` 下的 observation unit、sample、variantset。
|
||||
755
docs/requirements/01-core-data-entry-requirements.md
Normal file
755
docs/requirements/01-core-data-entry-requirements.md
Normal file
@@ -0,0 +1,755 @@
|
||||
# Core 模块业务需求文档
|
||||
|
||||
## 1. 模块定位
|
||||
|
||||
Core 是项目、试验、地点、人员、季节的基础上下文模块,是所有业务数据的总上游。它不是普通字典维护,而是为后续 Germplasm/Seed、Phenotyping、Genotyping 提供“这批数据属于哪个项目、哪次试验、哪个地点、哪个年度、谁负责”的业务台账。
|
||||
|
||||
推荐先完成 Core 录入,再录入 Germplasm、Phenotyping、Genotyping。
|
||||
|
||||
## 2. 真实业务理解
|
||||
|
||||
Core 不只是“基础字典”,它在真实育种业务里是项目台账:回答“谁在什么项目、什么地点、什么年度、做哪一次试验”。后续所有材料、田间布置、表型采集、样本检测、基因型结果,都需要回到 Core 上下文里解释。
|
||||
|
||||
最常见的业务链路是:
|
||||
|
||||
```text
|
||||
crop 作物
|
||||
-> program 长期育种项目
|
||||
-> trial 某一批试验/区域试验
|
||||
-> study 一次真正落地执行的试验
|
||||
```
|
||||
|
||||
例如:
|
||||
|
||||
```text
|
||||
crop: 水稻
|
||||
program: 水稻抗倒伏育种项目
|
||||
trial: 2026 湖北区域试验
|
||||
location: 荆门试验站
|
||||
season: 2026 春季
|
||||
study: 2026 荆门水稻抗倒伏田间试验
|
||||
```
|
||||
|
||||
这里最关键的是 `study`。`program` 更像长期项目,`trial` 是试验批次,`study` 才是具体执行单元。后续 `observation_unit`、`event`、`observation`、`plate`、`sample`、`variantset` 都会直接或间接挂到 `study` 上。
|
||||
|
||||
## 3. 用户角色和使用场景
|
||||
|
||||
| 角色 | 主要职责 | 典型入口 | 权限重点 |
|
||||
| --- | --- | --- | --- |
|
||||
| 项目管理员 | 维护作物、项目、人员、地点等基础台账 | crop、person、program、location 列表页 | 可新增/编辑基础数据,可停用未被引用的数据 |
|
||||
| 试验负责人 | 创建 trial 和 study,维护联系人、季节、试验设计 | trial 列表、study 列表、Study 工作台 | 可创建试验上下文,可配置 study 联系人、season、变量清单 |
|
||||
| 数据录入员 | 进入 Study 工作台继续录观测单元、表型、样本 | Study 工作台 | 主要使用下游入口,一般不能删除 Core 主数据 |
|
||||
| 数据管理员 | 处理导入、导出、停用、删除校验、数据修复 | 导入导出页、数据质量页 | 可批量导入、导出、停用,有删除前引用检查权限 |
|
||||
|
||||
### 3.1 项目管理员场景
|
||||
|
||||
项目管理员先建立 `crop`、`person`、`program`、`location`。例如创建“水稻抗倒伏育种项目”,选择 crop=水稻,负责人=张三。录完后,试验负责人才能在该项目下创建 trial 和 study。
|
||||
|
||||
### 3.2 试验负责人场景
|
||||
|
||||
试验负责人创建“2026 湖北区域试验”和“2026 荆门水稻抗倒伏田间试验”。创建 study 时选择 program、trial、location、season,保存后进入 Study 工作台。
|
||||
|
||||
### 3.3 数据录入员场景
|
||||
|
||||
数据录入员通常不从 crop/program 开始,而是从 Study 工作台进入,继续录 observation unit、observation、sample 等下游数据。
|
||||
|
||||
### 3.4 数据管理员场景
|
||||
|
||||
数据管理员负责批量导入 Core 数据、处理重复数据、做删除/停用前检查。例如某 trial 已有关联 study 时,不允许物理删除,只能停用。
|
||||
|
||||
## 用户为什么要录 Core
|
||||
|
||||
| 用户问题 | 由哪些表回答 | 录完能做什么 |
|
||||
| --- | --- | --- |
|
||||
| 我们做什么作物? | `crop` | 限定项目、材料、变量、图谱的作物范围 |
|
||||
| 谁负责项目和试验? | `person`、`program.lead_person_id`、`trial_contact`、`study_contact` | 项目责任追踪、联系人展示、权限和通知 |
|
||||
| 这是哪个长期育种项目? | `program` | 聚合多个 trial/study、材料、样本、结果 |
|
||||
| 这是哪一批试验? | `trial` | 管理区域试验、年度试验、批次试验 |
|
||||
| 试验在哪里、哪一季做? | `location`、`season`、`study_season` | 支持多地点、多季节筛选和统计 |
|
||||
| 哪一次试验真正落地执行? | `study` | 进入 Study 工作台,继续录观测单元、表型、样本、基因型 |
|
||||
|
||||
## 6. 页面需求
|
||||
|
||||
Core 的核心页面不应该只是表单 CRUD,而应该围绕 `study` 做“Study 工作台”。
|
||||
|
||||
### 6.1 Study 工作台
|
||||
|
||||
```text
|
||||
Study 工作台
|
||||
├─ 基本信息:名称、编号、类型、起止日期、负责人
|
||||
├─ 上下文:crop、program、trial、location、season
|
||||
├─ 试验设计:experimental design、observation level、growth facility
|
||||
├─ 联系人/附件:study_contact、data_link、external_references
|
||||
├─ 观测单元:跳转 observation_unit
|
||||
├─ 表型数据:跳转 observation / matrix
|
||||
├─ 样本与样本板:跳转 sample / plate
|
||||
├─ 基因型数据:跳转 variantset / callset / allele_call
|
||||
└─ 导入导出:按 study 导入观测单元、表型、样本、基因型
|
||||
```
|
||||
|
||||
### 6.2 Crop 列表页
|
||||
|
||||
| 项目 | 需求 |
|
||||
| --- | --- |
|
||||
| 功能 | 查询 crop;新增、编辑、查看详情、停用 |
|
||||
| 筛选 | 关键词搜索,按 crop_name 模糊匹配 |
|
||||
| 表格字段 | `crop_name`、下游 program 数、下游 study 数、状态 |
|
||||
| 操作 | 查看、编辑、停用 |
|
||||
| 验收标准 | crop 已被 program/germplasm/study 引用时,不允许物理删除 |
|
||||
|
||||
### 6.3 Person 列表页
|
||||
|
||||
| 项目 | 需求 |
|
||||
| --- | --- |
|
||||
| 功能 | 查询人员;新增、编辑、查看详情、停用 |
|
||||
| 筛选 | 姓名、邮箱、机构 |
|
||||
| 表格字段 | 姓名、邮箱、电话、机构、负责项目数、参与 study 数 |
|
||||
| 操作 | 查看、编辑、停用 |
|
||||
| 验收标准 | 邮箱格式正确;作为负责人或联系人被引用时,不允许物理删除 |
|
||||
|
||||
### 6.4 Program 列表页
|
||||
|
||||
| 项目 | 需求 |
|
||||
| --- | --- |
|
||||
| 功能 | 查询 program;新增、编辑、查看详情、停用 |
|
||||
| 筛选 | crop、负责人、program_type、关键词 |
|
||||
| 表格字段 | `name`、`abbreviation`、crop、负责人、trial 数、study 数 |
|
||||
| 操作 | 查看、编辑、停用、查看 trial、查看 study |
|
||||
| 验收标准 | 创建 program 时必须选择 crop;负责人来自 person 选择器 |
|
||||
|
||||
### 6.5 Trial 列表页
|
||||
|
||||
| 项目 | 需求 |
|
||||
| --- | --- |
|
||||
| 功能 | 查询 trial;新增、编辑、查看详情、停用 |
|
||||
| 筛选 | crop、program、active、起止日期、关键词 |
|
||||
| 表格字段 | `trial_name`、program、crop、`start_date`、`end_date`、active |
|
||||
| 操作 | 查看、编辑、停用、查看 study |
|
||||
| 验收标准 | 选择 program 后自动带出 crop;已被 study 引用时不允许物理删除 |
|
||||
|
||||
### 6.6 Location 列表页
|
||||
|
||||
| 项目 | 需求 |
|
||||
| --- | --- |
|
||||
| 功能 | 查询 location;新增、编辑、查看详情、停用 |
|
||||
| 筛选 | crop、program、location_type、country、关键词 |
|
||||
| 表格字段 | `location_name`、location_type、country、program、crop、父级地点 |
|
||||
| 操作 | 查看、编辑、停用、地图查看 |
|
||||
| 验收标准 | 父级地点不能选择自己;坐标字段格式合法 |
|
||||
|
||||
### 6.7 Season 列表页
|
||||
|
||||
| 项目 | 需求 |
|
||||
| --- | --- |
|
||||
| 功能 | 查询 season;新增、编辑、查看详情、停用 |
|
||||
| 筛选 | year、season |
|
||||
| 表格字段 | `year`、`season`、关联 study 数 |
|
||||
| 操作 | 查看、编辑、停用 |
|
||||
| 验收标准 | 同一年份内 season 名称不建议重复 |
|
||||
|
||||
### 6.8 Study 列表页
|
||||
|
||||
| 项目 | 需求 |
|
||||
| --- | --- |
|
||||
| 功能 | 查询 study;新增、编辑、查看详情、停用;点击 study 名称进入 Study 工作台 |
|
||||
| 筛选 | crop、program、trial、location、season、active、study_type、关键词 |
|
||||
| 表格字段 | `study_name`、`study_code`、program、trial、location、`start_date`、`end_date`、active |
|
||||
| 操作 | 查看、编辑、停用、进入工作台 |
|
||||
| 验收标准 | 选择 program 后只展示该 program 下的 trial;选择 trial 后自动带出 crop;已有 observation_unit/sample/observation 时不能物理删除 |
|
||||
|
||||
### 6.9 Study 新增/编辑页
|
||||
|
||||
| 区域 | 字段/功能 | 要求 |
|
||||
| --- | --- | --- |
|
||||
| 基本信息 | study_name、study_code、study_type、active、start_date、end_date | study_name 必填;结束日期不早于开始日期 |
|
||||
| 上下文 | crop、program、trial、location、season | program、trial、crop、location 必选;program -> trial 联动 |
|
||||
| 说明信息 | study_description、cultural_practices、observation_units_description、license、documentationurl | URL 格式校验 |
|
||||
| 联系人 | study_contact | 多选 person,可增删 |
|
||||
| 季节 | study_season | 可多选 season |
|
||||
| 提交后 | 保存成功进入 Study 工作台 | 新建成功后自动跳转 |
|
||||
|
||||
### 6.10 List 详情页
|
||||
|
||||
| 项目 | 需求 |
|
||||
| --- | --- |
|
||||
| 功能 | 维护 list 和 list_item |
|
||||
| 页面形态 | 顶部 list 基本信息,下方 list_item 表格 |
|
||||
| 操作 | 新增 item、批量导入 item、删除 item、排序 |
|
||||
| 验收标准 | 同一 list 内 item 不重复 |
|
||||
|
||||
## 哪些表是业务动作痕迹
|
||||
|
||||
| 动作 | 痕迹表 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| 给 trial 添加联系人 | `trial_contact` | trial 与 person 的关系,不是独立主数据 |
|
||||
| 给 study 添加联系人 | `study_contact` | study 与 person 的关系 |
|
||||
| 给 study 绑定季节 | `study_season` | 一个 study 可关联多个 season |
|
||||
| 给 study 绑定观测变量 | `study_variable` | 表示该 study 要采集哪些指标 |
|
||||
| 给 trial 记录出版物 | `trial_publication` | trial 的文献/报告引用 |
|
||||
| 给 list 添加成员 | `list_item` | list 的明细项,是分组动作留下的记录 |
|
||||
|
||||
## 4. 核心业务流程
|
||||
|
||||
### 4.1 新建 Core 试验上下文流程
|
||||
|
||||
1. 用户创建 `crop`。
|
||||
2. 用户创建 `person`。
|
||||
3. 用户创建 `program`,并选择 `crop` 和负责人 `lead_person_id`。
|
||||
4. 用户创建 `location`,可选绑定 `program` 和 `crop`。
|
||||
5. 用户创建 `trial`,选择 `program` 后自动带出 `crop`。
|
||||
6. 用户创建 `season`。
|
||||
7. 用户创建 `study`,按 `program -> trial -> location -> season` 的顺序选择上下文。
|
||||
8. `study` 创建成功后,系统自动进入 Study 工作台。
|
||||
|
||||
### 4.2 业务流程图
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["创建 crop"] --> B["创建 person"]
|
||||
B --> C["创建 program<br/>选择 crop / lead person"]
|
||||
C --> D["创建 location<br/>可选 program / crop"]
|
||||
C --> E["创建 trial<br/>选择 program 自动带出 crop"]
|
||||
E --> F["创建 season"]
|
||||
D --> G["创建 study<br/>选择 program -> trial -> location -> season"]
|
||||
F --> G
|
||||
G --> H["进入 Study 工作台"]
|
||||
```
|
||||
|
||||
## 5. 核心对象说明
|
||||
|
||||
### 5.1 crop 作物
|
||||
|
||||
#### 数据库录入
|
||||
|
||||
| 项目 | 要求 |
|
||||
| --- | --- |
|
||||
| 表 | `crop` |
|
||||
| 前置依赖 | 无 |
|
||||
| 主要字段 | 作物 ID、作物名称、公共作物名、描述 |
|
||||
| 下游引用 | `program`、`location`、`trial`、`study`、`germplasm`、`observation_variable`、`genome_map` |
|
||||
|
||||
#### 界面形态
|
||||
|
||||
列表页展示作物名称、描述、更新时间、下游引用数量。新增页是轻量表单,重点是作物名称唯一校验。详情页展示该作物下的项目、试验、研究、种质、图谱入口。
|
||||
|
||||
#### 校验规则
|
||||
|
||||
1. 作物名称必填且唯一。
|
||||
2. 已被下游引用的作物不能直接删除,只允许停用或提示引用关系。
|
||||
|
||||
### 5.2 person 人员
|
||||
|
||||
#### 数据库录入
|
||||
|
||||
| 项目 | 要求 |
|
||||
| --- | --- |
|
||||
| 表 | `person` |
|
||||
| 前置依赖 | 无 |
|
||||
| 主要字段 | 姓名、邮箱、机构、联系方式、角色 |
|
||||
| 下游引用 | `program.lead_person_id`、`trial_contact`、`study_contact`、`list.list_owner_person_id` |
|
||||
|
||||
#### 界面形态
|
||||
|
||||
列表页支持姓名、邮箱、机构筛选。新增/编辑页用联系人表单。详情页展示负责的 program、参与的 trial/study、拥有的 list。
|
||||
|
||||
#### 校验规则
|
||||
|
||||
1. 姓名必填。
|
||||
2. 邮箱格式正确。
|
||||
3. 同一邮箱不建议重复录入。
|
||||
|
||||
### 5.3 program 项目
|
||||
|
||||
#### 数据库录入
|
||||
|
||||
| 项目 | 要求 |
|
||||
| --- | --- |
|
||||
| 表 | `program` |
|
||||
| 前置依赖 | `crop`,可选 `person` |
|
||||
| 主要字段 | 项目名称、作物、负责人、缩写、目标、描述 |
|
||||
| 下游引用 | `trial`、`study`、`location`、`crossing_project`、`seed_lot`、`plate`、`sample` |
|
||||
|
||||
#### 界面形态
|
||||
|
||||
新增页分为“基本信息”和“负责人/作物”两组。作物和负责人使用搜索选择器。详情页 Tab 展示 trial、study、location、seed lot、sample。
|
||||
|
||||
#### 校验规则
|
||||
|
||||
1. 项目名称必填。
|
||||
2. 作物必选。
|
||||
3. 选择负责人时必须存在于 `person`。
|
||||
|
||||
### 5.4 location 地点
|
||||
|
||||
#### 数据库录入
|
||||
|
||||
| 项目 | 要求 |
|
||||
| --- | --- |
|
||||
| 表 | `location` |
|
||||
| 前置依赖 | 可选 `crop`、`program`、父级 `location`、坐标 |
|
||||
| 主要字段 | 地点名称、地点类型、国家/地区、经纬度、父级地点、所属项目 |
|
||||
| 下游引用 | `study`、`seed_lot` |
|
||||
|
||||
#### 界面形态
|
||||
|
||||
列表页支持地图/表格两种视图。新增页包含基本信息、行政区、坐标、父级地点。详情页展示该地点下的 study 和 seed lot。
|
||||
|
||||
#### 校验规则
|
||||
|
||||
1. 地点名称必填。
|
||||
2. 经纬度格式合法。
|
||||
3. 父级地点不能选择自己。
|
||||
|
||||
### 5.5 trial 试验批次
|
||||
|
||||
#### 数据库录入
|
||||
|
||||
| 项目 | 要求 |
|
||||
| --- | --- |
|
||||
| 表 | `trial` |
|
||||
| 前置依赖 | `program`、`crop` |
|
||||
| 主要字段 | 试验名称、项目、作物、开始/结束日期、联系人、出版物 |
|
||||
| 下游引用 | `study`、`observation_unit`、`observation`、`plate`、`sample` |
|
||||
|
||||
#### 界面形态
|
||||
|
||||
新增页包含基本信息、项目作物、联系人、出版物四个区域。联系人用可增删表格选择 `person`。详情页展示 study 列表和 phenotyping/genotyping 入口。
|
||||
|
||||
#### 校验规则
|
||||
|
||||
1. 试验名称必填。
|
||||
2. program 与 crop 必须匹配。
|
||||
3. 结束日期不能早于开始日期。
|
||||
|
||||
### 5.6 season 季节
|
||||
|
||||
#### 数据库录入
|
||||
|
||||
| 项目 | 要求 |
|
||||
| --- | --- |
|
||||
| 表 | `season` |
|
||||
| 前置依赖 | 无 |
|
||||
| 主要字段 | 季节名称、年份、开始日期、结束日期 |
|
||||
| 下游引用 | `study_season`、部分 `observation` |
|
||||
|
||||
#### 界面形态
|
||||
|
||||
列表页按年份和季节筛选。新增页是简单表单。详情页展示关联 study。
|
||||
|
||||
#### 校验规则
|
||||
|
||||
1. 名称和年份必填。
|
||||
2. 同一年份内季节名称不建议重复。
|
||||
|
||||
### 5.7 study 研究/试验实施单元
|
||||
|
||||
#### 数据库录入
|
||||
|
||||
| 项目 | 要求 |
|
||||
| --- | --- |
|
||||
| 表 | `study` |
|
||||
| 前置依赖 | `crop`、`program`、`trial`、`location`,可选 `season` |
|
||||
| 主要字段 | study 名称、study 类型、项目、试验、地点、季节、开始/结束日期、实验设计 |
|
||||
| 下游引用 | `observation_unit`、`event`、`observation`、`plate`、`sample`、`variantset` |
|
||||
|
||||
#### 界面形态
|
||||
|
||||
新增页是分组表单:基本信息、上下文、地点季节、实验设计、联系人。上下文字段按顺序联动:选择 program 后过滤 trial 和 crop,选择 trial 后带出 crop。详情页作为工作台,展示 observation unit、observation、sample、variantset。
|
||||
|
||||
#### 校验规则
|
||||
|
||||
1. study 名称必填。
|
||||
2. program、trial、crop、location 必选。
|
||||
3. trial 必须属于所选 program。
|
||||
4. study 是下游核心引用,删除必须强提示。
|
||||
|
||||
### 5.8 list / list_item 通用列表
|
||||
|
||||
#### 数据库录入
|
||||
|
||||
| 项目 | 要求 |
|
||||
| --- | --- |
|
||||
| 表 | `list`、`list_item` |
|
||||
| 前置依赖 | 可选 `person` |
|
||||
| 主要字段 | 列表名称、列表类型、owner、项目项 ID、项目项名称 |
|
||||
| 下游引用 | 业务查询和分组 |
|
||||
|
||||
#### 界面形态
|
||||
|
||||
列表页展示 list。详情页内嵌 list item 表格,支持手动添加、批量导入、排序。新增 item 时可选择目标类型,例如 germplasm、study、sample。
|
||||
|
||||
#### 校验规则
|
||||
|
||||
1. list 名称必填。
|
||||
2. list item 在同一 list 内不应重复。
|
||||
3. 如果绑定 owner,owner 必须存在于 `person`。
|
||||
|
||||
## 7. 字段需求
|
||||
|
||||
### crop
|
||||
|
||||
| 字段 | 类型 | 控件 | 录入说明 | 校验/来源 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `id` | varchar(255) | 隐藏/只读 | 作物主键,新增时系统生成,也可导入时指定 | 必填、唯一 |
|
||||
| `auth_user_id` | varchar(255) | 隐藏 | 数据所属用户 | 登录上下文自动写入 |
|
||||
| `crop_name` | varchar(255) | 文本框 | 作物名称,用于所有下拉选择展示 | 必填、建议唯一 |
|
||||
|
||||
### person
|
||||
|
||||
| 字段 | 类型 | 控件 | 录入说明 | 校验/来源 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `id` | varchar(255) | 隐藏/只读 | 人员主键 | 必填、唯一 |
|
||||
| `auth_user_id` | varchar(255) | 隐藏 | 数据所属用户 | 登录上下文自动写入 |
|
||||
| `description` | varchar(255) | 多行文本 | 人员说明、职责补充 | 可选 |
|
||||
| `email_address` | varchar(255) | 邮箱输入框 | 邮箱地址 | 邮箱格式校验,建议唯一 |
|
||||
| `first_name` | varchar(255) | 文本框 | 名 | 与 `last_name` 至少填写一个 |
|
||||
| `institute_name` | varchar(255) | 文本框/选择器 | 所属机构 | 可选 |
|
||||
| `last_name` | varchar(255) | 文本框 | 姓 | 与 `first_name` 至少填写一个 |
|
||||
| `mailing_address` | varchar(255) | 多行文本 | 通讯地址 | 可选 |
|
||||
| `middle_name` | varchar(255) | 文本框 | 中间名 | 可选 |
|
||||
| `phone_number` | varchar(255) | 电话输入框 | 联系电话 | 可选,格式提示 |
|
||||
| `userid` | varchar(255) | 文本框 | 外部用户 ID 或登录名 | 可选,建议唯一 |
|
||||
|
||||
### program
|
||||
|
||||
| 字段 | 类型 | 控件 | 录入说明 | 校验/来源 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `id` | varchar(255) | 隐藏/只读 | 项目主键 | 必填、唯一 |
|
||||
| `auth_user_id` | varchar(255) | 隐藏 | 数据所属用户 | 登录上下文自动写入 |
|
||||
| `abbreviation` | varchar(255) | 文本框 | 项目缩写 | 可选 |
|
||||
| `documentationurl` | varchar(255) | URL 输入框 | 项目文档链接 | URL 格式校验 |
|
||||
| `funding_information` | varchar(255) | 多行文本 | 经费来源说明 | 可选 |
|
||||
| `name` | varchar(255) | 文本框 | 项目名称 | 必填 |
|
||||
| `objective` | varchar(255) | 多行文本 | 项目目标 | 可选 |
|
||||
| `program_type` | integer | 下拉框 | 项目类型枚举 | 可选,按 BrAPI 枚举 |
|
||||
| `crop_id` | varchar(255) | 作物选择器 | 所属作物 | 必选,来源 `crop.id` |
|
||||
| `lead_person_id` | varchar(255) | 人员选择器 | 项目负责人 | 可选,来源 `person.id` |
|
||||
|
||||
### location
|
||||
|
||||
| 字段 | 类型 | 控件 | 录入说明 | 校验/来源 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `id` | varchar(255) | 隐藏/只读 | 地点主键 | 必填、唯一 |
|
||||
| `auth_user_id` | varchar(255) | 隐藏 | 数据所属用户 | 登录上下文自动写入 |
|
||||
| `abbreviation` | varchar(255) | 文本框 | 地点缩写 | 可选 |
|
||||
| `coordinate_description` | varchar(255) | 多行文本 | 坐标描述 | 可选 |
|
||||
| `coordinate_uncertainty` | varchar(255) | 文本框 | 坐标不确定性 | 可选 |
|
||||
| `country_code` | varchar(255) | 国家代码选择器 | 国家代码 | 可选,建议 ISO 代码 |
|
||||
| `country_name` | varchar(255) | 文本框 | 国家名称 | 可选,可由国家代码带出 |
|
||||
| `documentationurl` | varchar(255) | URL 输入框 | 地点文档链接 | URL 格式校验 |
|
||||
| `environment_type` | varchar(255) | 下拉框/文本框 | 环境类型 | 可选 |
|
||||
| `exposure` | varchar(255) | 文本框 | 暴露条件 | 可选 |
|
||||
| `institute_address` | varchar(255) | 多行文本 | 机构地址 | 可选 |
|
||||
| `institute_name` | varchar(255) | 文本框 | 机构名称 | 可选 |
|
||||
| `location_name` | varchar(255) | 文本框 | 地点名称 | 必填 |
|
||||
| `location_type` | varchar(255) | 下拉框 | 地点类型,如 field、greenhouse、storage | 可选 |
|
||||
| `site_status` | varchar(255) | 下拉框 | 地点状态 | 可选 |
|
||||
| `slope` | varchar(255) | 文本框 | 坡度 | 可选 |
|
||||
| `topography` | varchar(255) | 文本框 | 地形 | 可选 |
|
||||
| `coordinates_id` | varchar(255) | 坐标选择器/地图取点 | 坐标对象 | 可选,来源 `geojson/coordinate` |
|
||||
| `crop_id` | varchar(255) | 作物选择器 | 关联作物 | 可选,来源 `crop.id` |
|
||||
| `parent_location_id` | varchar(255) | 地点选择器 | 父级地点 | 可选,不能选择自己 |
|
||||
| `program_id` | varchar(255) | 项目选择器 | 所属项目 | 可选,来源 `program.id` |
|
||||
|
||||
### trial
|
||||
|
||||
| 字段 | 类型 | 控件 | 录入说明 | 校验/来源 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `id` | varchar(255) | 隐藏/只读 | 试验主键 | 必填、唯一 |
|
||||
| `auth_user_id` | varchar(255) | 隐藏 | 数据所属用户 | 登录上下文自动写入 |
|
||||
| `active` | boolean | 开关 | 是否启用 | 默认启用 |
|
||||
| `documentationurl` | varchar(255) | URL 输入框 | 试验文档链接 | URL 格式校验 |
|
||||
| `end_date` | timestamp | 日期选择器 | 结束日期 | 不早于 `start_date` |
|
||||
| `start_date` | timestamp | 日期选择器 | 开始日期 | 可选 |
|
||||
| `trial_description` | varchar(255) | 多行文本 | 试验描述 | 可选 |
|
||||
| `trial_name` | varchar(255) | 文本框 | 试验名称 | 必填 |
|
||||
| `trialpui` | varchar(255) | 文本框 | 试验永久标识 | 可选,建议唯一 |
|
||||
| `crop_id` | varchar(255) | 作物选择器 | 所属作物 | 必选,来源 `crop.id` |
|
||||
| `program_id` | varchar(255) | 项目选择器 | 所属项目 | 必选,来源 `program.id` |
|
||||
|
||||
### season
|
||||
|
||||
| 字段 | 类型 | 控件 | 录入说明 | 校验/来源 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `id` | varchar(255) | 隐藏/只读 | 季节主键 | 必填、唯一 |
|
||||
| `auth_user_id` | varchar(255) | 隐藏 | 数据所属用户 | 登录上下文自动写入 |
|
||||
| `season` | varchar(255) | 文本框/下拉框 | 季节名称,如 Spring、Summer | 必填 |
|
||||
| `year` | integer | 年份选择器 | 年份 | 必填,四位年份 |
|
||||
|
||||
### study
|
||||
|
||||
| 字段 | 类型 | 控件 | 录入说明 | 校验/来源 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `id` | varchar(255) | 隐藏/只读 | study 主键 | 必填、唯一 |
|
||||
| `auth_user_id` | varchar(255) | 隐藏 | 数据所属用户 | 登录上下文自动写入 |
|
||||
| `active` | boolean | 开关 | 是否启用 | 默认启用 |
|
||||
| `cultural_practices` | varchar(255) | 多行文本 | 栽培管理说明 | 可选 |
|
||||
| `documentationurl` | varchar(255) | URL 输入框 | study 文档链接 | URL 格式校验 |
|
||||
| `end_date` | timestamp | 日期选择器 | 结束日期 | 不早于 `start_date` |
|
||||
| `license` | varchar(255) | 文本框 | 数据许可证 | 可选 |
|
||||
| `observation_units_description` | varchar(255) | 多行文本 | 观测单元说明 | 可选 |
|
||||
| `start_date` | timestamp | 日期选择器 | 开始日期 | 可选 |
|
||||
| `study_code` | varchar(255) | 文本框 | study 编码 | 可选,建议同项目内唯一 |
|
||||
| `study_description` | varchar(255) | 多行文本 | study 描述 | 可选 |
|
||||
| `study_name` | varchar(255) | 文本框 | study 名称 | 必填 |
|
||||
| `studypui` | varchar(255) | 文本框 | study 永久标识 | 可选,建议唯一 |
|
||||
| `study_type` | varchar(255) | 下拉框 | study 类型 | 可选 |
|
||||
| `crop_id` | varchar(255) | 作物选择器 | 所属作物 | 必选,来源 `crop.id` |
|
||||
| `location_id` | varchar(255) | 地点选择器 | 实施地点 | 必选,来源 `location.id` |
|
||||
| `program_id` | varchar(255) | 项目选择器 | 所属项目 | 必选,来源 `program.id` |
|
||||
| `trial_id` | varchar(255) | 试验选择器 | 所属 trial | 必选,来源 `trial.id` |
|
||||
|
||||
### list
|
||||
|
||||
| 字段 | 类型 | 控件 | 录入说明 | 校验/来源 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `id` | varchar(255) | 隐藏/只读 | 列表主键 | 必填、唯一 |
|
||||
| `auth_user_id` | varchar(255) | 隐藏 | 数据所属用户 | 登录上下文自动写入 |
|
||||
| `date_created` | timestamp | 只读日期时间 | 创建时间 | 系统自动写入 |
|
||||
| `date_modified` | timestamp | 只读日期时间 | 修改时间 | 系统自动更新 |
|
||||
| `description` | varchar(255) | 多行文本 | 列表描述 | 可选 |
|
||||
| `list_name` | varchar(255) | 文本框 | 列表名称 | 必填 |
|
||||
| `list_owner_name` | varchar(255) | 文本框 | 列表 owner 名称 | 可选,可由 owner person 带出 |
|
||||
| `list_source` | varchar(255) | 文本框 | 列表来源 | 可选 |
|
||||
| `list_type` | integer | 下拉框 | 列表类型 | 必填,按 BrAPI 枚举 |
|
||||
| `list_owner_person_id` | varchar(255) | 人员选择器 | 列表 owner | 可选,来源 `person.id` |
|
||||
|
||||
### list_item
|
||||
|
||||
| 字段 | 类型 | 控件 | 录入说明 | 校验/来源 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `id` | varchar(255) | 隐藏/只读 | 列表项主键 | 必填、唯一 |
|
||||
| `item` | varchar(255) | 文本框/对象选择器 | 列表项值,可存目标对象 ID 或文本 | 必填,同一 list 内不重复 |
|
||||
| `list_id` | varchar(255) | List 选择器 | 所属列表 | 必选,来源 `list.id` |
|
||||
|
||||
### trial_contact
|
||||
|
||||
| 字段 | 类型 | 控件 | 录入说明 | 校验/来源 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `trial_db_id` | varchar(255) | Trial 选择器 | 所属 trial | 必选,来源 `trial.id` |
|
||||
| `person_db_id` | varchar(255) | 人员选择器 | 联系人 | 必选,来源 `person.id` |
|
||||
|
||||
### trial_publication
|
||||
|
||||
| 字段 | 类型 | 控件 | 录入说明 | 校验/来源 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `id` | varchar(255) | 隐藏/只读 | 出版物记录主键 | 必填、唯一 |
|
||||
| `publicationpui` | varchar(255) | 文本框 | 出版物 PUI | 可选 |
|
||||
| `publication_reference` | varchar(255) | 文本框/URL | 出版物引用 | 可选 |
|
||||
| `trial_id` | varchar(255) | Trial 选择器 | 所属 trial | 必选,来源 `trial.id` |
|
||||
|
||||
### study_contact
|
||||
|
||||
| 字段 | 类型 | 控件 | 录入说明 | 校验/来源 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `study_db_id` | varchar(255) | Study 选择器 | 所属 study | 必选,来源 `study.id` |
|
||||
| `person_db_id` | varchar(255) | 人员选择器 | 联系人 | 必选,来源 `person.id` |
|
||||
|
||||
### study_season
|
||||
|
||||
| 字段 | 类型 | 控件 | 录入说明 | 校验/来源 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `study_db_id` | varchar(255) | Study 选择器 | 所属 study | 必选,来源 `study.id` |
|
||||
| `season_db_id` | varchar(255) | Season 选择器 | 关联季节 | 必选,来源 `season.id` |
|
||||
|
||||
## 8. 接口需求
|
||||
|
||||
接口命名可以按项目现有 BrAPI Controller 适配;本节描述前端页面需要的能力,不强制限定最终 URL 必须完全一致。
|
||||
|
||||
### 8.1 CRUD 接口
|
||||
|
||||
| 对象 | 接口 | 用途 |
|
||||
| --- | --- | --- |
|
||||
| crop | `GET /crops` | crop 分页查询 |
|
||||
| crop | `POST /crops` | 新增 crop |
|
||||
| crop | `GET /crops/{id}` | crop 详情 |
|
||||
| crop | `PUT /crops/{id}` | 编辑 crop |
|
||||
| crop | `DELETE /crops/{id}` | 删除或停用 crop,需做引用检查 |
|
||||
| person | `GET /persons` | person 分页查询 |
|
||||
| person | `POST /persons` | 新增 person |
|
||||
| person | `GET /persons/{id}` | person 详情 |
|
||||
| person | `PUT /persons/{id}` | 编辑 person |
|
||||
| person | `DELETE /persons/{id}` | 删除或停用 person,需做引用检查 |
|
||||
| program | `GET /programs` | program 分页查询 |
|
||||
| program | `POST /programs` | 新增 program |
|
||||
| program | `GET /programs/{id}` | program 详情 |
|
||||
| program | `PUT /programs/{id}` | 编辑 program |
|
||||
| program | `DELETE /programs/{id}` | 删除或停用 program,需做引用检查 |
|
||||
| location | `GET /locations` | location 分页查询 |
|
||||
| location | `POST /locations` | 新增 location |
|
||||
| location | `GET /locations/{id}` | location 详情 |
|
||||
| location | `PUT /locations/{id}` | 编辑 location |
|
||||
| trial | `GET /trials` | trial 分页查询 |
|
||||
| trial | `POST /trials` | 新增 trial |
|
||||
| trial | `GET /trials/{id}` | trial 详情 |
|
||||
| trial | `PUT /trials/{id}` | 编辑 trial |
|
||||
| trial | `DELETE /trials/{id}` | 删除或停用 trial,需检查 study 引用 |
|
||||
| season | `GET /seasons` | season 查询 |
|
||||
| season | `POST /seasons` | 新增 season |
|
||||
| season | `PUT /seasons/{id}` | 编辑 season |
|
||||
| study | `GET /studies` | study 分页查询 |
|
||||
| study | `POST /studies` | 新增 study |
|
||||
| study | `GET /studies/{id}` | study 详情 |
|
||||
| study | `PUT /studies/{id}` | 编辑 study |
|
||||
| study | `DELETE /studies/{id}` | 删除或停用 study,需检查下游引用 |
|
||||
| list | `GET /lists` | list 查询 |
|
||||
| list | `POST /lists` | 新增 list |
|
||||
| list | `PUT /lists/{id}` | 编辑 list |
|
||||
| list item | `POST /lists/{id}/items` | 给 list 添加 item |
|
||||
| list item | `DELETE /lists/{id}/items/{itemId}` | 删除 list item |
|
||||
|
||||
### 8.2 详情聚合接口
|
||||
|
||||
| 接口 | 用途 | 返回重点 |
|
||||
| --- | --- | --- |
|
||||
| `GET /programs/{id}/trials` | 查询 program 下 trial | trial 基础信息、active |
|
||||
| `GET /programs/{id}/studies` | 查询 program 下 study | study 基础信息、location、trial |
|
||||
| `GET /trials/{id}/studies` | 查询 trial 下 study | study 列表 |
|
||||
| `GET /studies/{id}/workbench` | Study 工作台聚合信息 | study 基本信息、联系人、season、observation_unit 数、observation 数、sample 数、variantset 数 |
|
||||
| `GET /studies/{id}/contacts` | study 联系人 | person 列表 |
|
||||
| `GET /studies/{id}/seasons` | study 季节 | season 列表 |
|
||||
|
||||
### 8.3 选择器接口
|
||||
|
||||
| 接口 | 用途 | 参数 |
|
||||
| --- | --- | --- |
|
||||
| `GET /selectors/crops` | crop 搜索下拉 | `keyword` |
|
||||
| `GET /selectors/persons` | person 搜索下拉 | `keyword`、`instituteName` |
|
||||
| `GET /selectors/programs` | program 搜索下拉 | `keyword`、`cropId` |
|
||||
| `GET /selectors/trials` | trial 搜索下拉 | `keyword`、`programId`、`cropId`、`active` |
|
||||
| `GET /selectors/locations` | location 搜索下拉 | `keyword`、`programId`、`cropId`、`locationType` |
|
||||
| `GET /selectors/seasons` | season 搜索下拉 | `year`、`keyword` |
|
||||
| `GET /selectors/studies` | study 搜索下拉 | `keyword`、`programId`、`trialId`、`locationId`、`active` |
|
||||
|
||||
### 8.4 导入导出接口
|
||||
|
||||
| 接口 | 用途 |
|
||||
| --- | --- |
|
||||
| `GET /imports/templates/core/{objectType}` | 下载 Core 对象导入模板 |
|
||||
| `POST /imports/core/{objectType}/preview` | 上传文件并预校验,不落库 |
|
||||
| `POST /imports/core/{objectType}/commit` | 确认导入 |
|
||||
| `GET /exports/core/{objectType}` | 导出当前筛选结果 |
|
||||
| `GET /imports/{jobId}/errors` | 获取导入错误报告 |
|
||||
|
||||
## 9. 联动规则
|
||||
|
||||
| 场景 | 联动规则 |
|
||||
| --- | --- |
|
||||
| 创建 program | 必须先选择 crop;负责人从 person 选择器选择 |
|
||||
| 创建 trial | 选择 program 后自动带出 crop;trial 的 crop 必须与 program.crop_id 一致 |
|
||||
| 创建 location | 选择 program 后可自动带出 crop,但允许地点作为公共地点不绑定 program |
|
||||
| 创建 study | 先选 program,再按 program 过滤 trial;选择 trial 后自动带出 crop |
|
||||
| 创建 study | location 可按 program/crop 过滤;season 支持多选 |
|
||||
| 编辑 study | 如果已有 observation_unit/sample/observation,变更 program/trial/crop/location 前必须二次确认并检查一致性 |
|
||||
| Study 工作台 | 入口必须携带 study_id;下游新增 observation_unit、sample、variantset 默认继承该 study 上下文 |
|
||||
| 外键显示 | 前端展示名称,提交保存 ID;表格列需要同时支持名称展示和 ID 调试查看 |
|
||||
|
||||
## 10. 删除/停用规则
|
||||
|
||||
| 对象 | 删除/停用规则 |
|
||||
| --- | --- |
|
||||
| crop | 已被 program、location、trial、study、germplasm、observation_variable、genome_map 引用时,不允许物理删除 |
|
||||
| person | 已作为 program 负责人、trial 联系人、study 联系人、list owner 时,不允许物理删除 |
|
||||
| program | 已有关联 trial、study、location、crossing_project、seed_lot、plate、sample 时,不允许物理删除 |
|
||||
| location | 已被 study 或 seed_lot 引用时,不允许物理删除 |
|
||||
| trial | 已被 study、observation_unit、observation、plate、sample 引用时,不允许物理删除 |
|
||||
| season | 已被 study_season 或 observation 引用时,不允许物理删除 |
|
||||
| study | 已被 observation_unit、event、observation、plate、sample、variantset 引用时,不允许物理删除 |
|
||||
| list | 已有 list_item 时,删除前必须提示;可先清空明细再删除 |
|
||||
|
||||
删除失败时,后端应返回被引用对象类型和数量,前端弹窗展示,例如:
|
||||
|
||||
```text
|
||||
该 study 已被以下数据引用,不能删除:
|
||||
- observation_unit: 120
|
||||
- observation: 560
|
||||
- sample: 96
|
||||
请先处理下游数据,或将 study 停用。
|
||||
```
|
||||
|
||||
## 11. 导入导出需求
|
||||
|
||||
### 11.1 导入对象
|
||||
|
||||
Core 模块至少支持以下对象导入:
|
||||
|
||||
```text
|
||||
crop
|
||||
person
|
||||
program
|
||||
location
|
||||
trial
|
||||
season
|
||||
study
|
||||
list
|
||||
list_item
|
||||
trial_contact
|
||||
study_contact
|
||||
study_season
|
||||
```
|
||||
|
||||
### 11.2 导入流程
|
||||
|
||||
1. 用户选择对象类型,例如 `study`。
|
||||
2. 下载 CSV/Excel 模板。
|
||||
3. 用户填写模板并上传。
|
||||
4. 系统做预校验:字段完整性、必填、格式、外键是否存在、联动关系是否一致。
|
||||
5. 预览页展示可导入行、错误行、警告行。
|
||||
6. 用户确认后提交导入。
|
||||
7. 导入完成后生成导入报告。
|
||||
|
||||
### 11.3 导入模板要求
|
||||
|
||||
| 要求 | 说明 |
|
||||
| --- | --- |
|
||||
| 字段名 | 使用数据库字段名作为模板列名 |
|
||||
| 外键列 | 支持填写 ID;可额外支持名称匹配,但名称重复时必须报错 |
|
||||
| 错误报告 | 返回行号、字段名、错误原因、建议修正方式 |
|
||||
| 幂等策略 | 可按 ID 更新;无 ID 时新增 |
|
||||
| 权限 | 数据录入员可导入 study 下游数据;Core 主数据导入建议仅项目管理员/数据管理员可操作 |
|
||||
|
||||
### 11.4 导出需求
|
||||
|
||||
1. 所有列表页支持导出当前筛选结果。
|
||||
2. 导出文件应包含 ID 和展示名称。
|
||||
3. study 导出应支持导出工作台摘要,包括联系人、season、下游数据数量。
|
||||
4. 导出大数据量时应走异步任务。
|
||||
|
||||
## 12. 验收标准
|
||||
|
||||
### 12.1 前端验收标准
|
||||
|
||||
1. 所有列表页支持分页、关键词搜索、基础筛选。
|
||||
2. 所有外键字段前端展示名称,提交保存 ID。
|
||||
3. 创建 program 时,必须选择 crop。
|
||||
4. 创建 trial 时,选择 program 后自动带出 crop。
|
||||
5. 创建 study 时,必须选择 program、trial、crop、location。
|
||||
6. Study 列表页支持按 crop、program、trial、location、season、active 筛选。
|
||||
7. 点击 study 名称或“进入工作台”按钮后进入 Study 工作台。
|
||||
8. Study 工作台可以看到观测单元、表型、样本、基因型入口。
|
||||
9. 删除被引用数据时,前端展示引用详情,不允许静默失败。
|
||||
10. 导入预校验结果能展示错误行、错误字段、错误原因。
|
||||
|
||||
### 12.2 后端验收标准
|
||||
|
||||
1. 创建 program 时校验 `crop_id` 存在。
|
||||
2. 创建 trial 时校验 `program_id` 存在,且 trial.crop_id 与 program.crop_id 一致。
|
||||
3. 创建 study 时校验 `program_id`、`trial_id`、`crop_id`、`location_id` 存在。
|
||||
4. 创建 study 时校验 trial 属于所选 program。
|
||||
5. 删除 crop/program/trial/study 前必须检查下游引用。
|
||||
6. `GET /studies/{id}/workbench` 能返回工作台所需聚合数量。
|
||||
7. 选择器接口支持 keyword 模糊搜索和上游过滤参数。
|
||||
8. 导入接口支持 preview 和 commit 两阶段。
|
||||
|
||||
### 12.3 测试点
|
||||
|
||||
1. 选择 program 后,trial 下拉框只展示该 program 下的数据。
|
||||
2. 选择 trial 后,系统自动带出 crop。
|
||||
3. 已被 study 引用的 trial 不能直接删除。
|
||||
4. 已被 observation_unit、sample、observation 引用的 study 不能直接删除。
|
||||
5. study 创建成功后自动进入 Study 工作台。
|
||||
6. 修改 study 的 program/trial/crop 时,如果已有下游数据,必须提示风险。
|
||||
7. 导入 study 时,如果 trial 不属于 program,应报错并指出行号。
|
||||
8. 导入 location 时,如果 parent_location_id 指向自己,应报错。
|
||||
9. 导出列表时,筛选条件必须生效。
|
||||
10. 所有接口返回的分页字段一致,便于前端表格复用。
|
||||
609
docs/requirements/02-germplasm-seed-entry-requirements.md
Normal file
609
docs/requirements/02-germplasm-seed-entry-requirements.md
Normal file
@@ -0,0 +1,609 @@
|
||||
# Germplasm / Seed 模块专业数据录入需求文档 V2
|
||||
|
||||
## 1. 文档目的
|
||||
|
||||
本文档用于指导 Germplasm / Seed 模块的前端页面、后端接口、字段校验、数据导入、测试验收设计。本文档不再只描述数据库表关系,而是从真实育种业务出发,解释每个字段的业务意义、录入方式、控件建议、校验规则和上下游影响。
|
||||
|
||||
## 2. 模块定位
|
||||
|
||||
Germplasm / Seed 模块描述育种材料的生命周期:
|
||||
|
||||
```text
|
||||
材料身份 -> 材料属性 -> 杂交计划/实际杂交 -> 亲本 -> 系谱 -> 种子批次 -> 库存流转 -> 被 study / observation_unit 使用
|
||||
```
|
||||
|
||||
核心概念如下:
|
||||
|
||||
| 概念 | 业务含义 | 主要表 |
|
||||
| ---------------------------- | --------------------------------------------------------- | ------------------------------------------------------------ |
|
||||
| Germplasm 材料身份 | 一个品种、品系、后代材料、种质资源的身份信息 | `germplasm` |
|
||||
| Germplasm Attribute 材料属性 | 材料自身稳定特征,如抗性、熟期、籽粒硬度、基因/QTL 标记等 | `germplasm_attribute_definition`、`germplasm_attribute_value` |
|
||||
| Cross 杂交动作 | 一次计划杂交或实际杂交,包含亲本、状态、项目归属 | `crossing_project`、`cross_entity`、`cross_parent` |
|
||||
| Pedigree 系谱 | 材料之间的亲子、同胞、后代关系 | `pedigree_node`、`pedigree_edge` |
|
||||
| SeedLot 种子批次 | 某个材料或杂交组合对应的一批实物种子,有数量、单位、库位 | `seed_lot`、`seed_lot_content_mixture` |
|
||||
| Transaction 库存流转 | 入库、出库、转移、分装、合并、消耗等动作流水 | `seed_lot_transaction` |
|
||||
|
||||
## 3. 推荐业务流程
|
||||
|
||||
```text
|
||||
1. 维护育种方法 breeding_method
|
||||
2. 维护材料属性定义 germplasm_attribute_definition
|
||||
3. 创建材料 germplasm
|
||||
4. 给材料补充属性值 germplasm_attribute_value
|
||||
5. 如涉及杂交,创建 crossing_project
|
||||
6. 创建计划杂交 cross_entity(planned=true)
|
||||
7. 给计划杂交录入亲本 cross_parent
|
||||
8. 实际完成杂交后,创建实际杂交 cross_entity(planned=false, planned_cross_id=计划杂交)
|
||||
9. 如产生后代材料,创建新的 germplasm,并补充 pedigree_node / pedigree_edge
|
||||
10. 如产生实物种子,创建 seed_lot
|
||||
11. 在 seed_lot_content_mixture 中描述批次组成
|
||||
12. 后续库存变化通过 seed_lot_transaction 记录
|
||||
13. seed_lot / germplasm / cross_entity 后续可作为 observation_unit 的材料来源
|
||||
```
|
||||
|
||||
## 4. 页面总体设计
|
||||
|
||||
### 4.1 Germplasm 材料详情页
|
||||
|
||||
```text
|
||||
Germplasm 详情页
|
||||
├─ 基本信息:名称、默认显示名、PUI、accession、作物、育种方法
|
||||
├─ 分类信息:genus、species、subtaxa、country_of_origin_code
|
||||
├─ 来源信息:acquisition_date、seed_source、seed_source_description、collection
|
||||
├─ 属性值:germplasm_attribute_value
|
||||
├─ 系谱:pedigree_node / pedigree_edge 树图
|
||||
├─ 作为亲本:cross_parent
|
||||
├─ 种子批次:seed_lot_content_mixture -> seed_lot
|
||||
└─ 下游使用:observation_unit、reference_set、sample 追踪
|
||||
```
|
||||
|
||||
### 4.2 SeedLot 库存详情页
|
||||
|
||||
```text
|
||||
SeedLot 详情页
|
||||
├─ 当前库存:amount、units、location、storage_location
|
||||
├─ 批次组成:germplasm / cross / mixture_percentage
|
||||
├─ 出入库操作:入库、出库、转移、分装、合并、消耗
|
||||
├─ 流转流水:seed_lot_transaction
|
||||
└─ 下游使用:哪些 study / observation_unit 使用了该批种子
|
||||
```
|
||||
|
||||
### 4.3 CrossingProject 杂交项目工作台
|
||||
|
||||
```text
|
||||
CrossingProject 工作台
|
||||
├─ 项目信息:name、program、description
|
||||
├─ 计划杂交:cross_entity(planned=true)
|
||||
├─ 实际杂交:cross_entity(planned=false)
|
||||
├─ 亲本:cross_parent
|
||||
├─ 后代材料:germplasm / pedigree_node
|
||||
└─ 产生种子批次:seed_lot
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 5. 字段级专业录入需求
|
||||
|
||||
## 5.1 breeding_method 育种方法
|
||||
|
||||
### 业务说明
|
||||
|
||||
`breeding_method` 是育种方法字典,用来说明某个 germplasm 是通过什么方式形成的,例如杂交选育、回交、自交系选育、诱变、转基因、克隆选择等。它不是一次具体杂交动作,而是材料来源方法的分类。
|
||||
|
||||
### 字段说明
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 控件建议 | 校验规则 |
|
||||
| -------------- | -------------------------------------------- | -------------------------------- | --------- | -------------------------------- |
|
||||
| `id` | 育种方法主键,系统内部唯一标识 | 新增时系统生成;导入时可允许指定 | 隐藏/只读 | 必填、唯一;编辑时不允许修改 |
|
||||
| `auth_user_id` | 数据所属用户或租户 | 登录上下文自动写入 | 隐藏 | 不允许前端手填 |
|
||||
| `abbreviation` | 方法缩写,如 MB、BC、DH | 用户录入 | 文本框 | 可选;建议同一用户下唯一 |
|
||||
| `description` | 方法解释,如“回交用于恢复目标基因” | 用户录入 | 多行文本 | 可选,限制长度 |
|
||||
| `name` | 方法名称,如 Male Backcross、Doubled Haploid | 用户录入 | 文本框 | 必填;建议唯一;作为下拉展示名称 |
|
||||
|
||||
### 页面与交互
|
||||
|
||||
- 列表页展示:方法名称、缩写、描述、使用材料数量。
|
||||
- 新增页为简单字典表单。
|
||||
- 删除前检查是否被 `germplasm.breeding_method_id` 引用;已引用时不允许物理删除,只允许停用。
|
||||
|
||||
---
|
||||
|
||||
## 5.2 germplasm 种质 / 材料身份
|
||||
|
||||
### 业务说明
|
||||
|
||||
`germplasm` 是材料身份证,描述一个品种、品系、亲本、后代材料、遗传资源或研究材料“是谁”。它不表示库存数量,库存数量由 `seed_lot` 表达。
|
||||
|
||||
### 字段说明
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 控件建议 | 校验规则 |
|
||||
| ------------------------------------- | ------------------------------------------------------------ | -------------------------------------- | ----------------- | --------------------------------------------------------- |
|
||||
| `id` | 种质主键,系统内部唯一标识 | 系统生成;导入可指定 | 隐藏/只读 | 必填、唯一;编辑不可随意修改 |
|
||||
| `auth_user_id` | 数据所属用户或租户 | 登录上下文自动写入 | 隐藏 | 不允许手填 |
|
||||
| `accession_number` | 材料在种质库/机构内的 accession 编号,如 PI 113869 | 用户录入或导入 | 文本框 | 可选;建议同一 crop / institution 下唯一 |
|
||||
| `acquisition_date` | 材料进入本系统或本机构的获取日期 | 用户录入 | 日期选择器 | 可选;不得晚于当前日期太多,导入时允许缺月/缺日需统一规则 |
|
||||
| `acquisition_source_code` | 获取来源编码,如采集、引进、交换、繁殖等 | 用户选择 | 下拉框 | 可选;值来自 BrAPI/MCPD 枚举或系统字典 |
|
||||
| `biological_status_of_accession_code` | 材料生物状态,如野生、地方品种、育种材料、改良品种、突变体等 | 用户选择 | 下拉框 | 可选;使用受控枚举,不建议自由输入 |
|
||||
| `collection` | 材料所属集合、群体、panel 或 collection | 用户录入/选择 | 文本框/选择器 | 可选;可用于分组筛选 |
|
||||
| `country_of_origin_code` | 原产国或育成/选育国家代码 | 用户选择 | 国家代码选择器 | 可选;建议使用 ISO 3166-1 三字母代码 |
|
||||
| `default_display_name` | 系统默认展示名,给下拉框、表格、详情标题使用 | 用户录入,可由 germplasm_name 自动带出 | 文本框 | 与 `germplasm_name` 至少填一个;建议必填 |
|
||||
| `documentationurl` | 材料说明文档、外部数据库页面或 DOI 链接 | 用户录入 | URL 输入框 | 可选;校验 URL 格式 |
|
||||
| `genus` | 属名,如 Oryza、Triticum | 用户录入/字典选择 | 文本框/物种选择器 | 可选;建议首字母大写 |
|
||||
| `germplasm_name` | 材料名称,可以是品种名、品系名、后代编号 | 用户录入 | 文本框 | 与 `default_display_name` 至少填一个;不强制全局唯一 |
|
||||
| `germplasmpui` | 永久唯一标识,通常是 DOI、URI 或全局唯一编码 | 用户录入/外部导入 | 文本框/URL 输入框 | 可选;若填写必须唯一;建议用于跨系统交换 |
|
||||
| `germplasm_preprocessing` | 材料用于试验前的统一处理说明,如消毒、催芽、低温处理 | 用户录入 | 文本框/多行文本 | 可选 |
|
||||
| `mls_status` | 多边系统 MLS 状态,涉及植物遗传资源交换协议 | 用户选择 | 下拉框 | 可选;普通业务可隐藏到高级信息 |
|
||||
| `seed_source` | 材料来源标识,如来源机构+accession,或亲本组合描述 | 用户录入 | 文本框 | 可选;注意它不是库存批次,不等于 seed_lot |
|
||||
| `seed_source_description` | 材料来源详细说明 | 用户录入 | 多行文本 | 可选 |
|
||||
| `species` | 种名,如 sativa、aestivum | 用户录入/物种字典 | 文本框 | 可选;建议小写 |
|
||||
| `species_authority` | 种名命名权威,如 L. | 用户录入 | 文本框 | 可选 |
|
||||
| `subtaxa` | 亚种、变种、品种群、line 等更细分类 | 用户录入 | 文本框 | 可选 |
|
||||
| `subtaxa_authority` | 亚种/变种命名权威 | 用户录入 | 文本框 | 可选 |
|
||||
| `breeding_method_id` | 该材料形成所使用的育种方法 | 从 breeding_method 选择 | 搜索选择器 | 可选;必须引用存在的 breeding_method |
|
||||
| `crop_id` | 所属作物 | 从 crop 选择 | 作物选择器 | 必填;后续 trial/study/attribute 应尽量同 crop |
|
||||
|
||||
### 录入建议
|
||||
|
||||
- 新建材料时,第一屏只放核心字段:`crop_id`、`germplasm_name`、`default_display_name`、`germplasmpui`、`accession_number`、`breeding_method_id`。
|
||||
- 分类与来源信息放在“高级信息”或“来源信息”分组。
|
||||
- `germplasmpui`、`accession_number`、`germplasm_name` 三者不要混为一谈:
|
||||
- `germplasm_name` 是人看的名字;
|
||||
- `accession_number` 是机构内编号;
|
||||
- `germplasmpui` 是跨系统长期唯一标识。
|
||||
|
||||
### 验收标准
|
||||
|
||||
1. 新增 germplasm 时,必须选择 crop。
|
||||
2. `germplasm_name` 和 `default_display_name` 至少填写一个。
|
||||
3. 下拉选择材料时展示 `default_display_name`,辅助展示 accession number / PUI。
|
||||
4. 如果 germplasm 已被 seed lot、cross parent、observation unit 引用,不允许物理删除。
|
||||
|
||||
---
|
||||
|
||||
## 5.3 germplasm_attribute_definition 属性定义
|
||||
|
||||
### 业务说明
|
||||
|
||||
属性定义描述“材料可以有哪些稳定属性”。这些属性通常不是环境依赖的田间观测值,而是材料自身特征,例如籽粒颜色、抗病基因、硬度、熟期类型、特定 QTL、分子标记结果等。
|
||||
|
||||
### 字段说明
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 控件建议 | 校验规则 |
|
||||
| ---------------------- | ------------------------------------------------------------ | ---------------------------- | ----------------- | ------------------------------------- |
|
||||
| `id` | 属性定义主键 | 系统生成 | 隐藏/只读 | 必填、唯一 |
|
||||
| `auth_user_id` | 数据所属用户或租户 | 登录上下文自动写入 | 隐藏 | 不允许手填 |
|
||||
| `default_value` | 属性默认值 | 用户录入 | 动态输入框 | 可选;按 datatype / scale 校验 |
|
||||
| `documentationurl` | 属性说明文档链接 | 用户录入 | URL 输入框 | 可选;校验 URL |
|
||||
| `growth_stage` | 属性适用生长阶段,如 flowering | 用户录入/选择 | 下拉框/文本框 | 可选 |
|
||||
| `institution` | 提交或维护该属性定义的机构 | 用户录入 | 文本框 | 可选 |
|
||||
| `language` | 定义语言,如 zh、en | 用户选择 | 下拉框 | 可选;建议 ISO 639-1 |
|
||||
| `scientist` | 提交该属性定义的科学家或负责人 | 用户录入/人员选择 | 文本框/人员选择器 | 可选 |
|
||||
| `status` | 属性状态,如 recommended、obsolete、legacy | 用户选择 | 下拉框 | 可选;推荐使用枚举 |
|
||||
| `submission_timestamp` | 属性定义提交时间 | 系统默认当前时间,可手动调整 | 日期时间选择器 | 可选;新增默认当前时间 |
|
||||
| `crop_id` | 适用作物 | 从 crop 选择 | 作物选择器 | 可选;若填写,下游材料应同 crop |
|
||||
| `method_id` | 属性测定方法 | 从 method 选择 | 方法选择器 | 可选;若填写,属性值录入按该方法解释 |
|
||||
| `ontology_id` | 所属本体 | 从 ontology 选择 | 本体选择器 | 可选 |
|
||||
| `scale_id` | 值标尺/单位/有效值范围 | 从 scale 选择 | 标尺选择器 | 可选;若填写,属性值必须按 scale 校验 |
|
||||
| `trait_id` | 关联性状 | 从 trait 选择 | 性状选择器 | 可选 |
|
||||
| `attribute_category` | 属性分类,如 Morphological、Genetic、Quality | 用户选择/录入 | 下拉框/文本框 | 可选;建议字典化 |
|
||||
| `code` | 属性代码,便于导入导出 | 用户录入 | 文本框 | 可选;建议唯一 |
|
||||
| `datatype` | 属性值数据类型,如 text、numeric、date、boolean、categorical | 用户选择 | 下拉框 | 必填 |
|
||||
| `description` | 属性解释 | 用户录入 | 多行文本 | 可选 |
|
||||
| `name` | 属性名称 | 用户录入 | 文本框 | 必填;作为属性选择器展示名称 |
|
||||
| `pui` | 属性永久标识 | 用户录入 | 文本框/URL 输入框 | 可选;建议唯一 |
|
||||
| `uri` | 属性 URI | 用户录入 | URL 输入框 | 可选;校验 URL |
|
||||
|
||||
### 录入建议
|
||||
|
||||
- 属性定义页面本质是“属性字典配置”。
|
||||
- 前端应根据 `datatype` 动态决定属性值录入控件:
|
||||
- numeric:数字输入框;
|
||||
- categorical:下拉框;
|
||||
- date:日期选择器;
|
||||
- boolean:开关;
|
||||
- text:文本框。
|
||||
- 若绑定了 `scale_id`,则优先按 scale 的单位、上下限、有效分类值校验。
|
||||
|
||||
---
|
||||
|
||||
## 5.4 germplasm_attribute_value 属性值
|
||||
|
||||
### 业务说明
|
||||
|
||||
属性值是“某个 germplasm 在某个属性上的实际取值”。它不是属性定义,也不是 observation。它适合记录材料相对稳定、不强依赖环境的特征。
|
||||
|
||||
### 字段说明
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 控件建议 | 校验规则 |
|
||||
| ----------------- | ------------------------------ | -------------------------------------- | ---------- | ---------------------------------------- |
|
||||
| `id` | 属性值主键 | 系统生成 | 隐藏/只读 | 必填、唯一 |
|
||||
| `auth_user_id` | 数据所属用户或租户 | 登录上下文自动写入 | 隐藏 | 不允许手填 |
|
||||
| `determined_date` | 属性值被测定或确认的日期 | 用户录入 | 日期选择器 | 可选;多次测定时必须填写以区分记录 |
|
||||
| `value` | 某个材料在某个属性上的实际取值 | 用户录入 | 动态控件 | 必填;按 attribute datatype / scale 校验 |
|
||||
| `attribute_id` | 属性定义 | 从 germplasm_attribute_definition 选择 | 属性选择器 | 必选;必须存在 |
|
||||
| `germplasm_id` | 所属材料 | 从 germplasm 选择 | 材料选择器 | 必选;必须存在 |
|
||||
|
||||
### 录入建议
|
||||
|
||||
- 推荐嵌入 Germplasm 详情页的“属性值”Tab。
|
||||
- 支持批量导入,模板列建议为:`germplasm_id/germplasm_name`、`attribute_code/attribute_name`、`value`、`determined_date`。
|
||||
- 同一个 germplasm + attribute 可以允许多次测定,但页面必须显示测定日期、来源和最新值标记。
|
||||
|
||||
---
|
||||
|
||||
## 5.5 crossing_project 杂交项目
|
||||
|
||||
### 业务说明
|
||||
|
||||
`crossing_project` 是某个育种项目下的一组杂交任务集合。它不是一次杂交,而是一个杂交工作台,例如“2026 抗倒伏杂交项目”。
|
||||
|
||||
### 字段说明
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 控件建议 | 校验规则 |
|
||||
| -------------- | ------------------ | ------------------ | ---------- | ----------------------------- |
|
||||
| `id` | 杂交项目主键 | 系统生成 | 隐藏/只读 | 必填、唯一 |
|
||||
| `auth_user_id` | 数据所属用户或租户 | 登录上下文自动写入 | 隐藏 | 不允许手填 |
|
||||
| `description` | 杂交项目说明 | 用户录入 | 多行文本 | 可选 |
|
||||
| `name` | 杂交项目名称 | 用户录入 | 文本框 | 必填;同一 program 下建议唯一 |
|
||||
| `program_id` | 所属育种项目 | 从 program 选择 | 项目选择器 | 必选;必须存在 |
|
||||
|
||||
### 页面与交互
|
||||
|
||||
- 详情页应展示计划杂交、实际杂交、潜在亲本、后代材料、产生的 seed lot。
|
||||
- 创建 cross 时应自动带入 crossing_project_id。
|
||||
|
||||
---
|
||||
|
||||
## 5.6 cross_entity 计划杂交 / 实际杂交
|
||||
|
||||
### 业务说明
|
||||
|
||||
`cross_entity` 统一承载计划杂交和实际杂交。通过 `planned` 字段区分计划与实际,通过 `planned_cross_id` 指向来源计划。
|
||||
|
||||
### 字段说明
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 控件建议 | 校验规则 |
|
||||
| --------------------- | ------------------------------------------- | ------------------------ | -------------- | --------------------------------------- |
|
||||
| `id` | cross 主键 | 系统生成 | 隐藏/只读 | 必填、唯一 |
|
||||
| `auth_user_id` | 数据所属用户或租户 | 登录上下文自动写入 | 隐藏 | 不允许手填 |
|
||||
| `cross_type` | 杂交类型,如 biparental、self、backcross 等 | 用户选择 | 下拉框 | 可选;值来自枚举字典 |
|
||||
| `name` | cross 名称,如 A × B、A/B、Cross-2026-001 | 用户录入或自动生成 | 文本框 | 必填;同一 crossing project 下建议唯一 |
|
||||
| `planned` | 是否为计划杂交 | 页面根据入口自动设置 | 开关/分段控件 | 必填;计划杂交为 true,实际杂交为 false |
|
||||
| `status` | 状态,如 TODO、DONE、SKIPPED、FAILED | 用户选择/系统更新 | 下拉框 | 可选;计划杂交常用 TODO/DONE/SKIPPED |
|
||||
| `crossing_project_id` | 所属杂交项目 | 从 crossing_project 选择 | 杂交项目选择器 | 必选 |
|
||||
| `planned_cross_id` | 实际杂交来源的计划杂交 | 从 cross_entity 选择 | Cross 选择器 | 可选;不能选择自己;实际杂交建议填写 |
|
||||
|
||||
### 录入建议
|
||||
|
||||
- 页面上分成“计划杂交”和“实际杂交”两个入口,但后端都保存到 `cross_entity`。
|
||||
- 创建计划杂交时:`planned=true`,`planned_cross_id=null`。
|
||||
- 完成实际杂交时:`planned=false`,`planned_cross_id=原计划杂交 id`。
|
||||
- 亲本不要直接塞在 cross 主表字段中,应通过 `cross_parent` 维护,便于支持多亲本和 observation_unit 亲本来源。
|
||||
|
||||
---
|
||||
|
||||
## 5.7 cross_parent 杂交亲本
|
||||
|
||||
### 业务说明
|
||||
|
||||
`cross_parent` 表示某个 cross 的亲本。亲本可以来自 `germplasm`,也可以来自某个 `observation_unit`,例如田间某一株实际被选作父本/母本。
|
||||
|
||||
### 字段说明
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 控件建议 | 校验规则 |
|
||||
| --------------------- | --------------------------------------------------- | ---------------------------------- | ----------------- | ------------------------------------- |
|
||||
| `id` | 亲本记录主键 | 系统生成 | 隐藏/只读 | 必填、唯一 |
|
||||
| `parent_type` | 亲本角色,如 MALE、FEMALE、SELF、POPULATION、CLONAL | 用户选择 | 下拉框 | 必填;使用枚举 |
|
||||
| `cross_id` | 所属 cross | 从 cross_entity 选择或由详情页带入 | Cross 选择器/隐藏 | 必选 |
|
||||
| `crossing_project_id` | 所属 crossing project | 由 cross 自动带出 | 只读/隐藏 | 可选;如填写必须与 cross 一致 |
|
||||
| `germplasm_id` | 亲本材料 | 从 germplasm 选择 | 材料选择器 | 与 `observation_unit_id` 至少一个必填 |
|
||||
| `observation_unit_id` | 亲本观测单元 | 从 observation_unit 选择 | 观测单元选择器 | 与 `germplasm_id` 至少一个必填 |
|
||||
|
||||
### 录入建议
|
||||
|
||||
- 在 Cross 详情页内嵌“亲本列表”。
|
||||
- 常见快捷录入:Parent1 / Parent2。
|
||||
- 对于田间选株杂交,优先记录 observation_unit_id,同时可带出 germplasm_id,保证可追溯到具体植株。
|
||||
|
||||
---
|
||||
|
||||
## 5.8 pedigree_node 系谱节点
|
||||
|
||||
### 业务说明
|
||||
|
||||
`pedigree_node` 是系谱图中的节点,通常对应一个 germplasm。它用于描述材料在系谱树中的位置,不等同于一次杂交记录。
|
||||
|
||||
### 字段说明
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 控件建议 | 校验规则 |
|
||||
| --------------------- | --------------------- | ------------------------ | -------------- | ----------------------------------------- |
|
||||
| `id` | 系谱节点主键 | 系统生成 | 隐藏/只读 | 必填、唯一 |
|
||||
| `auth_user_id` | 数据所属用户或租户 | 登录上下文自动写入 | 隐藏 | 不允许手填 |
|
||||
| `crossing_year` | 亲本最初杂交年份 | 用户录入 | 年份选择器 | 可选;四位年份 |
|
||||
| `family_code` | 家系编号 | 用户录入 | 文本框 | 可选;同一 crossing_project 下建议唯一 |
|
||||
| `pedigree_string` | 系谱字符串,如 A/B//C | 用户录入/系统生成 | 文本框 | 可选;建议支持 Purdy notation |
|
||||
| `crossing_project_id` | 产生该节点的杂交项目 | 从 crossing_project 选择 | 杂交项目选择器 | 可选 |
|
||||
| `germplasm_id` | 该系谱节点对应的材料 | 从 germplasm 选择 | 材料选择器 | 建议必填;同一 germplasm 不建议重复建节点 |
|
||||
|
||||
### 录入建议
|
||||
|
||||
- Germplasm 详情页提供“系谱”Tab。
|
||||
- 支持两种维护方式:树图拖拽维护、表格维护节点和边。
|
||||
- 如果 cross 完成后产生后代 germplasm,应自动或半自动创建 pedigree_node。
|
||||
|
||||
---
|
||||
|
||||
## 5.9 pedigree_edge 系谱边
|
||||
|
||||
### 业务说明
|
||||
|
||||
`pedigree_edge` 是系谱图中的边,描述节点之间的父子、同胞等关系。
|
||||
|
||||
### 字段说明
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 控件建议 | 校验规则 |
|
||||
| ------------------- | ------------------------------------------ | --------------------- | ---------- | ------------------------------------ |
|
||||
| `id` | 系谱边主键 | 系统生成 | 隐藏/只读 | 必填、唯一 |
|
||||
| `auth_user_id` | 数据所属用户或租户 | 登录上下文自动写入 | 隐藏 | 不允许手填 |
|
||||
| `edge_type` | 边类型,如 parent、child、sibling | 用户选择 | 下拉框 | 必填 |
|
||||
| `parent_type` | 如果是亲本关系,标识 MALE、FEMALE、SELF 等 | 用户选择 | 下拉框 | 可选;当 edge_type=parent 时建议必填 |
|
||||
| `connceted_node_id` | 被连接节点 | 从 pedigree_node 选择 | 节点选择器 | 必选 |
|
||||
| `this_node_id` | 当前节点 | 从 pedigree_node 选择 | 节点选择器 | 必选;不能等于 connected node |
|
||||
|
||||
### 录入建议
|
||||
|
||||
- 前端展示时不要暴露“this_node_id / connected_node_id”这种技术词,应该显示为“当前材料”和“关联材料”。
|
||||
- 添加父本/母本时,系统自动创建 edge_type=parent。
|
||||
- 需要避免明显循环,例如 A 是 B 的父本,同时 B 又是 A 的父本。
|
||||
|
||||
---
|
||||
|
||||
## 5.10 seed_lot 种子批次
|
||||
|
||||
### 业务说明
|
||||
|
||||
`seed_lot` 是实物库存批次,描述某一批种子当前有多少、放在哪里、属于哪个项目。它不是 germplasm 身份;同一个 germplasm 可以有多个 seed_lot。
|
||||
|
||||
### 字段说明
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 控件建议 | 校验规则 |
|
||||
| ------------------- | ---------------------------------------------------- | --------------------- | ------------------- | ----------------------------- |
|
||||
| `id` | SeedLot 主键 | 系统生成 | 隐藏/只读 | 必填、唯一 |
|
||||
| `auth_user_id` | 数据所属用户或租户 | 登录上下文自动写入 | 隐藏 | 不允许手填 |
|
||||
| `amount` | 当前库存数量,可以是粒数、重量、株数等 | 用户录入/交易自动更新 | 数字输入框 | 必填;非负;交易后自动更新 |
|
||||
| `created_date` | 批次创建时间 | 系统默认,可导入 | 日期时间选择器/只读 | 默认当前时间 |
|
||||
| `description` | 批次说明 | 用户录入 | 多行文本 | 可选 |
|
||||
| `last_updated` | 最后更新时间,包含交易变化 | 系统自动更新 | 只读 | 不允许手动改 |
|
||||
| `name` | 批次名称,如 华占-2026-荆门-扩繁批 | 用户录入或自动生成 | 文本框 | 必填;同一 program 下建议唯一 |
|
||||
| `source_collection` | 原始来源 collection,如野外采集、nursery、种质库集合 | 用户录入 | 文本框 | 可选 |
|
||||
| `storage_location` | 具体库位描述,如 冰箱A-2层-盒03 | 用户录入 | 文本框 | 可选 |
|
||||
| `units` | 数量单位,如 seeds、g、kg、plants | 用户选择 | 下拉框/文本框 | 必填;交易单位需一致或可换算 |
|
||||
| `location_id` | 库存所在地点 | 从 location 选择 | 地点选择器 | 可选 |
|
||||
| `program_id` | 所属项目 | 从 program 选择 | 项目选择器 | 可选;用于项目库存筛选 |
|
||||
|
||||
### 录入建议
|
||||
|
||||
- 创建 seed_lot 后必须进入“批次组成”Tab,至少录入一条 `seed_lot_content_mixture`。
|
||||
- 普通用户不要直接编辑 amount;amount 应通过入库、出库、转移、分装等交易动作更新。
|
||||
- 支持库存状态:充足、低库存、耗尽,可由 amount 和阈值计算。
|
||||
|
||||
---
|
||||
|
||||
## 5.11 seed_lot_content_mixture 批次组成
|
||||
|
||||
### 业务说明
|
||||
|
||||
`seed_lot_content_mixture` 描述一个 seed_lot 由哪些材料或 cross 组成。单一材料批次也需要有一条组成记录,比例为 100%。混合批次则多条记录占比合计为 100%。
|
||||
|
||||
### 字段说明
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 控件建议 | 校验规则 |
|
||||
| -------------------- | ----------------------------- | -------------------- | ------------------- | ---------------------------------------- |
|
||||
| `id` | 批次组成主键 | 系统生成 | 隐藏/只读 | 必填、唯一 |
|
||||
| `mixture_percentage` | 该材料或 cross 在批次中的占比 | 用户录入 | 百分比输入框 | 0 到 100;同一 seed lot 总和建议等于 100 |
|
||||
| `cross_id` | 来源 cross | 从 cross_entity 选择 | Cross 选择器 | 与 `germplasm_id` 至少一个必填 |
|
||||
| `germplasm_id` | 来源 germplasm | 从 germplasm 选择 | 材料选择器 | 与 `cross_id` 至少一个必填 |
|
||||
| `seed_lot_id` | 所属 seed lot | 由详情页带入或选择 | SeedLot 选择器/隐藏 | 必选 |
|
||||
|
||||
### 录入建议
|
||||
|
||||
- 新建 seed_lot 时,如果用户选择了单个 germplasm,系统自动生成一条 mixture:`germplasm_id=所选材料`,`mixture_percentage=100`。
|
||||
- 如果来源是某次杂交产生的种子,优先填写 `cross_id`。
|
||||
- 如果既能追溯 cross 又能追溯 germplasm,可按系统设计决定是否允许同时填写;若允许,同时展示“来源杂交”和“当前材料身份”。
|
||||
|
||||
---
|
||||
|
||||
## 5.12 seed_lot_transaction 批次流转
|
||||
|
||||
### 业务说明
|
||||
|
||||
`seed_lot_transaction` 记录库存变化。它不应该由用户像普通表单一样手动维护,而应该由“入库、出库、转移、分装、合并、消耗”等业务动作自动生成。
|
||||
|
||||
### 字段说明
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 控件建议 | 校验规则 |
|
||||
| ------------------ | -------------------------------------------- | ----------------------- | ------------------- | ------------------------------------------------- |
|
||||
| `id` | 流转记录主键 | 系统生成 | 隐藏/只读 | 必填、唯一 |
|
||||
| `auth_user_id` | 数据所属用户或租户 | 登录上下文自动写入 | 隐藏 | 不允许手填 |
|
||||
| `amount` | 流转数量 | 用户在业务动作中录入 | 数字输入框 | 必填;大于 0 |
|
||||
| `description` | 流转说明,如用于某 study、分装原因、报废原因 | 用户录入 | 多行文本 | 可选;出库/报废建议必填 |
|
||||
| `timestamp` | 流转发生时间 | 默认当前时间,可调整 | 日期时间选择器 | 必填 |
|
||||
| `units` | 流转单位 | 默认继承 seed_lot.units | 下拉框/只读 | 必填;需与 seed_lot 单位一致或有换算关系 |
|
||||
| `from_seed_lot_id` | 来源批次 | 按动作自动设置 | SeedLot 选择器/隐藏 | 与 `to_seed_lot_id` 至少一个存在 |
|
||||
| `to_seed_lot_id` | 目标批次 | 按动作自动设置 | SeedLot 选择器/隐藏 | 与 `from_seed_lot_id` 至少一个存在;不能等于 from |
|
||||
|
||||
### 业务动作映射
|
||||
|
||||
| 动作 | from_seed_lot_id | to_seed_lot_id | amount 对库存影响 |
|
||||
| --------- | ---------------- | -------------- | -------------------------------------------- |
|
||||
| 入库 | 空 | 目标批次 | 目标批次增加 |
|
||||
| 出库 | 来源批次 | 空 | 来源批次减少 |
|
||||
| 转移 | 来源批次 | 目标批次 | 来源减少,目标增加 |
|
||||
| 分装 | 原批次 | 新批次 | 原批次减少,新批次增加 |
|
||||
| 合并 | 多个来源批次 | 目标批次 | 来源减少,目标增加;可能生成多条 transaction |
|
||||
| 消耗/报废 | 来源批次 | 空 | 来源减少,并记录原因 |
|
||||
|
||||
### 验收标准
|
||||
|
||||
1. amount 必须大于 0。
|
||||
2. 出库/消耗时,amount 不得超过来源批次当前库存。
|
||||
3. from 和 to 不能相同。
|
||||
4. transaction 创建后应自动更新 seed_lot.amount 和 last_updated。
|
||||
5. 已生成的 transaction 原则上不允许随意修改;如需纠错,应通过反向交易或更正记录处理。
|
||||
|
||||
---
|
||||
|
||||
# 6. 跨表联动与关键校验
|
||||
|
||||
## 6.1 选择器联动
|
||||
|
||||
| 场景 | 联动规则 |
|
||||
| ------------------------- | ------------------------------------------------------------ |
|
||||
| 创建 germplasm | 必须先选择 crop;breeding_method 可选 |
|
||||
| 创建 attribute definition | crop 可选;若选择 trait/method/scale/ontology,则必须引用存在记录 |
|
||||
| 创建 attribute value | 选择 germplasm 后,attribute 选择器优先展示同 crop 或未限定 crop 的属性 |
|
||||
| 创建 crossing project | 必须选择 program |
|
||||
| 创建 cross | 必须先选择 crossing_project;计划杂交和实际杂交使用不同入口 |
|
||||
| 创建 cross parent | 从 cross 详情页进入时自动带出 cross 和 crossing_project |
|
||||
| 创建 seed lot | 可选择 program、location;保存后必须维护 content mixture |
|
||||
| 创建 transaction | 从 seed lot 详情页进入时自动带出 from/to seed lot |
|
||||
|
||||
## 6.2 删除规则
|
||||
|
||||
| 对象 | 删除限制 |
|
||||
| -------------------- | ------------------------------------------------------------ |
|
||||
| breeding_method | 已被 germplasm 引用时不可删除 |
|
||||
| germplasm | 已被 attribute value、cross parent、seed lot mixture、pedigree、observation unit 引用时不可删除 |
|
||||
| attribute definition | 已有 attribute value 时不可删除 |
|
||||
| crossing_project | 已有 cross / cross parent / pedigree node 时不可删除 |
|
||||
| cross_entity | 已有 parent、seed lot mixture、observation unit 引用时不可删除 |
|
||||
| seed_lot | 已有 mixture、transaction、observation unit 引用时不可删除 |
|
||||
| transaction | 原则上不可物理删除;只能冲销或作废 |
|
||||
|
||||
## 6.3 批量导入要求
|
||||
|
||||
### Germplasm 导入
|
||||
|
||||
必需列建议:
|
||||
|
||||
```text
|
||||
crop_id 或 crop_name
|
||||
germplasm_name 或 default_display_name
|
||||
```
|
||||
|
||||
强烈建议列:
|
||||
|
||||
```text
|
||||
accession_number
|
||||
germplasmpui
|
||||
breeding_method_name
|
||||
country_of_origin_code
|
||||
genus
|
||||
species
|
||||
seed_source
|
||||
```
|
||||
|
||||
### SeedLot 导入
|
||||
|
||||
必需列建议:
|
||||
|
||||
```text
|
||||
seed_lot_name
|
||||
amount
|
||||
units
|
||||
germplasm_id/germplasm_name 或 cross_id/cross_name
|
||||
mixture_percentage
|
||||
```
|
||||
|
||||
### Attribute Value 导入
|
||||
|
||||
必需列建议:
|
||||
|
||||
```text
|
||||
germplasm_id 或 germplasm_name
|
||||
attribute_id 或 attribute_name/code
|
||||
value
|
||||
```
|
||||
|
||||
可选列:
|
||||
|
||||
```text
|
||||
determined_date
|
||||
source
|
||||
remark
|
||||
```
|
||||
|
||||
导入必须先预校验,错误报告至少包含:行号、字段、错误原因、建议修复方式。
|
||||
|
||||
---
|
||||
|
||||
# 7. 后端接口建议
|
||||
|
||||
## 7.1 主数据接口
|
||||
|
||||
```text
|
||||
GET /germplasm
|
||||
POST /germplasm
|
||||
GET /germplasm/{id}
|
||||
PUT /germplasm/{id}
|
||||
DELETE /germplasm/{id}
|
||||
|
||||
GET /breeding-methods
|
||||
POST /breeding-methods
|
||||
|
||||
GET /germplasm-attributes
|
||||
POST /germplasm-attributes
|
||||
|
||||
GET /germplasm/{id}/attribute-values
|
||||
POST /germplasm/{id}/attribute-values
|
||||
|
||||
GET /crossing-projects
|
||||
POST /crossing-projects
|
||||
GET /crossing-projects/{id}/crosses
|
||||
|
||||
GET /crosses
|
||||
POST /crosses
|
||||
GET /crosses/{id}/parents
|
||||
POST /crosses/{id}/parents
|
||||
|
||||
GET /seed-lots
|
||||
POST /seed-lots
|
||||
GET /seed-lots/{id}/mixtures
|
||||
POST /seed-lots/{id}/mixtures
|
||||
GET /seed-lots/{id}/transactions
|
||||
POST /seed-lots/{id}/transactions
|
||||
```
|
||||
|
||||
## 7.2 选择器接口
|
||||
|
||||
```text
|
||||
GET /selectors/crops
|
||||
GET /selectors/breeding-methods
|
||||
GET /selectors/germplasm?cropId=&keyword=
|
||||
GET /selectors/germplasm-attributes?cropId=&datatype=&keyword=
|
||||
GET /selectors/crossing-projects?programId=&keyword=
|
||||
GET /selectors/crosses?crossingProjectId=&planned=&keyword=
|
||||
GET /selectors/seed-lots?programId=&locationId=&keyword=
|
||||
GET /selectors/observation-units?studyId=&germplasmId=&keyword=
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 8. 测试验收清单
|
||||
|
||||
1. 创建 germplasm 时,未选择 crop 不允许保存。
|
||||
2. `germplasm_name` 和 `default_display_name` 都为空时不允许保存。
|
||||
3. `germplasmpui` 重复时提示唯一性冲突。
|
||||
4. attribute value 的 value 必须按 attribute datatype / scale 校验。
|
||||
5. 创建 cross 时必须选择 crossing project。
|
||||
6. 实际杂交选择 planned_cross_id 时不能选择自身。
|
||||
7. cross parent 必须选择 parent_type。
|
||||
8. cross parent 的 germplasm_id 和 observation_unit_id 至少填写一个。
|
||||
9. seed lot 的 amount 不允许小于 0。
|
||||
10. seed lot 的 units 必填。
|
||||
11. seed lot content mixture 的 percentage 必须在 0 到 100 之间。
|
||||
12. 同一 seed lot 的 mixture_percentage 总和不为 100 时,保存前应提示或禁止保存,具体取决于业务配置。
|
||||
13. 出库数量不能超过当前库存。
|
||||
14. transaction 创建后自动更新 seed_lot.amount。
|
||||
15. 已有关联下游数据的 germplasm、cross、seed_lot 不允许物理删除。
|
||||
|
||||
778
docs/requirements/03-phenotyping-entry-requirements.md
Normal file
778
docs/requirements/03-phenotyping-entry-requirements.md
Normal file
@@ -0,0 +1,778 @@
|
||||
# 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 不只是备注,它是解释表型差异的重要上下文,应支持作用范围和参数化。
|
||||
|
||||
890
docs/requirements/04-genotyping-entry-requirements.md
Normal file
890
docs/requirements/04-genotyping-entry-requirements.md
Normal file
@@ -0,0 +1,890 @@
|
||||
# Genotyping 模块专业数据录入需求文档 V2
|
||||
|
||||
## 1. 文档目的
|
||||
|
||||
本文档用于指导 Genotyping 基因型模块的前端页面、后端接口、字段校验、文件导入、矩阵展示和测试验收设计。本文档不只描述数据库表关系,而是从真实基因型检测业务出发,解释每个对象和字段的业务意义、录入方式、控件建议、校验规则和上下游影响。
|
||||
|
||||
## 2. 模块定位
|
||||
|
||||
Genotyping 模块描述从田间或实验对象取样,到实验室检测,再到导入基因型结果矩阵的完整流程。
|
||||
|
||||
它的核心业务不是“录 allele_call 表”,而是:
|
||||
|
||||
```text
|
||||
从哪个 study / observation_unit 取样
|
||||
样本放在哪块 plate 的哪个 well
|
||||
检测结果基于哪个 reference_set
|
||||
结果文件包含哪些 variantset / variant
|
||||
每个 sample 在每个 variant 上的 genotype 是什么
|
||||
这些位点是否还映射到 genome_map / linkage_group
|
||||
```
|
||||
|
||||
## 3. 核心业务主线
|
||||
|
||||
Genotyping 有三条主线:
|
||||
|
||||
```text
|
||||
样本线:study / observation_unit -> plate -> sample -> callset
|
||||
位点线:reference_set -> reference -> reference_bases -> variantset -> variant
|
||||
结果线:callset + variant -> allele_call
|
||||
图谱线:crop -> genome_map -> linkage_group -> marker_position -> variant
|
||||
```
|
||||
|
||||
### 3.1 样本线
|
||||
|
||||
样本线回答:
|
||||
|
||||
```text
|
||||
从哪个材料、哪个 plot、哪株植物或哪个样本来源取样?
|
||||
样本叫什么?
|
||||
谁采的?什么时候采的?
|
||||
放在哪块样本板哪个孔?
|
||||
```
|
||||
|
||||
对应核心表:
|
||||
|
||||
```text
|
||||
plate
|
||||
sample
|
||||
callset
|
||||
```
|
||||
|
||||
### 3.2 位点线
|
||||
|
||||
位点线回答:
|
||||
|
||||
```text
|
||||
检测结果基于哪个参考基因组?
|
||||
参考序列有哪些 chromosome / scaffold / contig?
|
||||
检测了哪些 SNP / Indel / SV 位点?
|
||||
这些位点属于哪个 variantset?
|
||||
```
|
||||
|
||||
对应核心表:
|
||||
|
||||
```text
|
||||
reference_set
|
||||
reference
|
||||
reference_bases
|
||||
variantset
|
||||
variant
|
||||
```
|
||||
|
||||
### 3.3 结果线
|
||||
|
||||
结果线回答:
|
||||
|
||||
```text
|
||||
某个 sample 在某个 variant 上的 genotype 是什么?
|
||||
测序深度是多少?
|
||||
是否 phased?
|
||||
是否有 likelihood?
|
||||
```
|
||||
|
||||
对应核心表:
|
||||
|
||||
```text
|
||||
callset
|
||||
allele_call
|
||||
```
|
||||
|
||||
### 3.4 图谱线
|
||||
|
||||
图谱线回答:
|
||||
|
||||
```text
|
||||
某个位点在遗传图谱或物理图谱上的位置是什么?
|
||||
它属于哪个 linkage group / chromosome / scaffold?
|
||||
单位是 cM 还是 Mb?
|
||||
```
|
||||
|
||||
对应核心表:
|
||||
|
||||
```text
|
||||
genome_map
|
||||
linkage_group
|
||||
marker_position
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 4. 推荐业务流程
|
||||
|
||||
```text
|
||||
1. 准备 Core / Phenotyping 上游数据:crop、program、trial、study、observation_unit
|
||||
2. 从 observation_unit 或 study 批量生成 sample
|
||||
3. 创建 plate,并把 sample 分配到 well / row / column
|
||||
4. 送检或接收实验室结果文件
|
||||
5. 创建 reference_set,维护 reference / reference_bases
|
||||
6. 创建 variantset,导入 variant
|
||||
7. 为 sample 生成 callset
|
||||
8. 绑定 callset 与 variantset
|
||||
9. 导入 allele_call,形成 sample × variant 基因型矩阵
|
||||
10. 如有遗传图谱,创建 genome_map / linkage_group / marker_position
|
||||
11. 做结果质控:缺失率、重复位点、重复样本、read depth、genotype 格式、reference 一致性
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 5. 推荐页面形态
|
||||
|
||||
## 5.1 Genotyping 工作台
|
||||
|
||||
建议以 study 或 genotyping project 为入口组织页面:
|
||||
|
||||
```text
|
||||
Genotyping 工作台
|
||||
├─ 样本管理:sample 列表、从 observation_unit 生成 sample
|
||||
├─ 样本板:plate 列表、96/384 孔位布局
|
||||
├─ 参考基因组:reference_set、reference、reference_bases
|
||||
├─ 变异集合:variantset、variant、analysis、format
|
||||
├─ 检测结果:callset、allele_call、genotype matrix
|
||||
├─ 遗传图谱:genome_map、linkage_group、marker_position
|
||||
├─ 导入导出:sample 模板、VCF/Hapmap/CSV、allele matrix
|
||||
└─ 质控:缺失率、重复率、深度、位点过滤、样本过滤
|
||||
```
|
||||
|
||||
## 5.2 Plate 孔位布局页
|
||||
|
||||
样本板不建议只做普通 CRUD,应提供 96 孔 / 384 孔布局视图:
|
||||
|
||||
```text
|
||||
Plate-96-001
|
||||
01 02 03 04
|
||||
A SAMPLE-001 SAMPLE-002 SAMPLE-003 SAMPLE-004
|
||||
B SAMPLE-013 SAMPLE-014 SAMPLE-015 SAMPLE-016
|
||||
C SAMPLE-025 SAMPLE-026 EMPTY EMPTY
|
||||
...
|
||||
```
|
||||
|
||||
用户在孔位上放置 sample,系统保存:
|
||||
|
||||
```text
|
||||
sample.plate_id
|
||||
sample.well
|
||||
sample.plate_row
|
||||
sample.plate_column
|
||||
```
|
||||
|
||||
## 5.3 Genotype Matrix 结果页
|
||||
|
||||
`allele_call` 是基因型矩阵中的一个格子:
|
||||
|
||||
| sample / callset | SNP001 | SNP002 | SNP003 |
|
||||
| ---------------- | ------ | ------ | ------ |
|
||||
| SAMPLE-001 | A/G | C/C | T/G |
|
||||
| SAMPLE-002 | A/A | C/T | T/T |
|
||||
| SAMPLE-003 | ./ . | C/C | T/G |
|
||||
|
||||
导入时:
|
||||
|
||||
```text
|
||||
样本列 / 样本行 -> sample / callset
|
||||
位点列 / 位点行 -> variant
|
||||
单元格 genotype -> allele_call.genotype
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 6. 字段级专业录入需求
|
||||
|
||||
## 6.1 plate 样本板
|
||||
|
||||
### 业务说明
|
||||
|
||||
`plate` 用于管理承载样本的样本板或样本容器组。真实业务里,它通常是一块 96 孔板、384 孔板,也可能是一组试管或实验室提交批次。plate 的核心价值是支持实验室送样、扫码、孔位追踪和结果回填。
|
||||
|
||||
### 字段说明
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 控件建议 | 校验规则 |
|
||||
| -------------------------- | ----------------------------------------------- | ------------------------------ | ----------------- | -------------------------------------------- |
|
||||
| `id` | 样本板主键,系统内部唯一标识 | 系统生成;导入可指定 | 隐藏/只读 | 必填、唯一;编辑不可修改 |
|
||||
| `auth_user_id` | 数据所属用户或租户 | 登录上下文自动写入 | 隐藏 | 不允许前端手填 |
|
||||
| `client_plate_barcode` | 客户侧或外部实验室给出的板条码 | 用户录入/扫码/导入 | 文本框/扫码框 | 可选;建议唯一 |
|
||||
| `client_plate_db_id` | 客户侧系统中的 plate ID | 用户录入/导入 | 文本框 | 可选;用于外部系统对接 |
|
||||
| `plate_barcode` | 本系统样本板条码 | 系统生成/扫码录入 | 文本框/扫码框 | 可选;建议唯一 |
|
||||
| `plate_format` | 样本板规格,如 96、384 | 用户选择 | 下拉框 | 可选;若填写,应限制为系统支持规格 |
|
||||
| `plate_name` | 样本板名称,如 Plate-2026-001 | 用户录入/系统生成 | 文本框 | 与 barcode 至少一个必填 |
|
||||
| `sample_submission_format` | 样本提交格式,如 DNA、tissue、seed、tube、plate | 用户选择 | 下拉框 | 可选;建议枚举化 |
|
||||
| `sample_type` | 该板默认样本类型 | 用户选择 | 下拉框 | 可选;可作为 sample 默认值 |
|
||||
| `status_time_stamp` | 样本板状态更新时间 | 系统自动写入 | 只读日期时间 | 可选;状态变化时自动更新 |
|
||||
| `program_id` | 所属项目 | 从 program 选择或由 study 带出 | 项目选择器/只读 | 可选;若 study 已选,应与 study.program 一致 |
|
||||
| `study_id` | 所属 study | 从 study 选择 | Study 选择器 | 可选;若选择则带出 trial/program |
|
||||
| `submission_id` | 外部 vendor submission 或送检单 ID | 用户录入/选择 | 选择器/文本框 | 可选;用于实验室送检对接 |
|
||||
| `trial_id` | 所属 trial | 从 trial 选择或由 study 带出 | Trial 选择器/只读 | 可选;若 study 已选,应与 study.trial 一致 |
|
||||
|
||||
### 录入建议
|
||||
|
||||
- 创建 plate 时,用户先选择 `plate_format`,系统生成孔位矩阵。
|
||||
- 如果从 study 工作台创建,`program_id`、`trial_id`、`study_id` 自动带出。
|
||||
- 如果通过实验室返回文件创建,应优先用 `plate_barcode` 或 `client_plate_barcode` 匹配。
|
||||
|
||||
### 验收标准
|
||||
|
||||
1. `plate_name` 和 `plate_barcode` 至少填写一个。
|
||||
2. 同一系统中 `plate_barcode` 不应重复。
|
||||
3. 选择 study 后,trial/program 自动带出且保持一致。
|
||||
4. plate_format 为 96 时,只允许 A01-H12;384 时只允许 A01-P24。
|
||||
|
||||
---
|
||||
|
||||
## 6.2 sample 样本
|
||||
|
||||
### 业务说明
|
||||
|
||||
`sample` 是 genotyping 流程的样本入口,表示从田间、温室、实验室或库存材料中取出的一个物理生物样本,例如叶片、种子、DNA、组织、提取液等。sample 应尽可能关联 observation_unit,这样才能把表型和基因型打通。
|
||||
|
||||
### 字段说明
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 控件建议 | 校验规则 |
|
||||
| --------------------- | ------------------------------------ | -------------------------------------- | ---------------------- | --------------------------------------------------------- |
|
||||
| `id` | 样本主键,系统内部唯一标识 | 系统生成;导入可指定 | 隐藏/只读 | 必填、唯一 |
|
||||
| `auth_user_id` | 数据所属用户或租户 | 登录上下文自动写入 | 隐藏 | 不允许手填 |
|
||||
| `concentration` | 样本浓度,如 DNA 浓度 50 ng/µL | 用户录入/仪器导入 | 数字+单位输入 | 可选;建议拆分数值和单位,当前字段可暂存文本 |
|
||||
| `plate_column` | 样本在 plate 中的列号 | 孔位选择自动生成 | 数字输入/孔位控件 | 绑定 plate 时必填或由 well 解析;范围受 plate_format 限制 |
|
||||
| `plate_row` | 样本在 plate 中的行号 | 孔位选择自动生成 | 下拉/孔位控件 | 绑定 plate 时必填或由 well 解析;范围受 plate_format 限制 |
|
||||
| `sample_barcode` | 样本条码 | 扫码/系统生成/导入 | 文本框/扫码框 | 可选;建议唯一 |
|
||||
| `sample_description` | 样本说明 | 用户录入 | 多行文本 | 可选 |
|
||||
| `sample_group_db_id` | 样本组 ID,用于批次、送检组或处理组 | 用户选择/导入 | 选择器/文本框 | 可选 |
|
||||
| `sample_name` | 样本名称,如 SAMPLE-001 | 用户录入/系统生成 | 文本框 | 必填;同一 study 下建议唯一 |
|
||||
| `samplepui` | 样本永久唯一标识 | 用户录入/系统生成 | 文本框/URL 输入框 | 可选;若填写建议唯一 |
|
||||
| `sample_timestamp` | 采样时间 | 用户录入/设备带出 | 日期时间选择器 | 可选;建议必填 |
|
||||
| `sample_type` | 样本类型,如 tissue、DNA、seed、leaf | 用户选择 | 下拉框 | 可选;建议枚举化 |
|
||||
| `taken_by` | 采样人 | 用户选择/登录用户带出 | 人员选择器/文本框 | 可选 |
|
||||
| `tissue_type` | 组织类型,如 leaf、root、grain、stem | 用户选择/录入 | 下拉框/文本框 | 可选;样本为组织时建议填写 |
|
||||
| `volume` | 样本体积,如 50 µL | 用户录入/仪器导入 | 数字+单位输入 | 可选;当前字段可暂存文本 |
|
||||
| `well` | 孔位,如 A01、H12 | 孔位布局选择 | 孔位选择器 | 绑定 plate 时建议必填;同一 plate 内唯一 |
|
||||
| `observation_unit_id` | 来源观测单元 | 从 observation_unit 选择或批量生成带出 | ObservationUnit 选择器 | 可选但强烈建议填写;必须存在 |
|
||||
| `plate_id` | 所属样本板 | 从 plate 选择 | Plate 选择器 | 可选;若填写需校验孔位唯一 |
|
||||
| `program_id` | 项目上下文 | 由 observation_unit/study 带出 | 只读/隐藏 | 可选但建议保存;需与 study 一致 |
|
||||
| `study_id` | 所属 study | 由 observation_unit 带出 | Study 选择器/只读 | 可选但建议保存;若有 observation_unit 必须一致 |
|
||||
| `taxon_id_id` | 分类单元 ID | 从 taxon 选择 | Taxon 选择器 | 可选 |
|
||||
| `trial_id` | 所属 trial | 由 observation_unit/study 带出 | Trial 选择器/只读 | 可选但建议保存 |
|
||||
|
||||
### 录入建议
|
||||
|
||||
- 推荐从 observation_unit 批量生成 sample:一行 observation_unit 生成一个或多个 sample。
|
||||
- 如果样本放入 plate,应通过孔位布局页设置 well,不建议手填 A01。
|
||||
- `well`、`plate_row`、`plate_column` 应保持一致:well=A01,则 row=A,column=1。
|
||||
- sample 是物理样本,不是 genotype 结果;结果必须进入 callset / allele_call。
|
||||
|
||||
### 验收标准
|
||||
|
||||
1. `sample_name` 必填。
|
||||
2. 同一 plate 中 `well` 不允许重复。
|
||||
3. 绑定 observation_unit 时,study/trial/program 自动带出。
|
||||
4. 绑定 plate 时,well 必须符合 plate_format。
|
||||
5. 样本已有 callset 时,不允许物理删除。
|
||||
|
||||
---
|
||||
|
||||
## 6.3 reference_set 参考集
|
||||
|
||||
### 业务说明
|
||||
|
||||
`reference_set` 表示一个参考基因组集合,也就是一套 reference genome assembly。它定义 variant 坐标体系。不同参考基因组版本之间的位点坐标不可随意混用。
|
||||
|
||||
### 字段说明
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 控件建议 | 校验规则 |
|
||||
| -------------------------- | ---------------------------------------------- | ------------------ | ----------------- | ---------------------------- |
|
||||
| `id` | 参考集主键 | 系统生成 | 隐藏/只读 | 必填、唯一 |
|
||||
| `auth_user_id` | 数据所属用户或租户 | 登录上下文自动写入 | 隐藏 | 不允许手填 |
|
||||
| `assemblypui` | 参考组装永久标识,如 DOI、INSDC accession、URI | 用户录入/导入 | 文本框/URL 输入框 | 可选;建议唯一 |
|
||||
| `description` | 参考集说明 | 用户录入 | 多行文本 | 可选 |
|
||||
| `is_derived` | 是否由其他参考集派生 | 用户选择 | 开关 | 可选 |
|
||||
| `md5checksum` | 参考集校验值 | 用户录入/文件计算 | 文本框 | 可选;若填写应符合 MD5 格式 |
|
||||
| `reference_set_name` | 参考集名称,如 IRGSP-1.0、B73 RefGen_v5 | 用户录入 | 文本框 | 必填;建议唯一 |
|
||||
| `sourceuri` | 参考集来源 URI 或下载地址 | 用户录入 | URL 输入框 | 可选;校验 URL |
|
||||
| `species_ontology_term` | 物种本体术语名称 | 用户录入/选择 | 文本框/本体选择器 | 可选 |
|
||||
| `species_ontology_termuri` | 物种本体 URI | 用户录入/本体带出 | URL 输入框 | 可选;校验 URL |
|
||||
| `source_germplasm_id` | 参考基因组来源材料 | 从 germplasm 选择 | Germplasm 选择器 | 可选;必须引用存在 germplasm |
|
||||
|
||||
### 录入建议
|
||||
|
||||
- reference_set 是 variantset 和 variant 的上游,不能随意改。
|
||||
- 如果导入 VCF,必须先明确该 VCF 的 reference_set。
|
||||
- 同一 crop 可能存在多个 reference_set,应在页面上清楚显示版本和来源。
|
||||
|
||||
---
|
||||
|
||||
## 6.4 reference 参考序列
|
||||
|
||||
### 业务说明
|
||||
|
||||
`reference` 是 reference_set 中的一条参考序列,例如 chromosome、contig、scaffold。variant 的坐标通常是基于某条 reference 的位置。
|
||||
|
||||
### 字段说明
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 控件建议 | 校验规则 |
|
||||
| ------------------- | --------------------------------------------- | --------------------- | ------------------- | ------------------------------- |
|
||||
| `id` | 参考序列主键 | 系统生成 | 隐藏/只读 | 必填、唯一 |
|
||||
| `auth_user_id` | 数据所属用户或租户 | 登录上下文自动写入 | 隐藏 | 不允许手填 |
|
||||
| `length` | 序列长度 | 用户录入/FASTA 导入 | 数字输入框 | 必填或建议必填;非负整数 |
|
||||
| `md5checksum` | 单条 reference 的 MD5 校验值 | 用户录入/文件计算 | 文本框 | 可选;若填写应符合 MD5 格式 |
|
||||
| `reference_name` | 参考序列名称,如 chr1、1、Chr01、scaffold_001 | 用户录入/FASTA 导入 | 文本框 | 必填;同一 reference_set 内唯一 |
|
||||
| `source_divergence` | 与来源序列的差异程度 | 用户录入/导入 | 数字输入框 | 可选;范围建议 0-1 或按业务定义 |
|
||||
| `reference_set_id` | 所属 reference_set | 从 reference_set 选择 | ReferenceSet 选择器 | 必选 |
|
||||
|
||||
### 录入建议
|
||||
|
||||
- 建议通过 FASTA 索引文件批量导入 chromosome / contig。
|
||||
- 同一 reference_set 下 reference_name 必须唯一。
|
||||
- variant 导入时,文件中的 CHROM 必须能匹配 reference.reference_name。
|
||||
|
||||
---
|
||||
|
||||
## 6.5 reference_bases 参考片段
|
||||
|
||||
### 业务说明
|
||||
|
||||
`reference_bases` 保存 reference 的序列片段或分页。通常不建议人工录入,而是由 FASTA 导入或接口生成。对于大型基因组,不建议把完整序列全部塞入普通业务表,除非系统确实需要序列查询。
|
||||
|
||||
### 字段说明
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 控件建议 | 校验规则 |
|
||||
| -------------- | ------------------ | ------------------- | --------------------- | --------------------------------------- |
|
||||
| `id` | 参考片段主键 | 系统生成 | 隐藏/只读 | 必填、唯一 |
|
||||
| `auth_user_id` | 数据所属用户或租户 | 登录上下文自动写入 | 隐藏 | 不允许手填 |
|
||||
| `bases` | 碱基序列片段 | FASTA 导入/接口生成 | 多行文本/只读 | 必填;只能包含合法碱基字符 A/C/G/T/N 等 |
|
||||
| `page_number` | 分页编号 | 系统生成 | 数字输入/只读 | 必填;非负整数;同一 reference 下唯一 |
|
||||
| `reference_id` | 所属 reference | 由导入带出 | Reference 选择器/隐藏 | 必选 |
|
||||
|
||||
### 录入建议
|
||||
|
||||
- 不建议前端做手工新增入口,只提供查看和导入。
|
||||
- 每页 bases 长度应固定或可配置,例如 2KB、10KB、100KB。
|
||||
- 导入时校验页码连续性和字符合法性。
|
||||
|
||||
---
|
||||
|
||||
## 6.6 variantset 变异集合
|
||||
|
||||
### 业务说明
|
||||
|
||||
`variantset` 是一批 variant 和相关 call 的集合,通常对应一个 VCF 文件、一个 SNP 芯片结果、一个测序分析批次或一个 study 的基因型数据集。它必须明确 reference_set,否则 variant 坐标无意义。
|
||||
|
||||
### 字段说明
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 控件建议 | 校验规则 |
|
||||
| ------------------ | ---------------------------------------------- | --------------------- | ------------------- | ----------------------------------------- |
|
||||
| `id` | 变异集合主键 | 系统生成 | 隐藏/只读 | 必填、唯一 |
|
||||
| `auth_user_id` | 数据所属用户或租户 | 登录上下文自动写入 | 隐藏 | 不允许手填 |
|
||||
| `variant_set_name` | 变异集合名称,如 Rice-2026-SNPSet、VCF-Run-001 | 用户录入/文件名带出 | 文本框 | 必填;建议唯一 |
|
||||
| `reference_set_id` | 使用的参考集 | 从 reference_set 选择 | ReferenceSet 选择器 | 必选 |
|
||||
| `study_id` | 关联 study | 从 study 选择 | Study 选择器 | 可选;若填写,应与样本来源 study 保持一致 |
|
||||
|
||||
### 录入建议
|
||||
|
||||
- 导入 VCF/CSV 前必须先选择 reference_set。
|
||||
- 如果 variantset 来源于某次 study 的样本检测,应填写 study_id。
|
||||
- variantset 详情页应展示:variant 数量、callset 数量、分析信息、文件格式、导入状态。
|
||||
|
||||
---
|
||||
|
||||
## 6.7 variant 变异位点
|
||||
|
||||
### 业务说明
|
||||
|
||||
`variant` 表示一个遗传序列上的位点或区间,可以是 SNP、INDEL、SV,也可以是传统 marker。它通常由 reference_set、reference_name、start/end、reference_bases、alternate_bases 共同定义。
|
||||
|
||||
### 字段说明
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 控件建议 | 校验规则 |
|
||||
| ------------------ | ------------------------------------ | ------------------------------------- | ------------------------ | ------------------------------------------------ |
|
||||
| `id` | 变异主键 | 系统生成/导入 | 隐藏/只读 | 必填、唯一 |
|
||||
| `auth_user_id` | 数据所属用户或租户 | 登录上下文自动写入 | 隐藏 | 不允许手填 |
|
||||
| `created` | 创建时间 | 系统默认/文件导入 | 日期时间只读 | 可选;默认当前时间 |
|
||||
| `variant_end` | 变异结束位置 | 文件导入/用户录入 | 数字输入框 | 可选;非负整数;不小于 start |
|
||||
| `filters_applied` | 是否执行过过滤 | 文件导入/系统写入 | 开关/只读 | 可选 |
|
||||
| `filters_passed` | 是否通过过滤 | 文件导入/系统写入 | 开关/只读 | 可选 |
|
||||
| `reference_bases` | 参考等位基因或参考碱基 | 文件导入/用户录入 | 文本框 | 可选;合法碱基字符 |
|
||||
| `variant_start` | 变异起始位置 | 文件导入/用户录入 | 数字输入框 | 必填;非负整数 |
|
||||
| `svlen` | 结构变异长度 | 文件导入/用户录入 | 数字输入框 | 可选;SV 类型时建议填写 |
|
||||
| `updated` | 更新时间 | 系统自动写入 | 只读日期时间 | 自动更新 |
|
||||
| `variant_name` | 变异名称,如 SNP001、chr1_123456_A_G | 用户录入/自动生成 | 文本框 | 必填或由位置自动生成;同一 variantset 下建议唯一 |
|
||||
| `variant_type` | 变异类型,如 SNP、INDEL、SV、marker | 用户选择/文件导入 | 下拉框 | 可选;建议枚举化 |
|
||||
| `reference_set_id` | 所属参考集 | 从 reference_set 选择/variantset 带出 | ReferenceSet 选择器/隐藏 | 必选;需与 variantset.reference_set 一致 |
|
||||
| `variant_set_id` | 所属变异集合 | 从 variantset 选择 | VariantSet 选择器/隐藏 | 必选 |
|
||||
|
||||
### 录入建议
|
||||
|
||||
- 大量 variant 应通过 VCF、HapMap、CSV 批量导入,不建议手工录入。
|
||||
- 如果当前表缺少 alternate_bases、reference_name 字段,应在导入逻辑或扩展表中补齐,否则变异定义不完整。
|
||||
- variant 的唯一性建议使用:`reference_set_id + reference_name + start + reference_bases + alternate_bases`。
|
||||
|
||||
---
|
||||
|
||||
## 6.8 callset 调用集合
|
||||
|
||||
### 业务说明
|
||||
|
||||
`callset` 表示某个 sample 参与一次测序、芯片或分析事件后形成的一组 genotype calls。多数情况下,一个 sample 对应一个 callset;但如果同一个 sample 多次送检或使用不同分析流程,则可以有多个 callset。
|
||||
|
||||
### 字段说明
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 控件建议 | 校验规则 |
|
||||
| --------------- | -------------------------------- | ----------------------- | ------------- | ---------------------------- |
|
||||
| `id` | CallSet 主键 | 系统生成/导入 | 隐藏/只读 | 必填、唯一 |
|
||||
| `auth_user_id` | 数据所属用户或租户 | 登录上下文自动写入 | 隐藏 | 不允许手填 |
|
||||
| `call_set_name` | 调用集合名称,如 SAMPLE-001_Run1 | 用户录入/系统生成 | 文本框 | 必填;同一 sample 下建议唯一 |
|
||||
| `created` | 创建时间 | 系统默认/导入 | 日期时间 | 默认当前时间 |
|
||||
| `updated` | 更新时间 | 系统自动写入 | 只读日期时间 | 自动更新 |
|
||||
| `sample_id` | 所属样本 | 从 sample 选择/导入匹配 | Sample 选择器 | 必选 |
|
||||
|
||||
### 录入建议
|
||||
|
||||
- 从 genotype 文件导入时,可以按样本列自动创建 callset。
|
||||
- callset 需要通过 `callset_variant_sets` 绑定它覆盖的 variantset。
|
||||
- sample 详情页应展示 callset 列表和每个 callset 的 variantset、call 数量、导入时间。
|
||||
|
||||
---
|
||||
|
||||
## 6.9 callset_variant_sets 调用集合与变异集合关系
|
||||
|
||||
### 业务说明
|
||||
|
||||
`callset_variant_sets` 表示某个 callset 覆盖哪些 variantset。它是一次检测结果和一批位点集合之间的关系。
|
||||
|
||||
### 字段说明
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 控件建议 | 校验规则 |
|
||||
| ----------------- | -------- | ------------------------------- | ---------------------- | -------- |
|
||||
| `call_sets_id` | 调用集合 | 从 callset 选择/导入自动创建 | CallSet 选择器/隐藏 | 必选 |
|
||||
| `variant_sets_id` | 变异集合 | 从 variantset 选择/导入自动绑定 | VariantSet 选择器/隐藏 | 必选 |
|
||||
|
||||
### 录入建议
|
||||
|
||||
- 从 variantset 导入 genotype matrix 时,系统应自动绑定所有新建 callset 到该 variantset。
|
||||
- 同一 callset + variantset 不允许重复。
|
||||
|
||||
---
|
||||
|
||||
## 6.10 allele_call 基因型结果
|
||||
|
||||
### 业务说明
|
||||
|
||||
`allele_call` 是最终基因型事实数据,表示某个 callset 在某个 variant 上的 genotype 结果。它是 genotype matrix 的一个格子。
|
||||
|
||||
### 字段说明
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 控件建议 | 校验规则 |
|
||||
| --------------------- | --------------------------------------------------------- | ------------------------ | --------------------- | ------------------------------ |
|
||||
| `id` | 基因型结果主键 | 系统生成/导入 | 隐藏/只读 | 必填、唯一 |
|
||||
| `auth_user_id` | 数据所属用户或租户 | 登录上下文自动写入 | 隐藏 | 不允许手填 |
|
||||
| `genotype` | genotype 结果,如 A/G、0/1、0 | 1、AA、./. | 文件导入/少量手工修正 | 文本框/基因型控件 |
|
||||
| `genotype_likelihood` | genotype likelihood,表示不同 genotype 的可能性或置信信息 | 文件导入 | 数字输入框 | 可选;范围和含义按导入格式定义 |
|
||||
| `phase_set` | phase set,用于 phased genotype 分组 | 文件导入 | 文本框 | 可选;phased 数据时使用 |
|
||||
| `read_depth` | 测序深度 | 文件导入/用户录入 | 数字输入框 | 可选;非负整数 |
|
||||
| `call_set_id` | 所属 callset | 从 callset 选择/导入匹配 | CallSet 选择器/隐藏 | 必选 |
|
||||
| `variant_id` | 对应 variant | 从 variant 选择/导入匹配 | Variant 选择器/隐藏 | 必选 |
|
||||
|
||||
### 录入建议
|
||||
|
||||
- allele_call 不建议大量手工新增,主入口应该是 VCF/HapMap/CSV 文件导入。
|
||||
- 同一 `call_set_id + variant_id` 不应重复。
|
||||
- `genotype` 需要兼容常见格式:
|
||||
- Allele 格式:A/G、C/C、T/G;
|
||||
- VCF 编码:0/0、0/1、1/1、0|1;
|
||||
- 缺失:./.、NA、N、--,具体缺失字符串由 variantset_format 配置。
|
||||
- 导入后应支持按 sample/callset 或 variant 两种视角查询。
|
||||
|
||||
---
|
||||
|
||||
## 6.11 variantset_analysis 变异集合分析信息
|
||||
|
||||
### 业务说明
|
||||
|
||||
`variantset_analysis` 记录 variantset 的分析流程、软件、版本、参数、时间等信息。它是数据可追溯的重要来源。
|
||||
|
||||
### 字段说明
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 控件建议 | 校验规则 |
|
||||
| ---------------- | ------------------------------------------------------------ | ------------------ | ---------------------- | -------------- |
|
||||
| `id` | 分析记录主键 | 系统生成 | 隐藏/只读 | 必填、唯一 |
|
||||
| `analysis_name` | 分析名称,如 GATK SNP Calling Run 2026-01 | 用户录入/导入带出 | 文本框 | 必填 |
|
||||
| `created` | 创建时间 | 系统默认 | 日期时间 | 默认当前时间 |
|
||||
| `description` | 分析说明,如软件版本、过滤参数 | 用户录入 | 多行文本 | 可选,建议填写 |
|
||||
| `type` | 分析类型,如 SNP calling、imputation、filtering、chip genotyping | 用户选择/录入 | 下拉框/文本框 | 可选 |
|
||||
| `updated` | 更新时间 | 系统自动写入 | 只读日期时间 | 自动更新 |
|
||||
| `variant_set_id` | 所属 variantset | 从 variantset 选择 | VariantSet 选择器/隐藏 | 必选 |
|
||||
|
||||
### 录入建议
|
||||
|
||||
- 导入结果文件时,应生成或要求填写 analysis 信息。
|
||||
- 推荐记录:软件名称、版本、参数、过滤阈值、执行人、执行日期。
|
||||
|
||||
---
|
||||
|
||||
## 6.12 variantset_format 变异集合文件格式
|
||||
|
||||
### 业务说明
|
||||
|
||||
`variantset_format` 描述 variantset 数据文件格式,例如 VCF、HapMap、CSV、TSV、DArTSeq 等。它影响 genotype 的解析方式、缺失值、phased/unphased 分隔符等。
|
||||
|
||||
### 字段说明
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 控件建议 | 校验规则 |
|
||||
| -------------------- | --------------------------------------------- | ------------------- | ---------------------- | ------------------------ |
|
||||
| `id` | 格式记录主键 | 系统生成 | 隐藏/只读 | 必填、唯一 |
|
||||
| `data_format` | 数据内部结构,如 VCF、HapMap、DArTSeq、matrix | 用户选择/文件识别 | 下拉框 | 可选;建议枚举化 |
|
||||
| `expand_homozygotes` | 是否展开纯合 genotype,例如 A -> A/A | 用户选择 | 开关 | 可选 |
|
||||
| `file_format` | 文件 MIME 或外层格式,如 csv、tsv、zip、excel | 用户选择/文件识别 | 下拉框 | 可选 |
|
||||
| `fileurl` | 原始结果文件链接 | 上传后生成/用户填写 | URL 输入框 | 可选;URL 格式校验 |
|
||||
| `sep_phased` | phased genotype 分隔符,如 `|` | 用户录入/默认 | 文本框 | 可选;通常为 `|` |
|
||||
| `sep_unphased` | unphased genotype 分隔符,如 `/` | 用户录入/默认 | 文本框 | 可选;通常为 `/` |
|
||||
| `unknown_string` | 缺失值字符串,如 `./.`、NA、-- | 用户录入/默认 | 文本框 | 可选;导入时用于缺失识别 |
|
||||
| `variant_set_id` | 所属 variantset | 从 variantset 选择 | VariantSet 选择器/隐藏 | 必选 |
|
||||
|
||||
### 录入建议
|
||||
|
||||
- 文件上传时应自动识别 file_format 和 data_format。
|
||||
- 缺失值字符串必须参与 allele_call 导入解析。
|
||||
- 如果格式是 VCF,应支持 phased/unphased 分隔符。
|
||||
|
||||
---
|
||||
|
||||
## 6.13 genome_map 遗传图谱
|
||||
|
||||
### 业务说明
|
||||
|
||||
`genome_map` 描述遗传图谱或物理图谱。遗传图谱通常单位为 cM,物理图谱通常单位为 Mb 或 bp。它用于把 variant / marker 映射到 linkage group 上。
|
||||
|
||||
### 字段说明
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 控件建议 | 校验规则 |
|
||||
| ------------------ | ------------------------------ | ------------------ | ----------------- | ------------------------------------------ |
|
||||
| `id` | 图谱主键 | 系统生成 | 隐藏/只读 | 必填、唯一 |
|
||||
| `auth_user_id` | 数据所属用户或租户 | 登录上下文自动写入 | 隐藏 | 不允许手填 |
|
||||
| `comments` | 图谱备注 | 用户录入 | 多行文本 | 可选 |
|
||||
| `documentationurl` | 图谱说明文档或论文链接 | 用户录入 | URL 输入框 | 可选;URL 格式校验 |
|
||||
| `map_name` | 图谱名称 | 用户录入 | 文本框 | 必填;建议唯一 |
|
||||
| `mappui` | 图谱永久唯一标识 | 用户录入 | 文本框/URL 输入框 | 可选;若填写建议唯一 |
|
||||
| `published_date` | 发布时间 | 用户录入 | 日期选择器 | 可选 |
|
||||
| `scientific_name` | 物种学名 | 用户录入 | 文本框 | 可选 |
|
||||
| `type` | 图谱类型,如 Genetic、Physical | 用户选择 | 下拉框 | 可选;建议枚举 |
|
||||
| `unit` | 图谱单位,如 cM、Mb、bp | 用户选择 | 单位选择器 | 可选;Genetic 常用 cM,Physical 常用 Mb/bp |
|
||||
| `crop_id` | 所属作物 | 从 crop 选择 | Crop 选择器 | 必选 |
|
||||
|
||||
### 录入建议
|
||||
|
||||
- 图谱命名应能体现是 consensus map、mapping population map、reference genome map 还是 pan-genome map。
|
||||
- 图谱类型和单位必须匹配:Genetic + cM,Physical + Mb/bp。
|
||||
|
||||
---
|
||||
|
||||
## 6.14 linkage_group 连锁群
|
||||
|
||||
### 业务说明
|
||||
|
||||
`linkage_group` 是 genome_map 中的分组。它可能对应 chromosome、scaffold、contig 或传统遗传连锁群。
|
||||
|
||||
### 字段说明
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 控件建议 | 校验规则 |
|
||||
| --------------------- | -------------------------------------- | ------------------ | --------------------- | ---------------------------- |
|
||||
| `id` | 连锁群主键 | 系统生成 | 隐藏/只读 | 必填、唯一 |
|
||||
| `auth_user_id` | 数据所属用户或租户 | 登录上下文自动写入 | 隐藏 | 不允许手填 |
|
||||
| `linkage_group_name` | 连锁群名称,如 Chr1、LG1、scaffold_001 | 用户录入/导入 | 文本框 | 必填;同一 genome_map 下唯一 |
|
||||
| `max_marker_position` | 该连锁群最大 marker 位置 | 用户录入/导入计算 | 数字输入框 | 可选;非负数 |
|
||||
| `genome_map_id` | 所属 genome_map | 从 genome_map 选择 | GenomeMap 选择器/隐藏 | 必选 |
|
||||
|
||||
### 录入建议
|
||||
|
||||
- 在 genome_map 详情页维护。
|
||||
- 支持从 marker_position 文件批量导入时自动创建 linkage_group。
|
||||
- 若填写 max_marker_position,marker_position.position 不得超过该值。
|
||||
|
||||
---
|
||||
|
||||
## 6.15 marker_position 图谱位置
|
||||
|
||||
### 业务说明
|
||||
|
||||
`marker_position` 把 variant 映射到某个 linkage_group 上的位置。它用于遗传图谱定位、QTL、marker 辅助选择和图谱展示。
|
||||
|
||||
### 字段说明
|
||||
|
||||
| 字段 | 业务意义 | 录入方式 | 控件建议 | 校验规则 |
|
||||
| ------------------ | --------------------------------- | --------------------- | ------------------- | ---------------------------------------- |
|
||||
| `id` | 图谱位置主键 | 系统生成 | 隐藏/只读 | 必填、唯一 |
|
||||
| `auth_user_id` | 数据所属用户或租户 | 登录上下文自动写入 | 隐藏 | 不允许手填 |
|
||||
| `position` | variant 在 linkage_group 上的位置 | 用户录入/文件导入 | 数字输入框 | 必填;非负;不得超过 max_marker_position |
|
||||
| `linkage_group_id` | 所属 linkage_group | 从 linkage_group 选择 | LinkageGroup 选择器 | 必选 |
|
||||
| `variant_id` | 对应 variant | 从 variant 选择 | Variant 选择器 | 必选 |
|
||||
|
||||
### 录入建议
|
||||
|
||||
- 不建议逐条手工录入,优先通过 map 文件批量导入。
|
||||
- 同一 genome_map 下,同一 variant 不应重复映射,除非业务允许多图谱位置。
|
||||
|
||||
---
|
||||
|
||||
# 7. 跨表联动规则
|
||||
|
||||
| 场景 | 联动规则 |
|
||||
| ------------------------------- | ------------------------------------------------------------ |
|
||||
| 从 observation_unit 生成 sample | 自动带出 study、trial、program;可带出 germplasm 信息用于展示 |
|
||||
| 给 sample 分配 plate | 选择 well 后自动生成 plate_row、plate_column |
|
||||
| 创建 plate | 选择 study 后自动带出 trial/program |
|
||||
| 创建 variantset | 必须先选择 reference_set |
|
||||
| 导入 variant | variant.reference_set 必须与 variantset.reference_set 一致 |
|
||||
| 创建 callset | 必须选择 sample;可按 sample_name 自动生成 call_set_name |
|
||||
| 导入 allele_call | 先匹配 callset,再匹配 variant;匹配不到时进入错误报告 |
|
||||
| 绑定 callset_variant_sets | 导入 genotype matrix 时自动绑定 callset 与 variantset |
|
||||
| 创建 genome_map | 必须选择 crop |
|
||||
| 创建 marker_position | variant 与 linkage_group 最好来自同一 crop/reference 背景 |
|
||||
|
||||
---
|
||||
|
||||
# 8. 删除和停用规则
|
||||
|
||||
| 对象 | 删除限制 |
|
||||
| --------------- | ------------------------------------------------------- |
|
||||
| plate | 已有 sample 时不可删除 |
|
||||
| sample | 已有 callset 时不可删除 |
|
||||
| reference_set | 已有 reference、variantset、variant 时不可删除 |
|
||||
| reference | 已有 reference_bases 或 variant 引用时不可删除 |
|
||||
| variantset | 已有 variant、callset 绑定或 allele_call 结果时不可删除 |
|
||||
| variant | 已有 allele_call 或 marker_position 时不可删除 |
|
||||
| callset | 已有 allele_call 时不可删除 |
|
||||
| allele_call | 原始检测结果原则上不建议物理删除,应支持作废或版本化 |
|
||||
| genome_map | 已有 linkage_group 时不可删除 |
|
||||
| linkage_group | 已有 marker_position 时不可删除 |
|
||||
| marker_position | 可删除,但需记录操作日志 |
|
||||
|
||||
---
|
||||
|
||||
# 9. 批量导入要求
|
||||
|
||||
## 9.1 Sample 导入模板
|
||||
|
||||
必需列建议:
|
||||
|
||||
```text
|
||||
sample_name
|
||||
study_id 或 study_name
|
||||
```
|
||||
|
||||
强烈建议列:
|
||||
|
||||
```text
|
||||
observation_unit_id 或 observation_unit_name
|
||||
sample_barcode
|
||||
sample_type
|
||||
tissue_type
|
||||
sample_timestamp
|
||||
taken_by
|
||||
plate_name
|
||||
well
|
||||
```
|
||||
|
||||
导入规则:
|
||||
|
||||
1. 如果提供 observation_unit,系统自动带出 study/trial/program。
|
||||
2. 如果提供 plate + well,需校验同板孔位唯一。
|
||||
3. 如果 plate 不存在,可根据配置自动创建或报错。
|
||||
|
||||
## 9.2 Plate 导入模板
|
||||
|
||||
```text
|
||||
plate_name
|
||||
plate_barcode
|
||||
plate_format
|
||||
study_name
|
||||
sample_name
|
||||
well
|
||||
```
|
||||
|
||||
导入规则:
|
||||
|
||||
1. plate_format 决定合法 well 范围。
|
||||
2. 同一 plate + well 不允许重复。
|
||||
3. sample_name 不存在时,可配置是否自动创建 sample。
|
||||
|
||||
## 9.3 Variant 导入模板
|
||||
|
||||
建议支持 VCF / HapMap / CSV。
|
||||
|
||||
CSV 必需列建议:
|
||||
|
||||
```text
|
||||
variant_name
|
||||
reference_name
|
||||
variant_start
|
||||
reference_bases
|
||||
alternate_bases
|
||||
variant_type
|
||||
variant_set_name
|
||||
reference_set_name
|
||||
```
|
||||
|
||||
导入规则:
|
||||
|
||||
1. 必须先选择或创建 reference_set。
|
||||
2. CHROM / reference_name 必须能匹配 reference。
|
||||
3. variant_start 必须非负。
|
||||
4. alternate_bases 不能为空,除非只是导入传统 marker。
|
||||
|
||||
## 9.4 Genotype Matrix / Allele Call 导入模板
|
||||
|
||||
推荐矩阵格式:
|
||||
|
||||
```text
|
||||
variant_name,SAMPLE-001,SAMPLE-002,SAMPLE-003
|
||||
SNP001,A/G,A/A,./.
|
||||
SNP002,C/C,C/T,C/C
|
||||
SNP003,T/G,T/T,T/G
|
||||
```
|
||||
|
||||
导入规则:
|
||||
|
||||
1. 每个样本列必须能匹配 sample 或 callset。
|
||||
2. 每个 variant 行必须能匹配 variant。
|
||||
3. 每个非空 genotype 单元格生成一条 allele_call。
|
||||
4. 缺失值按 variantset_format.unknown_string 处理。
|
||||
5. 同一 callset + variant 已存在时,根据导入策略选择覆盖、跳过或报错。
|
||||
|
||||
## 9.5 Genome Map 导入模板
|
||||
|
||||
```text
|
||||
map_name
|
||||
linkage_group_name
|
||||
variant_name
|
||||
position
|
||||
unit
|
||||
```
|
||||
|
||||
导入规则:
|
||||
|
||||
1. map_name 不存在时可配置自动创建 genome_map。
|
||||
2. linkage_group 不存在时可配置自动创建。
|
||||
3. variant 必须存在。
|
||||
4. position 必须非负且不超过 max_marker_position。
|
||||
|
||||
---
|
||||
|
||||
# 10. 后端接口建议
|
||||
|
||||
## 10.1 主数据接口
|
||||
|
||||
```text
|
||||
GET /plates
|
||||
POST /plates
|
||||
GET /plates/{id}
|
||||
PUT /plates/{id}
|
||||
GET /plates/{id}/layout
|
||||
POST /plates/{id}/assign-samples
|
||||
|
||||
GET /samples
|
||||
POST /samples
|
||||
POST /samples/batch-from-observation-units
|
||||
GET /samples/{id}
|
||||
PUT /samples/{id}
|
||||
GET /samples/{id}/callsets
|
||||
|
||||
GET /reference-sets
|
||||
POST /reference-sets
|
||||
GET /reference-sets/{id}
|
||||
GET /reference-sets/{id}/references
|
||||
POST /reference-sets/{id}/references
|
||||
|
||||
GET /references
|
||||
POST /references
|
||||
GET /references/{id}/bases
|
||||
POST /references/{id}/bases/import
|
||||
|
||||
GET /variantsets
|
||||
POST /variantsets
|
||||
GET /variantsets/{id}
|
||||
GET /variantsets/{id}/variants
|
||||
POST /variantsets/{id}/variants/import
|
||||
GET /variantsets/{id}/formats
|
||||
POST /variantsets/{id}/formats
|
||||
GET /variantsets/{id}/analyses
|
||||
POST /variantsets/{id}/analyses
|
||||
|
||||
GET /variants
|
||||
POST /variants
|
||||
GET /variants/{id}
|
||||
|
||||
GET /callsets
|
||||
POST /callsets
|
||||
GET /callsets/{id}
|
||||
POST /callsets/{id}/variantsets
|
||||
|
||||
GET /allele-calls
|
||||
POST /allele-calls/import
|
||||
GET /allele-matrix
|
||||
|
||||
GET /genome-maps
|
||||
POST /genome-maps
|
||||
GET /genome-maps/{id}/linkage-groups
|
||||
POST /genome-maps/{id}/linkage-groups
|
||||
POST /marker-positions/import
|
||||
```
|
||||
|
||||
## 10.2 选择器接口
|
||||
|
||||
```text
|
||||
GET /selectors/plates?studyId=&keyword=
|
||||
GET /selectors/samples?studyId=&plateId=&keyword=
|
||||
GET /selectors/reference-sets?cropId=&keyword=
|
||||
GET /selectors/references?referenceSetId=&keyword=
|
||||
GET /selectors/variantsets?referenceSetId=&studyId=&keyword=
|
||||
GET /selectors/variants?variantSetId=&referenceName=&start=&end=&keyword=
|
||||
GET /selectors/callsets?sampleId=&variantSetId=&keyword=
|
||||
GET /selectors/genome-maps?cropId=&keyword=
|
||||
GET /selectors/linkage-groups?genomeMapId=&keyword=
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 11. 测试验收清单
|
||||
|
||||
1. 创建 plate 时,plate_name 和 plate_barcode 至少填写一个。
|
||||
2. plate_barcode 重复时提示唯一性冲突。
|
||||
3. plate_format=96 时,只允许 A01-H12;plate_format=384 时,只允许 A01-P24。
|
||||
4. sample_name 必填。
|
||||
5. sample 绑定 plate 时,同一 plate + well 不允许重复。
|
||||
6. sample 绑定 observation_unit 后,study/trial/program 自动带出。
|
||||
7. reference_set_name 必填。
|
||||
8. reference_name 在同一 reference_set 内不允许重复。
|
||||
9. reference.length 必须为非负整数。
|
||||
10. reference_bases.bases 只能包含合法碱基字符。
|
||||
11. variantset 必须选择 reference_set。
|
||||
12. variant.reference_set_id 必须与 variantset.reference_set_id 一致。
|
||||
13. variant_start 必须非负。
|
||||
14. variant_end 不得小于 variant_start。
|
||||
15. variant_name 为空时系统能按 reference + position + allele 自动生成。
|
||||
16. callset 必须绑定 sample。
|
||||
17. 同一 sample 下 call_set_name 不建议重复。
|
||||
18. callset + variantset 关系不允许重复。
|
||||
19. allele_call 必须绑定 callset 和 variant。
|
||||
20. 同一 callset + variant 不允许重复,除非系统支持版本化。
|
||||
21. read_depth 必须为非负整数。
|
||||
22. genotype 必须符合导入格式规则。
|
||||
23. 缺失 genotype 必须按 unknown_string 统一处理。
|
||||
24. 导入 genotype matrix 时,无法匹配的 sample、variant 必须返回错误报告。
|
||||
25. 导入失败报告必须包含行号、列名、错误原因、建议修复方式。
|
||||
26. genome_map 必须选择 crop。
|
||||
27. linkage_group_name 在同一 genome_map 内不允许重复。
|
||||
28. marker_position.position 必须非负,且不超过 max_marker_position。
|
||||
29. 已有关联下游数据的 sample、variant、callset、variantset 不允许物理删除。
|
||||
30. 所有导入操作必须记录导入批次、上传人、上传时间和原始文件链接。
|
||||
|
||||
---
|
||||
|
||||
# 12. 开发实现重点
|
||||
|
||||
1. `sample` 是物理样本,不是基因型结果。
|
||||
2. `callset` 是 sample 参与一次检测/分析形成的结果集合。
|
||||
3. `allele_call` 才是某个 sample/callset 在某个 variant 上的 genotype 结果。
|
||||
4. `variantset` 必须绑定 reference_set,否则位点坐标没有意义。
|
||||
5. 大量 variant 和 allele_call 不应手工录入,主入口必须是文件导入。
|
||||
6. plate 必须做孔位布局,不要只做普通表单。
|
||||
7. genotype matrix 页面必须支持按 sample 和按 variant 两种方向查看。
|
||||
8. VCF/HapMap/CSV 的解析规则要独立成导入服务,不要散落在页面逻辑里。
|
||||
9. reference_set、variantset、callset、analysis、format 都是结果可追溯的关键上下文。
|
||||
10. 已导入的基因型结果建议支持版本化或作废,不建议直接物理删除。
|
||||
|
||||
3
frontend/.eslintrc.json
Normal file
3
frontend/.eslintrc.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": ["next/core-web-vitals", "next/typescript"]
|
||||
}
|
||||
23
frontend/README.md
Normal file
23
frontend/README.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# Frontend
|
||||
|
||||
## Start
|
||||
|
||||
```bash
|
||||
cd frontend
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Default URL: http://localhost:3010
|
||||
|
||||
## Routes
|
||||
|
||||
- /imports
|
||||
- /imports/new
|
||||
- /imports/[taskId]/mapping
|
||||
- /imports/[taskId]/validate
|
||||
- /imports/[taskId]/execute
|
||||
- /imports/[taskId]/errors
|
||||
- /imports/[taskId]/detail
|
||||
- /imports/rollback
|
||||
- /imports/history
|
||||
BIN
frontend/assets/main_page.png
Normal file
BIN
frontend/assets/main_page.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.9 MiB |
21
frontend/components.json
Normal file
21
frontend/components.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "default",
|
||||
"rsc": false,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "",
|
||||
"css": "src/styles/globals.css",
|
||||
"baseColor": "neutral",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"iconLibrary": "lucide",
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils",
|
||||
"ui": "@/components/ui",
|
||||
"lib": "@/lib",
|
||||
"hooks": "@/hooks"
|
||||
}
|
||||
}
|
||||
6
frontend/next-env.d.ts
vendored
Normal file
6
frontend/next-env.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
import "./.next/dev/types/routes.d.ts";
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
22
frontend/next.config.js
Normal file
22
frontend/next.config.js
Normal file
@@ -0,0 +1,22 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
async rewrites() {
|
||||
const apiBaseUrl = process.env.API_BASE_URL || "http://localhost:8081";
|
||||
return [
|
||||
{
|
||||
source: "/auth/:path*",
|
||||
destination: `${apiBaseUrl}/auth/:path*`,
|
||||
},
|
||||
{
|
||||
source: "/brapi/v2/:path*",
|
||||
destination: `${apiBaseUrl}/brapi/v2/:path*`,
|
||||
},
|
||||
{
|
||||
source: "/api/:path*",
|
||||
destination: `${apiBaseUrl}/api/:path*`,
|
||||
},
|
||||
];
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = nextConfig;
|
||||
37
frontend/openapi-ts.config.ts
Normal file
37
frontend/openapi-ts.config.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { defineConfig } from "@hey-api/openapi-ts";
|
||||
import { config } from "dotenv";
|
||||
import { existsSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
|
||||
const envPath = resolve(process.cwd(), ".env");
|
||||
if (existsSync(envPath)) {
|
||||
config({ path: envPath });
|
||||
}
|
||||
|
||||
const envLocalPath = resolve(process.cwd(), ".env.local");
|
||||
if (existsSync(envLocalPath)) {
|
||||
config({ path: envLocalPath });
|
||||
}
|
||||
|
||||
const apiBaseUrl =
|
||||
process.env.API_BASE_URL ||
|
||||
process.env.NEXT_PUBLIC_API_BASE_URL ||
|
||||
"http://localhost:8081";
|
||||
|
||||
const openApiUrl =
|
||||
process.env.OPENAPI_URL ||
|
||||
process.env.REACT_APP_OPENAPI_URL ||
|
||||
`${apiBaseUrl.replace(/\/$/, "")}/brapi/v2/openapi.json`;
|
||||
|
||||
export default defineConfig({
|
||||
client: "@hey-api/client-fetch",
|
||||
input: openApiUrl,
|
||||
output: "./src/lib/api",
|
||||
schemas: {
|
||||
name: "types.gen.ts"
|
||||
},
|
||||
services: {
|
||||
name: "sdk.gen.ts"
|
||||
},
|
||||
clientName: "client.gen.ts"
|
||||
});
|
||||
8394
frontend/package-lock.json
generated
Normal file
8394
frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
61
frontend/package.json
Normal file
61
frontend/package.json
Normal file
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"name": "agri-data-standard-ui",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev -p 3010",
|
||||
"build": "next build",
|
||||
"start": "next start -p 3010",
|
||||
"lint": "next lint",
|
||||
"api:generate": "node scripts/generate-api.cjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hey-api/openapi-ts": "^0.87.1",
|
||||
"@hookform/resolvers": "^4.1.3",
|
||||
"@radix-ui/react-accordion": "^1.2.12",
|
||||
"@radix-ui/react-alert-dialog": "^1.1.15",
|
||||
"@radix-ui/react-checkbox": "^1.3.3",
|
||||
"@radix-ui/react-collapsible": "^1.1.12",
|
||||
"@radix-ui/react-dialog": "^1.1.15",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
||||
"@radix-ui/react-label": "^2.1.8",
|
||||
"@radix-ui/react-popover": "^1.1.15",
|
||||
"@radix-ui/react-radio-group": "^1.3.8",
|
||||
"@radix-ui/react-select": "^2.2.6",
|
||||
"@radix-ui/react-separator": "^1.1.8",
|
||||
"@radix-ui/react-slot": "^1.2.4",
|
||||
"@radix-ui/react-switch": "^1.2.6",
|
||||
"@radix-ui/react-tabs": "^1.1.13",
|
||||
"@radix-ui/react-tooltip": "^1.2.8",
|
||||
"@tanstack/react-table": "^8.21.2",
|
||||
"axios": "^1.8.4",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.1.1",
|
||||
"date-fns": "^4.2.1",
|
||||
"lucide-react": "^0.475.0",
|
||||
"next": "^16.2.6",
|
||||
"next-themes": "^0.4.6",
|
||||
"openapi-fetch": "^0.15.0",
|
||||
"react": "19.1.0",
|
||||
"react-day-picker": "^10.0.1",
|
||||
"react-dom": "19.1.0",
|
||||
"react-hook-form": "^7.76.0",
|
||||
"sonner": "^2.0.7",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"zod": "^3.25.76",
|
||||
"zustand": "5.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/postcss": "^4.1.11",
|
||||
"@types/node": "22.15.21",
|
||||
"@types/react": "19.1.2",
|
||||
"@types/react-dom": "19.1.2",
|
||||
"dotenv": "^16.4.7",
|
||||
"eslint": "9.27.0",
|
||||
"eslint-config-next": "15.3.2",
|
||||
"tailwindcss": "^4.1.11",
|
||||
"typescript": "5.8.3"
|
||||
}
|
||||
}
|
||||
5
frontend/postcss.config.js
Normal file
5
frontend/postcss.config.js
Normal file
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
"@tailwindcss/postcss": {},
|
||||
},
|
||||
};
|
||||
42
frontend/scripts/generate-api.cjs
Normal file
42
frontend/scripts/generate-api.cjs
Normal file
@@ -0,0 +1,42 @@
|
||||
const { execSync } = require("node:child_process");
|
||||
const { existsSync } = require("node:fs");
|
||||
const path = require("node:path");
|
||||
const dotenv = require("dotenv");
|
||||
|
||||
dotenv.config({ path: ".env" });
|
||||
dotenv.config({ path: ".env.local", override: true });
|
||||
|
||||
const apiBaseUrl =
|
||||
process.env.API_BASE_URL ||
|
||||
process.env.NEXT_PUBLIC_API_BASE_URL ||
|
||||
"http://localhost:8081";
|
||||
|
||||
const openApiUrl =
|
||||
process.env.OPENAPI_URL ||
|
||||
process.env.REACT_APP_OPENAPI_URL ||
|
||||
`${apiBaseUrl.replace(/\/$/, "")}/brapi/v2/openapi.json`;
|
||||
|
||||
process.env.API_BASE_URL = apiBaseUrl;
|
||||
if (!process.env.OPENAPI_URL && !process.env.REACT_APP_OPENAPI_URL) {
|
||||
process.env.OPENAPI_URL = openApiUrl;
|
||||
}
|
||||
|
||||
console.log("API_BASE_URL:", apiBaseUrl);
|
||||
console.log("OPENAPI_URL:", openApiUrl);
|
||||
console.log("开始生成 OpenAPI 客户端...");
|
||||
|
||||
execSync("npx @hey-api/openapi-ts", {
|
||||
stdio: "inherit",
|
||||
env: process.env
|
||||
});
|
||||
|
||||
const apiDir = path.resolve(process.cwd(), "src/lib/api");
|
||||
const required = ["types.gen.ts", "sdk.gen.ts", "index.ts", "client.gen.ts"];
|
||||
const missing = required.filter((file) => !existsSync(path.join(apiDir, file)));
|
||||
|
||||
if (missing.length > 0) {
|
||||
console.error("生成失败,缺失文件:", missing.join(", "));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log("OpenAPI 客户端生成完成:src/lib/api");
|
||||
@@ -0,0 +1,5 @@
|
||||
import { PageHeader } from "@/components/common/PageHeader";
|
||||
|
||||
export default function CatalogPage() {
|
||||
return <PageHeader title="资产目录" description="三级菜单页面占位,一级与二级菜单目前为示例占位结构。" />;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { PageHeader } from "@/components/common/PageHeader";
|
||||
|
||||
export default function SupplierPage() {
|
||||
return <PageHeader title="供应商档案" description="三级菜单页面占位,一级与二级菜单目前为示例占位结构。" />;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { FormEvent } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import type { CropFormValues } from "./types";
|
||||
|
||||
interface CropFormProps {
|
||||
values: CropFormValues;
|
||||
submitting: boolean;
|
||||
onChange: (key: keyof CropFormValues, value: string) => void;
|
||||
onSubmit: (event: FormEvent<HTMLFormElement>) => void;
|
||||
}
|
||||
|
||||
export function CropForm({ values, submitting, onChange, onSubmit }: CropFormProps) {
|
||||
return (
|
||||
<form className="card-agriculture grid gap-5 p-5 md:p-6" onSubmit={onSubmit}>
|
||||
<div className="flex items-start gap-3">
|
||||
<span className="grid h-10 w-10 shrink-0 place-items-center rounded-2xl bg-secondary text-sm font-black text-secondary-foreground">01</span>
|
||||
<div>
|
||||
<h2 className="text-xl font-black tracking-tight text-card-foreground">新增作物</h2>
|
||||
<p className="mt-1 text-sm leading-6 text-muted-foreground">填写作物名称即可写入数据库,创建用户将由后端从当前登录态自动写入。</p>
|
||||
</div>
|
||||
</div>
|
||||
<label className="grid gap-2 text-sm font-black text-card-foreground">
|
||||
<span>作物名称</span>
|
||||
<input className="input-agriculture" value={values.cropName} onChange={(event) => onChange("cropName", event.target.value)} placeholder="例如:maize / rice / wheat" />
|
||||
</label>
|
||||
<Button variant="agriculture" size="lg" type="submit" disabled={submitting || !values.cropName.trim()}>
|
||||
{submitting ? "正在写入 crop 表..." : "保存作物"}
|
||||
</Button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
interface CropGuideProps {
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
export function CropGuide({ error }: CropGuideProps) {
|
||||
return (
|
||||
<div className="card-agriculture p-5 md:p-6">
|
||||
<h3 className="text-lg font-black text-card-foreground">录入规则</h3>
|
||||
<ul className="mt-4 grid gap-3 text-sm leading-6 text-muted-foreground">
|
||||
<li className="rounded-2xl bg-muted px-4 py-3">作物名称建议使用标准英文名或平台统一编码。</li>
|
||||
<li className="rounded-2xl bg-muted px-4 py-3">同一作物后续会关联 Program、Trial、Study。</li>
|
||||
<li className="rounded-2xl bg-muted px-4 py-3">保存后会立即刷新右侧列表,确认接口已经写入。</li>
|
||||
</ul>
|
||||
{error ? <div className="mt-4 rounded-2xl border border-destructive/20 bg-destructive/10 px-4 py-3 text-sm font-bold text-destructive">{error}</div> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
const quickCrops = ["rice", "maize", "wheat", "soybean", "cotton"];
|
||||
|
||||
interface CropHeaderProps {
|
||||
total: number;
|
||||
loading: boolean;
|
||||
onSelectQuickCrop: (name: string) => void;
|
||||
}
|
||||
|
||||
export function CropHeader({ total, loading, onSelectQuickCrop }: CropHeaderProps) {
|
||||
return (
|
||||
<div className="relative grid gap-6 overflow-hidden rounded-[34px] bg-[radial-gradient(circle_at_12%_15%,rgba(187,247,208,0.94),transparent_28%),linear-gradient(135deg,var(--primary),#0f766e_48%,#84cc16)] p-6 text-primary-foreground shadow-agriculture lg:grid-cols-[1fr_240px] lg:p-8">
|
||||
<div className="pointer-events-none absolute -right-16 -top-16 h-56 w-56 rounded-full bg-primary-foreground/15 blur-sm" />
|
||||
<div className="relative z-10 max-w-3xl">
|
||||
<div className="mb-4 inline-flex rounded-full border border-primary-foreground/25 bg-primary-foreground/15 px-3 py-1 text-xs font-black uppercase tracking-[0.2em]">
|
||||
农业基础字典入口 / Crop
|
||||
</div>
|
||||
<h1 className="text-3xl font-black tracking-tight md:text-5xl">作物字典录入</h1>
|
||||
<p className="mt-4 max-w-2xl text-sm leading-7 text-primary-foreground/90 md:text-base">
|
||||
维护 BrAPI 标准 <strong className="font-black">crop</strong> 表。这里录入的作物会成为 Program、Trial、Study、Germplasm 等数据的基础入口。
|
||||
</p>
|
||||
<div className="mt-5 flex flex-wrap gap-2">
|
||||
{quickCrops.map((name) => (
|
||||
<Button key={name} type="button" variant="outline" className="rounded-full border-primary-foreground/20 bg-primary-foreground/15 text-primary-foreground hover:bg-primary-foreground/25" onClick={() => onSelectQuickCrop(name)}>
|
||||
{name}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative z-10 flex min-h-44 flex-col justify-between rounded-3xl border border-primary-foreground/20 bg-primary-foreground/15 p-5 shadow-2xl shadow-emerald-950/15 backdrop-blur">
|
||||
<span className="text-sm font-black text-primary-foreground/85">当前作物</span>
|
||||
<strong className="text-6xl font-black leading-none">{total}</strong>
|
||||
<em className="not-italic text-sm font-bold text-primary-foreground/85">{loading ? "正在同步数据库..." : "已连接 crop 表"}</em>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import type { CropItem } from "./types";
|
||||
|
||||
interface CropTableProps {
|
||||
rows: CropItem[];
|
||||
loading: boolean;
|
||||
onRefresh: () => void;
|
||||
}
|
||||
|
||||
export function CropTable({ rows, loading, onRefresh }: CropTableProps) {
|
||||
return (
|
||||
<div className="card-agriculture overflow-hidden">
|
||||
<div className="flex flex-col gap-3 border-b border-border p-5 md:flex-row md:items-center md:justify-between md:p-6">
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="grid h-10 w-10 place-items-center rounded-2xl bg-accent text-sm font-black text-accent-foreground">02</span>
|
||||
<h2 className="text-xl font-black tracking-tight text-card-foreground">Crop 数据列表</h2>
|
||||
</div>
|
||||
<Button variant="agriculture" type="button" onClick={onRefresh}>{loading ? "刷新中..." : "刷新数据"}</Button>
|
||||
</div>
|
||||
{loading ? (
|
||||
<div className="p-8 text-center text-sm font-bold text-muted-foreground">正在从后端读取 crop 表...</div>
|
||||
) : rows.length > 0 ? (
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full border-collapse text-left text-sm">
|
||||
<thead className="bg-muted text-xs uppercase tracking-[0.16em] text-muted-foreground">
|
||||
<tr>
|
||||
<th className="px-5 py-4 font-black">ID</th>
|
||||
<th className="px-5 py-4 font-black">作物名称</th>
|
||||
<th className="px-5 py-4 font-black">创建用户</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-border">
|
||||
{rows.map((row) => (
|
||||
<tr key={row.id} className="transition hover:bg-muted/70">
|
||||
<td className="px-5 py-4 text-muted-foreground">{row.id}</td>
|
||||
<td className="px-5 py-4"><strong className="font-black text-card-foreground">{row.crop_name || "-"}</strong></td>
|
||||
<td className="px-5 py-4 text-muted-foreground">{row.user_name || "-"}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
) : (
|
||||
<div className="p-8 text-center text-sm font-bold text-muted-foreground">数据库里还没有作物。先在上方新增一个,比如 maize。</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { createCrop, listCrops } from "@/services/dictionaryService";
|
||||
import type { CropFormValues, CropItem } from "./types";
|
||||
|
||||
export async function fetchCropList(): Promise<CropItem[]> {
|
||||
return listCrops();
|
||||
}
|
||||
|
||||
export async function saveCrop(values: CropFormValues): Promise<CropItem> {
|
||||
return createCrop({
|
||||
crop_name: values.cropName.trim(),
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import type { CropPageState } from "./types";
|
||||
|
||||
export type CropPageAction =
|
||||
| { type: "SET_ROWS"; payload: CropPageState["rows"] }
|
||||
| { type: "SET_FORM_FIELD"; payload: { key: keyof CropPageState["form"]; value: string } }
|
||||
| { type: "RESET_FORM" }
|
||||
| { type: "SET_LOADING"; payload: boolean }
|
||||
| { type: "SET_SUBMITTING"; payload: boolean }
|
||||
| { type: "SET_ERROR"; payload: string | null };
|
||||
|
||||
export function cropReducer(state: CropPageState, action: CropPageAction): CropPageState {
|
||||
switch (action.type) {
|
||||
case "SET_ROWS":
|
||||
return { ...state, rows: action.payload };
|
||||
case "SET_FORM_FIELD":
|
||||
return { ...state, form: { ...state.form, [action.payload.key]: action.payload.value } };
|
||||
case "RESET_FORM":
|
||||
return { ...state, form: { cropName: "" } };
|
||||
case "SET_LOADING":
|
||||
return { ...state, loading: action.payload };
|
||||
case "SET_SUBMITTING":
|
||||
return { ...state, submitting: action.payload };
|
||||
case "SET_ERROR":
|
||||
return { ...state, error: action.payload };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import type { CropRecord } from "@/services/dictionaryService";
|
||||
|
||||
export type CropItem = CropRecord;
|
||||
|
||||
export interface CropFormValues {
|
||||
cropName: string;
|
||||
}
|
||||
|
||||
export interface CropPageState {
|
||||
rows: CropItem[];
|
||||
form: CropFormValues;
|
||||
loading: boolean;
|
||||
submitting: boolean;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
export const initialCropState: CropPageState = {
|
||||
rows: [],
|
||||
form: {
|
||||
cropName: "",
|
||||
},
|
||||
loading: true,
|
||||
submitting: false,
|
||||
error: null,
|
||||
};
|
||||
43
frontend/src/app/(app)/basic-dictionary/base/crop/page.tsx
Normal file
43
frontend/src/app/(app)/basic-dictionary/base/crop/page.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
"use client";
|
||||
|
||||
import { Leaf } from "lucide-react";
|
||||
import { BrapiEntityPage } from "@/components/brapi/BrapiEntityPage";
|
||||
import { createCrop, deleteCrop, listCrops, updateCrop } from "@/services/dictionaryService";
|
||||
|
||||
const loadCropRows = async () => listCrops() as unknown as Record<string, unknown>[];
|
||||
|
||||
const createCropRow = async (payload: Record<string, unknown>) => createCrop({
|
||||
crop_name: String(payload.crop_name ?? ""),
|
||||
}) as unknown as Record<string, unknown>;
|
||||
|
||||
const updateCropRow = async (id: string, payload: Record<string, unknown>) => updateCrop(id, {
|
||||
crop_name: String(payload.crop_name ?? ""),
|
||||
}) as unknown as Record<string, unknown>;
|
||||
|
||||
const deleteCropRow = async (id: string) => {
|
||||
await deleteCrop(id);
|
||||
};
|
||||
|
||||
export default function CropDictionaryPage() {
|
||||
return (
|
||||
<BrapiEntityPage
|
||||
icon={Leaf}
|
||||
iconBg="bg-gradient-to-br from-amber-500 to-orange-600"
|
||||
title="Crop 作物"
|
||||
description="多作物平台入口,所有业务对象均归属于特定作物"
|
||||
addLabel="新增作物"
|
||||
columns={[
|
||||
{ key: "crop_name", label: "作物名称" },
|
||||
]}
|
||||
fields={[
|
||||
{ key: "crop_name", label: "作物名称 (Crop Name)", type: "text", required: true, placeholder: "如 Maize、Rice、Wheat" },
|
||||
]}
|
||||
data={[]}
|
||||
stats={[{ label: "后端 crop 表", value: "API", className: "bg-amber-50 text-amber-700 dark:bg-amber-400/10 dark:text-amber-200" }]}
|
||||
loadData={loadCropRows}
|
||||
createRecord={createCropRow}
|
||||
updateRecord={updateCropRow}
|
||||
deleteRecord={deleteCropRow}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useParams } from "next/navigation";
|
||||
import { ArrowLeft, List, Pencil } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { fetchListDetail, fetchPersonOptions } from "../api";
|
||||
import { ListItemPanel } from "../components/ListItemPanel";
|
||||
import { ListMetaDialog } from "../components/ListMetaDialog";
|
||||
import { listTypeLabel, type ListRecord, type SelectOption } from "../types";
|
||||
|
||||
function formatTimestamp(value: string | null | undefined) {
|
||||
if (!value) return "—";
|
||||
const date = new Date(value);
|
||||
if (Number.isNaN(date.getTime())) return value;
|
||||
return date.toLocaleString();
|
||||
}
|
||||
|
||||
export default function ListDetailPage() {
|
||||
const params = useParams<{ listDbId: string }>();
|
||||
const listDbId = decodeURIComponent(params.listDbId ?? "");
|
||||
const [record, setRecord] = useState<ListRecord | null>(null);
|
||||
const [personOptions, setPersonOptions] = useState<SelectOption[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [editOpen, setEditOpen] = useState(false);
|
||||
|
||||
const loadDetail = useCallback(async () => {
|
||||
if (!listDbId) return;
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const [people, detail] = await Promise.all([fetchPersonOptions(), fetchListDetail(listDbId)]);
|
||||
setPersonOptions(people);
|
||||
setRecord(detail);
|
||||
} catch (event) {
|
||||
setError(event instanceof Error ? event.message : "加载失败");
|
||||
setRecord(null);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [listDbId]);
|
||||
|
||||
useEffect(() => {
|
||||
let mounted = true;
|
||||
loadDetail().catch(() => undefined);
|
||||
return () => {
|
||||
mounted = false;
|
||||
};
|
||||
}, [loadDetail]);
|
||||
|
||||
const handleItemsChange = useCallback((items: string[]) => {
|
||||
setRecord((current) => (current ? { ...current, data: items, listSize: items.length } : current));
|
||||
}, []);
|
||||
|
||||
if (!listDbId) {
|
||||
return <div className="p-6 text-sm text-destructive">缺少列表 ID</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex min-h-full flex-col">
|
||||
<div className="mb-5 flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
|
||||
<div className="flex items-start gap-3">
|
||||
<Button variant="outline" size="icon" className="shrink-0" asChild>
|
||||
<Link href="/basic-dictionary/base/list" aria-label="返回列表">
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
</Link>
|
||||
</Button>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="rounded-xl bg-gradient-to-br from-violet-500 to-purple-600 p-2.5">
|
||||
<List className="h-5 w-5 text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold text-slate-900 dark:text-slate-50">
|
||||
{record?.listName || "List 详情"}
|
||||
</h2>
|
||||
<p className="mt-0.5 text-sm text-slate-500 dark:text-slate-400">
|
||||
维护列表基本信息与 list_item 明细
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{record ? (
|
||||
<Button variant="outline" className="gap-2" onClick={() => setEditOpen(true)}>
|
||||
<Pencil className="h-4 w-4" />
|
||||
编辑基本信息
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{error ? (
|
||||
<div className="mb-4 rounded-xl border border-destructive/20 bg-destructive/10 px-4 py-3 text-sm text-destructive">
|
||||
{error}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="mb-5 rounded-xl border border-slate-200 bg-white p-4 dark:border-slate-800 dark:bg-slate-950">
|
||||
{loading ? (
|
||||
<div className="grid gap-3 md:grid-cols-2">
|
||||
{Array.from({ length: 6 }).map((_, index) => (
|
||||
<Skeleton key={index} className="h-5 w-full" />
|
||||
))}
|
||||
</div>
|
||||
) : record ? (
|
||||
<dl className="grid gap-3 text-sm md:grid-cols-2">
|
||||
<div>
|
||||
<dt className="text-xs text-slate-500 dark:text-slate-400">列表 ID</dt>
|
||||
<dd className="mt-0.5 break-all font-medium text-slate-800 dark:text-slate-100">{record.id}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-xs text-slate-500 dark:text-slate-400">列表类型</dt>
|
||||
<dd className="mt-0.5 text-slate-800 dark:text-slate-100">{listTypeLabel(record.listType)}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-xs text-slate-500 dark:text-slate-400">Owner</dt>
|
||||
<dd className="mt-0.5 text-slate-800 dark:text-slate-100">{record.listOwnerName || record.listOwnerPersonDbId || "—"}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-xs text-slate-500 dark:text-slate-400">来源</dt>
|
||||
<dd className="mt-0.5 text-slate-800 dark:text-slate-100">{record.listSource || "—"}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-xs text-slate-500 dark:text-slate-400">创建时间</dt>
|
||||
<dd className="mt-0.5 text-slate-800 dark:text-slate-100">{formatTimestamp(record.dateCreated)}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-xs text-slate-500 dark:text-slate-400">修改时间</dt>
|
||||
<dd className="mt-0.5 text-slate-800 dark:text-slate-100">{formatTimestamp(record.dateModified)}</dd>
|
||||
</div>
|
||||
<div className="md:col-span-2">
|
||||
<dt className="text-xs text-slate-500 dark:text-slate-400">描述</dt>
|
||||
<dd className="mt-0.5 whitespace-pre-wrap text-slate-800 dark:text-slate-100">{record.listDescription || "—"}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
) : (
|
||||
<p className="text-sm text-slate-400">未找到列表数据</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{record ? (
|
||||
<ListItemPanel
|
||||
listDbId={listDbId}
|
||||
items={record.data ?? []}
|
||||
onItemsChange={handleItemsChange}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{record ? (
|
||||
<ListMetaDialog
|
||||
open={editOpen}
|
||||
record={record}
|
||||
personOptions={personOptions}
|
||||
onOpenChange={setEditOpen}
|
||||
onSaved={setRecord}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
205
frontend/src/app/(app)/basic-dictionary/base/list/api.ts
Normal file
205
frontend/src/app/(app)/basic-dictionary/base/list/api.ts
Normal file
@@ -0,0 +1,205 @@
|
||||
import { getAuthToken } from "@/utils/token";
|
||||
import type { ListDetails, ListNewRequest, ListSummary } from "@/lib/api/types.gen";
|
||||
import { NONE_SELECT_VALUE, type ListRecord, type ListType, type SelectOption } from "./types";
|
||||
|
||||
interface BrapiPagination {
|
||||
currentPage: number;
|
||||
pageSize: number;
|
||||
totalCount: number;
|
||||
totalPages: number;
|
||||
}
|
||||
|
||||
interface BrapiListResponse<T> {
|
||||
metadata: {
|
||||
pagination: BrapiPagination;
|
||||
status: Array<Record<string, unknown>>;
|
||||
datafiles: Array<Record<string, unknown>>;
|
||||
};
|
||||
result: {
|
||||
data: T[];
|
||||
};
|
||||
}
|
||||
|
||||
interface BrapiSingleResponse<T> {
|
||||
metadata: {
|
||||
pagination: BrapiPagination;
|
||||
status: Array<Record<string, unknown>>;
|
||||
datafiles: Array<Record<string, unknown>>;
|
||||
};
|
||||
result: T;
|
||||
}
|
||||
|
||||
interface PersonResponse {
|
||||
personDbId: string;
|
||||
firstName?: string | null;
|
||||
lastName?: string | null;
|
||||
emailAddress?: string | null;
|
||||
}
|
||||
|
||||
type ListPayload = Partial<Record<
|
||||
| "listName"
|
||||
| "list_name"
|
||||
| "listType"
|
||||
| "list_type"
|
||||
| "listDescription"
|
||||
| "list_description"
|
||||
| "listSource"
|
||||
| "list_source"
|
||||
| "listOwnerName"
|
||||
| "list_owner_name"
|
||||
| "listOwnerPersonDbId"
|
||||
| "list_owner_person_id"
|
||||
| "data",
|
||||
unknown
|
||||
>>;
|
||||
|
||||
const apiBase = () => {
|
||||
if (typeof window !== "undefined") return "";
|
||||
return process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000";
|
||||
};
|
||||
|
||||
async function request<T>(path: string, init?: RequestInit): Promise<T> {
|
||||
const token = getAuthToken();
|
||||
const response = await fetch(`${apiBase()}${path}`, {
|
||||
...init,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
||||
...(init?.headers || {}),
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const detail = await response.text();
|
||||
throw new Error(detail || `请求失败:${response.status}`);
|
||||
}
|
||||
return response.json() as Promise<T>;
|
||||
}
|
||||
|
||||
const optionalText = (value: unknown) => {
|
||||
const normalized = String(value ?? "").trim();
|
||||
if (!normalized || normalized === NONE_SELECT_VALUE) return undefined;
|
||||
return normalized;
|
||||
};
|
||||
|
||||
const requiredText = (value: unknown, message: string) => {
|
||||
const normalized = optionalText(value);
|
||||
if (!normalized) throw new Error(message);
|
||||
return normalized;
|
||||
};
|
||||
|
||||
const requiredListType = (value: unknown): ListType => {
|
||||
const normalized = optionalText(value);
|
||||
if (!normalized) throw new Error("请选择列表类型");
|
||||
return normalized as ListType;
|
||||
};
|
||||
|
||||
export const mapListRecord = (list: ListSummary | ListDetails): ListRecord => ({
|
||||
...list,
|
||||
id: list.listDbId || "",
|
||||
list_name: list.listName ?? null,
|
||||
list_description: list.listDescription ?? null,
|
||||
list_source: list.listSource ?? null,
|
||||
list_owner_name: list.listOwnerName ?? null,
|
||||
list_owner_person_id: list.listOwnerPersonDbId ?? null,
|
||||
list_type: list.listType ?? null,
|
||||
date_created: list.dateCreated ?? null,
|
||||
date_modified: list.dateModified ?? null,
|
||||
data: "data" in list && list.data ? list.data : [],
|
||||
});
|
||||
|
||||
const toRequestBody = (payload: ListPayload, data?: string[]): ListNewRequest => ({
|
||||
listName: requiredText(payload.listName ?? payload.list_name, "请填写列表名称"),
|
||||
listType: requiredListType(payload.listType ?? payload.list_type),
|
||||
listDescription: optionalText(payload.listDescription ?? payload.list_description),
|
||||
listSource: optionalText(payload.listSource ?? payload.list_source),
|
||||
listOwnerName: optionalText(payload.listOwnerName ?? payload.list_owner_name),
|
||||
listOwnerPersonDbId: optionalText(payload.listOwnerPersonDbId ?? payload.list_owner_person_id),
|
||||
...(data !== undefined ? { data } : {}),
|
||||
});
|
||||
|
||||
export async function fetchListRows(): Promise<ListRecord[]> {
|
||||
const response = await request<BrapiListResponse<ListSummary>>("/brapi/v2/lists?page=0&pageSize=1000");
|
||||
return response.result.data.map(mapListRecord);
|
||||
}
|
||||
|
||||
export async function fetchListDetail(listDbId: string): Promise<ListRecord> {
|
||||
const response = await request<BrapiSingleResponse<ListDetails>>(`/brapi/v2/lists/${encodeURIComponent(listDbId)}`);
|
||||
return mapListRecord(response.result);
|
||||
}
|
||||
|
||||
export async function fetchPersonOptions(): Promise<SelectOption[]> {
|
||||
const response = await request<BrapiListResponse<PersonResponse>>("/brapi/v2/people?page=0&pageSize=1000");
|
||||
return response.result.data.map((person) => {
|
||||
const name = [person.firstName, person.lastName].filter(Boolean).join(" ").trim();
|
||||
const label = name
|
||||
? `${name}${person.emailAddress ? ` / ${person.emailAddress}` : ""}`
|
||||
: person.personDbId;
|
||||
return { value: person.personDbId, label };
|
||||
});
|
||||
}
|
||||
|
||||
export async function createListRow(payload: ListPayload): Promise<ListRecord> {
|
||||
const response = await request<BrapiListResponse<ListSummary>>("/brapi/v2/lists", {
|
||||
method: "POST",
|
||||
body: JSON.stringify([toRequestBody(payload)]),
|
||||
});
|
||||
return mapListRecord(response.result.data[0]);
|
||||
}
|
||||
|
||||
export async function updateListRow(listDbId: string, payload: ListPayload, data?: string[]): Promise<ListRecord> {
|
||||
const response = await request<BrapiSingleResponse<ListDetails>>(`/brapi/v2/lists/${encodeURIComponent(listDbId)}`, {
|
||||
method: "PUT",
|
||||
body: JSON.stringify(toRequestBody(payload, data)),
|
||||
});
|
||||
return mapListRecord(response.result);
|
||||
}
|
||||
|
||||
export async function appendListItems(listDbId: string, items: string[]): Promise<ListRecord> {
|
||||
const response = await request<BrapiSingleResponse<ListDetails>>(`/brapi/v2/lists/${encodeURIComponent(listDbId)}/items`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(items),
|
||||
});
|
||||
return mapListRecord(response.result);
|
||||
}
|
||||
|
||||
export async function replaceListItems(listDbId: string, items: string[]): Promise<ListRecord> {
|
||||
const detail = await fetchListDetail(listDbId);
|
||||
return updateListRow(
|
||||
listDbId,
|
||||
{
|
||||
listName: detail.listName,
|
||||
listType: detail.listType,
|
||||
listDescription: detail.listDescription,
|
||||
listSource: detail.listSource,
|
||||
listOwnerName: detail.listOwnerName,
|
||||
listOwnerPersonDbId: detail.listOwnerPersonDbId,
|
||||
},
|
||||
items,
|
||||
);
|
||||
}
|
||||
|
||||
export function normalizeNewItems(existing: string[], incoming: string[]): string[] {
|
||||
const seen = new Set(existing.map((item) => item.trim()).filter(Boolean));
|
||||
const duplicates: string[] = [];
|
||||
const added: string[] = [];
|
||||
|
||||
for (const raw of incoming) {
|
||||
const item = raw.trim();
|
||||
if (!item) continue;
|
||||
if (seen.has(item)) {
|
||||
duplicates.push(item);
|
||||
continue;
|
||||
}
|
||||
seen.add(item);
|
||||
added.push(item);
|
||||
}
|
||||
|
||||
if (duplicates.length > 0) {
|
||||
throw new Error(`以下列表项已存在:${duplicates.join("、")}`);
|
||||
}
|
||||
if (added.length === 0) {
|
||||
throw new Error("请至少填写一个有效的列表项");
|
||||
}
|
||||
return added;
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useState } from "react";
|
||||
import { ArrowDown, ArrowUp, Plus, Trash2, Upload } from "lucide-react";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from "@/components/ui/alert-dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import {
|
||||
Dialog,
|
||||
DialogBody,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
} from "@/components/common/shadcn-enhanced";
|
||||
import { appendListItems, normalizeNewItems, replaceListItems } from "../api";
|
||||
|
||||
type ListItemPanelProps = {
|
||||
listDbId: string;
|
||||
items: string[];
|
||||
onItemsChange: (items: string[]) => void;
|
||||
};
|
||||
|
||||
export function ListItemPanel({ listDbId, items, onItemsChange }: ListItemPanelProps) {
|
||||
const [addOpen, setAddOpen] = useState(false);
|
||||
const [importOpen, setImportOpen] = useState(false);
|
||||
const [newItem, setNewItem] = useState("");
|
||||
const [importText, setImportText] = useState("");
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [deleteIndex, setDeleteIndex] = useState<number | null>(null);
|
||||
|
||||
const runAction = useCallback(async (action: () => Promise<{ data?: string[] }>) => {
|
||||
setSaving(true);
|
||||
setError(null);
|
||||
try {
|
||||
const result = await action();
|
||||
onItemsChange(result.data ?? []);
|
||||
} catch (event) {
|
||||
setError(event instanceof Error ? event.message : "操作失败");
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
}, [onItemsChange]);
|
||||
|
||||
const handleAddItem = async () => {
|
||||
const added = normalizeNewItems(items, [newItem]);
|
||||
await runAction(async () => appendListItems(listDbId, added));
|
||||
setNewItem("");
|
||||
setAddOpen(false);
|
||||
};
|
||||
|
||||
const handleImport = async () => {
|
||||
const lines = importText.split(/\r?\n/);
|
||||
const added = normalizeNewItems(items, lines);
|
||||
await runAction(async () => appendListItems(listDbId, added));
|
||||
setImportText("");
|
||||
setImportOpen(false);
|
||||
};
|
||||
|
||||
const handleDelete = async () => {
|
||||
if (deleteIndex === null) return;
|
||||
const nextItems = items.filter((_, index) => index !== deleteIndex);
|
||||
await runAction(async () => replaceListItems(listDbId, nextItems));
|
||||
setDeleteIndex(null);
|
||||
};
|
||||
|
||||
const moveItem = async (index: number, direction: -1 | 1) => {
|
||||
const target = index + direction;
|
||||
if (target < 0 || target >= items.length) return;
|
||||
const nextItems = [...items];
|
||||
[nextItems[index], nextItems[target]] = [nextItems[target], nextItems[index]];
|
||||
await runAction(async () => replaceListItems(listDbId, nextItems));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="rounded-xl border border-slate-200 bg-white dark:border-slate-800 dark:bg-slate-950">
|
||||
<div className="flex flex-col gap-3 border-b border-slate-200 px-4 py-3 sm:flex-row sm:items-center sm:justify-between dark:border-slate-800">
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold text-slate-900 dark:text-slate-50">列表项 (list_item)</h3>
|
||||
<p className="mt-0.5 text-xs text-slate-500 dark:text-slate-400">同一列表内项值不可重复;删除与排序通过 PUT 整表替换实现</p>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Button size="sm" className="gap-1.5" onClick={() => setAddOpen(true)} disabled={saving}>
|
||||
<Plus className="h-3.5 w-3.5" />
|
||||
新增项
|
||||
</Button>
|
||||
<Button size="sm" variant="outline" className="gap-1.5" onClick={() => setImportOpen(true)} disabled={saving}>
|
||||
<Upload className="h-3.5 w-3.5" />
|
||||
批量导入
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error ? (
|
||||
<div className="mx-4 mt-3 rounded-lg border border-destructive/20 bg-destructive/10 px-3 py-2 text-sm text-destructive">
|
||||
{error}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow className="border-slate-200 bg-slate-50 dark:border-slate-800 dark:bg-slate-900">
|
||||
<TableHead className="w-12 text-xs">#</TableHead>
|
||||
<TableHead className="text-xs">项值 (item)</TableHead>
|
||||
<TableHead className="w-36 text-right text-xs">操作</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{items.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={3} className="py-12 text-center text-sm text-slate-400">
|
||||
暂无列表项,可新增或批量导入
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
items.map((item, index) => (
|
||||
<TableRow key={`${item}-${index}`} className="border-slate-100 dark:border-slate-800">
|
||||
<TableCell className="text-xs text-slate-400">{index + 1}</TableCell>
|
||||
<TableCell className="break-all text-sm text-slate-700 dark:text-slate-200">{item}</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<div className="flex justify-end gap-1">
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-7 w-7"
|
||||
disabled={saving || index === 0}
|
||||
onClick={() => moveItem(index, -1)}
|
||||
>
|
||||
<ArrowUp className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-7 w-7"
|
||||
disabled={saving || index === items.length - 1}
|
||||
onClick={() => moveItem(index, 1)}
|
||||
>
|
||||
<ArrowDown className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-7 w-7 text-slate-400 hover:text-red-500"
|
||||
disabled={saving}
|
||||
onClick={() => setDeleteIndex(index)}
|
||||
>
|
||||
<Trash2 className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
|
||||
<Dialog open={addOpen} onOpenChange={setAddOpen}>
|
||||
<DialogContent title="新增列表项" defaultWidth={520} defaultHeight={320} minHeight={280}>
|
||||
<DialogBody>
|
||||
<p className="mb-3 text-sm text-muted-foreground">填写目标对象 ID 或文本值,同一列表内不可重复</p>
|
||||
<Label htmlFor="new-list-item" className="mb-1.5 block text-sm">项值</Label>
|
||||
<Input
|
||||
id="new-list-item"
|
||||
value={newItem}
|
||||
onChange={(event) => setNewItem(event.target.value)}
|
||||
placeholder="如 germplasm ID、study ID 或备注文本"
|
||||
/>
|
||||
</DialogBody>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setAddOpen(false)}>取消</Button>
|
||||
<Button onClick={handleAddItem} disabled={saving || !newItem.trim()}>
|
||||
{saving ? "保存中..." : "确认添加"}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<Dialog open={importOpen} onOpenChange={setImportOpen}>
|
||||
<DialogContent title="批量导入列表项" defaultWidth={560} defaultHeight={480} minHeight={360}>
|
||||
<DialogBody>
|
||||
<p className="mb-3 text-sm text-muted-foreground">每行一个项值,重复项将拒绝导入</p>
|
||||
<Textarea
|
||||
value={importText}
|
||||
onChange={(event) => setImportText(event.target.value)}
|
||||
rows={8}
|
||||
placeholder={"GERM-001\nGERM-002\nstudy-2026-spring"}
|
||||
className="resize-none font-mono text-sm"
|
||||
/>
|
||||
</DialogBody>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setImportOpen(false)}>取消</Button>
|
||||
<Button onClick={handleImport} disabled={saving || !importText.trim()}>
|
||||
{saving ? "导入中..." : "确认导入"}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<AlertDialog open={deleteIndex !== null} onOpenChange={(open) => !open && setDeleteIndex(null)}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>删除列表项</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
确认删除项
|
||||
{" "}
|
||||
<span className="font-medium text-slate-700 dark:text-slate-200">
|
||||
{deleteIndex !== null ? items[deleteIndex] : ""}
|
||||
</span>
|
||||
{" "}
|
||||
吗?此操作通过更新整表数据生效。
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel disabled={saving}>取消</AlertDialogCancel>
|
||||
<AlertDialogAction onClick={handleDelete} disabled={saving} className="bg-destructive text-destructive-foreground hover:bg-destructive/90">
|
||||
{saving ? "删除中..." : "确认删除"}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import {
|
||||
Dialog,
|
||||
DialogBody,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
} from "@/components/common/shadcn-enhanced";
|
||||
import { updateListRow } from "../api";
|
||||
import { LIST_TYPE_OPTIONS, NONE_SELECT_VALUE, type ListRecord, type SelectOption } from "../types";
|
||||
|
||||
type ListMetaDialogProps = {
|
||||
open: boolean;
|
||||
record: ListRecord;
|
||||
personOptions: SelectOption[];
|
||||
onOpenChange: (open: boolean) => void;
|
||||
onSaved: (record: ListRecord) => void;
|
||||
};
|
||||
|
||||
export function ListMetaDialog({ open, record, personOptions, onOpenChange, onSaved }: ListMetaDialogProps) {
|
||||
const [form, setForm] = useState({
|
||||
listName: "",
|
||||
listType: "",
|
||||
listDescription: "",
|
||||
listSource: "",
|
||||
listOwnerName: "",
|
||||
listOwnerPersonDbId: NONE_SELECT_VALUE,
|
||||
});
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
setForm({
|
||||
listName: record.listName ?? "",
|
||||
listType: record.listType ?? "",
|
||||
listDescription: record.listDescription ?? "",
|
||||
listSource: record.listSource ?? "",
|
||||
listOwnerName: record.listOwnerName ?? "",
|
||||
listOwnerPersonDbId: record.listOwnerPersonDbId || NONE_SELECT_VALUE,
|
||||
});
|
||||
setError(null);
|
||||
}, [open, record]);
|
||||
|
||||
const updateField = (key: keyof typeof form, value: string) => {
|
||||
setForm((current) => ({ ...current, [key]: value }));
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
setSaving(true);
|
||||
setError(null);
|
||||
try {
|
||||
const updated = await updateListRow(record.id, form, record.data ?? []);
|
||||
onSaved(updated);
|
||||
onOpenChange(false);
|
||||
} catch (event) {
|
||||
setError(event instanceof Error ? event.message : "保存失败");
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent title="编辑列表基本信息" defaultWidth={720} defaultHeight={560} minHeight={420}>
|
||||
<DialogBody>
|
||||
<p className="mb-3 text-sm text-muted-foreground">修改 list 主表字段,不会影响已有 list_item</p>
|
||||
{error ? (
|
||||
<div className="mb-3 rounded-lg border border-destructive/20 bg-destructive/10 px-3 py-2 text-sm text-destructive">
|
||||
{error}
|
||||
</div>
|
||||
) : null}
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div className="md:col-span-2">
|
||||
<Label htmlFor="edit-listName" className="mb-1.5 block text-sm">
|
||||
列表名称<span className="ml-0.5 text-red-500">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
id="edit-listName"
|
||||
value={form.listName}
|
||||
onChange={(event) => updateField("listName", event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="edit-listType" className="mb-1.5 block text-sm">
|
||||
列表类型<span className="ml-0.5 text-red-500">*</span>
|
||||
</Label>
|
||||
<Select value={form.listType} onValueChange={(value) => updateField("listType", value)}>
|
||||
<SelectTrigger id="edit-listType">
|
||||
<SelectValue placeholder="请选择列表类型" />
|
||||
</SelectTrigger>
|
||||
<SelectContent position="item-aligned">
|
||||
{LIST_TYPE_OPTIONS.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="edit-listSource" className="mb-1.5 block text-sm">列表来源</Label>
|
||||
<Input
|
||||
id="edit-listSource"
|
||||
value={form.listSource}
|
||||
onChange={(event) => updateField("listSource", event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="edit-listOwnerName" className="mb-1.5 block text-sm">Owner 名称</Label>
|
||||
<Input
|
||||
id="edit-listOwnerName"
|
||||
value={form.listOwnerName}
|
||||
onChange={(event) => updateField("listOwnerName", event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="edit-listOwnerPersonDbId" className="mb-1.5 block text-sm">Owner 人员</Label>
|
||||
<Select
|
||||
value={form.listOwnerPersonDbId}
|
||||
onValueChange={(value) => updateField("listOwnerPersonDbId", value)}
|
||||
>
|
||||
<SelectTrigger id="edit-listOwnerPersonDbId">
|
||||
<SelectValue placeholder="不绑定人员" />
|
||||
</SelectTrigger>
|
||||
<SelectContent position="item-aligned">
|
||||
<SelectItem value={NONE_SELECT_VALUE}>不绑定人员</SelectItem>
|
||||
{personOptions.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="md:col-span-2">
|
||||
<Label htmlFor="edit-listDescription" className="mb-1.5 block text-sm">列表描述</Label>
|
||||
<Textarea
|
||||
id="edit-listDescription"
|
||||
value={form.listDescription}
|
||||
onChange={(event) => updateField("listDescription", event.target.value)}
|
||||
rows={3}
|
||||
className="resize-none"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</DialogBody>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => onOpenChange(false)} disabled={saving}>
|
||||
取消
|
||||
</Button>
|
||||
<Button onClick={handleSave} disabled={saving}>
|
||||
{saving ? "保存中..." : "保存"}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import type { BrapiFormField } from "@/components/brapi/BrapiEntityPage";
|
||||
import { LIST_TYPE_OPTIONS, NONE_SELECT_VALUE, type SelectOption } from "../types";
|
||||
|
||||
export function buildListFormFields(personOptions: SelectOption[]): BrapiFormField[] {
|
||||
return [
|
||||
{ key: "listName", label: "列表名称", type: "text", required: true, placeholder: "如 2026 核心种质清单" },
|
||||
{
|
||||
key: "listType",
|
||||
label: "列表类型",
|
||||
type: "select",
|
||||
required: true,
|
||||
options: LIST_TYPE_OPTIONS.map((option) => ({ value: option.value, label: option.label })),
|
||||
},
|
||||
{ key: "listDescription", label: "列表描述", type: "textarea", colSpan: 2, placeholder: "用途、范围或备注" },
|
||||
{ key: "listSource", label: "列表来源", type: "text", placeholder: "如 田间调查、导入批次" },
|
||||
{ key: "listOwnerName", label: "Owner 名称", type: "text", placeholder: "可选,选择人员后可自动带出" },
|
||||
{
|
||||
key: "listOwnerPersonDbId",
|
||||
label: "Owner 人员",
|
||||
type: "select",
|
||||
options: [{ value: NONE_SELECT_VALUE, label: "不绑定人员" }, ...personOptions],
|
||||
},
|
||||
];
|
||||
}
|
||||
85
frontend/src/app/(app)/basic-dictionary/base/list/page.tsx
Normal file
85
frontend/src/app/(app)/basic-dictionary/base/list/page.tsx
Normal file
@@ -0,0 +1,85 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import { List } from "lucide-react";
|
||||
import { BrapiEntityPage } from "@/components/brapi/BrapiEntityPage";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
createListRow,
|
||||
fetchListDetail,
|
||||
fetchListRows,
|
||||
fetchPersonOptions,
|
||||
updateListRow,
|
||||
} from "./api";
|
||||
import { buildListFormFields } from "./components/listFormFields";
|
||||
import { listTypeLabel, type SelectOption } from "./types";
|
||||
|
||||
export default function ListDictionaryPage() {
|
||||
const [personOptions, setPersonOptions] = useState<SelectOption[]>([]);
|
||||
|
||||
const loadRows = useCallback(async () => {
|
||||
const [people, rows] = await Promise.all([fetchPersonOptions(), fetchListRows()]);
|
||||
setPersonOptions(people);
|
||||
return rows as unknown as Record<string, unknown>[];
|
||||
}, []);
|
||||
|
||||
const fields = useMemo(() => buildListFormFields(personOptions), [personOptions]);
|
||||
|
||||
const createRecord = useCallback(
|
||||
async (payload: Record<string, unknown>) => createListRow(payload) as unknown as Record<string, unknown>,
|
||||
[],
|
||||
);
|
||||
|
||||
const updateRecord = useCallback(
|
||||
async (id: string, payload: Record<string, unknown>) => updateListRow(id, payload) as unknown as Record<string, unknown>,
|
||||
[],
|
||||
);
|
||||
|
||||
const fetchRecord = useCallback(
|
||||
async (id: string) => fetchListDetail(id) as unknown as Record<string, unknown>,
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<BrapiEntityPage
|
||||
icon={List}
|
||||
iconBg="bg-gradient-to-br from-violet-500 to-purple-600"
|
||||
title="List 通用列表"
|
||||
description="维护 BrAPI 通用分组列表,可在详情页管理 list_item 明细"
|
||||
addLabel="新增列表"
|
||||
useEnhancedDialog
|
||||
columns={[
|
||||
{ key: "listName", label: "列表名称" },
|
||||
{
|
||||
key: "listType",
|
||||
label: "类型",
|
||||
render: (value) => listTypeLabel(value),
|
||||
},
|
||||
{ key: "listSize", label: "项数" },
|
||||
{ key: "listOwnerName", label: "Owner" },
|
||||
{ key: "listSource", label: "来源" },
|
||||
{
|
||||
key: "id",
|
||||
label: "明细",
|
||||
render: (_value, row) => {
|
||||
const id = String(row.id ?? row.listDbId ?? "");
|
||||
if (!id) return <span className="text-slate-300">—</span>;
|
||||
return (
|
||||
<Button variant="link" className="h-auto p-0 text-violet-600 dark:text-violet-300" asChild>
|
||||
<Link href={`/basic-dictionary/base/list/${encodeURIComponent(id)}`}>管理明细</Link>
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
},
|
||||
]}
|
||||
fields={fields}
|
||||
data={[]}
|
||||
stats={[{ label: "/brapi/v2/lists", value: "BrAPI", className: "bg-violet-50 text-violet-700 dark:bg-violet-400/10 dark:text-violet-200" }]}
|
||||
loadData={loadRows}
|
||||
fetchRecord={fetchRecord}
|
||||
createRecord={createRecord}
|
||||
updateRecord={updateRecord}
|
||||
/>
|
||||
);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user