init: 初始化 dpb 桃育种系统代码库
前后端 + 后端 FastAPI 全量源码、部署脚本与文档。
This commit is contained in:
@@ -0,0 +1,738 @@
|
||||
"""育种统计 路由(一期 2 个最基础:ABLUP·EBV + 配合力 GCA/SCA)"""
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, Body, Depends, Query
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.api.v1.module_bre.statistics.schema import (
|
||||
AnovaIn,
|
||||
AnovaOut,
|
||||
ClusterIn,
|
||||
CombiningAbilityOut,
|
||||
CorrelationIn,
|
||||
CvFoldOut,
|
||||
CvResultOut,
|
||||
CvRunIn,
|
||||
DataQualityIn,
|
||||
DecisionPreviewIn,
|
||||
DescribeStatsIn,
|
||||
GblupRunIn,
|
||||
GeneticCorrIn,
|
||||
GeneticCorrOut,
|
||||
GeneticGainIn,
|
||||
GenotypingDatasetOut,
|
||||
GwasQtlXEIn,
|
||||
GwasResultOut,
|
||||
GwasRunIn,
|
||||
GwasSnpOut,
|
||||
InbreedingDepressionIn,
|
||||
KinshipIn,
|
||||
MatingRecommendIn,
|
||||
MabcIn,
|
||||
MasPanelIn,
|
||||
MasPanelOut,
|
||||
MasPanelSetMarkersIn,
|
||||
OcsIn,
|
||||
PredictionOut,
|
||||
PredictionValueOut,
|
||||
QtlIn,
|
||||
QtlOut,
|
||||
RunStatsIn,
|
||||
SelectionIndexApplyIn,
|
||||
SelectionIndexIn,
|
||||
SelectionIndexOut,
|
||||
StabilityOut,
|
||||
StabilityRunIn,
|
||||
StatisticsJobOut,
|
||||
TrialDesignIn,
|
||||
TypeBIn,
|
||||
TypeBOut,
|
||||
)
|
||||
from app.api.v1.module_bre.statistics.service import StatisticsService
|
||||
from app.core.base_schema import AuthSchema
|
||||
from app.core.router_class import OperationLogRoute
|
||||
from app.core.dependencies import db_getter
|
||||
from app.core.dependencies import AuthPermission
|
||||
from app.common.response import SuccessResponse
|
||||
|
||||
StatisticsRouter = APIRouter(route_class=OperationLogRoute, prefix="/statistics")
|
||||
|
||||
|
||||
@StatisticsRouter.post("/ablup/run", summary="运行 ABLUP/EBV 估计")
|
||||
async def run_ablup(
|
||||
data: RunStatsIn,
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:create"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
return SuccessResponse(await StatisticsService(auth, db).run_ablup(
|
||||
data.trait_id, data.trait_code, data.year, data.fixed_effects, data.covariate,
|
||||
data.data_gate, data.min_clone_n, data.min_pedigree_rate, data.max_missing_rate,
|
||||
data.gxe, data.gxe_env, data.stage, data.gxr, data.spatial, data.spatial_aniso,
|
||||
data.block,
|
||||
))
|
||||
|
||||
|
||||
@StatisticsRouter.post("/combining/run", summary="运行配合力 GCA/SCA 分析")
|
||||
async def run_combining(
|
||||
data: RunStatsIn,
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:create"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
return SuccessResponse(
|
||||
await StatisticsService(auth, db).run_combining(
|
||||
data.trait_id, data.trait_code, data.year, data.design_type,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@StatisticsRouter.get("/predictions", summary="育种值模型列表")
|
||||
async def list_predictions(
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:query"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
objs = await StatisticsService(auth, db).list_predictions()
|
||||
return SuccessResponse([PredictionOut.model_validate(o) for o in objs])
|
||||
|
||||
|
||||
@StatisticsRouter.get("/predictions/{prediction_id}/values", summary="EBV 排行")
|
||||
async def ebv_ranking(
|
||||
prediction_id: int,
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:query"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
objs = await StatisticsService(auth, db).ebv_ranking(prediction_id)
|
||||
return SuccessResponse([PredictionValueOut.model_validate(o) for o in objs])
|
||||
|
||||
|
||||
@StatisticsRouter.get("/predictions/{prediction_id}/clones", summary="无性系级 EBV 排行")
|
||||
async def clone_ranking(
|
||||
prediction_id: int,
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:query"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
result = await StatisticsService(auth, db).clone_ranking(prediction_id)
|
||||
return SuccessResponse(result)
|
||||
|
||||
|
||||
@StatisticsRouter.get("/combining", summary="配合力结果列表")
|
||||
async def list_combining(
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:query"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
objs = await StatisticsService(auth, db).list_combining()
|
||||
return SuccessResponse([CombiningAbilityOut.model_validate(o) for o in objs])
|
||||
|
||||
|
||||
@StatisticsRouter.get("/combining/{ca_id}", summary="配合力结果详情(GCA/SCA)")
|
||||
async def combining_detail(
|
||||
ca_id: int,
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:query"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
obj = await StatisticsService(auth, db).combining_detail(ca_id)
|
||||
return SuccessResponse(CombiningAbilityOut.model_validate(obj))
|
||||
|
||||
|
||||
@StatisticsRouter.get("/jobs", summary="统计任务列表")
|
||||
async def list_jobs(
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:query"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
objs = await StatisticsService(auth, db).list_jobs()
|
||||
return SuccessResponse([StatisticsJobOut.model_validate(o) for o in objs])
|
||||
|
||||
|
||||
@StatisticsRouter.get("/jobs/{job_id}", summary="统计任务状态")
|
||||
async def job_status(
|
||||
job_id: int,
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:query"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
obj = await StatisticsService(auth, db).job_status(job_id)
|
||||
return SuccessResponse(StatisticsJobOut.model_validate(obj))
|
||||
|
||||
|
||||
# ---------- k-fold 交叉验证(外部验证) ----------
|
||||
@StatisticsRouter.post("/cv/run", summary="运行 k-fold 交叉验证(外部验证预测准确度)")
|
||||
async def run_cv(
|
||||
data: CvRunIn,
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:create"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
return SuccessResponse(await StatisticsService(auth, db).run_cv(
|
||||
data.trait_id, data.trait_code, data.year, data.fixed_effects, data.covariate,
|
||||
data.gxe, data.gxe_env, data.k,
|
||||
data.dataset_id, data.method, data.maf_min,
|
||||
data.split, data.seed,
|
||||
))
|
||||
|
||||
|
||||
@StatisticsRouter.get("/cv", summary="k-fold CV 结果批次列表")
|
||||
async def list_cv(
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:query"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
objs = await StatisticsService(auth, db).list_cv()
|
||||
return SuccessResponse([CvResultOut.model_validate(o) for o in objs])
|
||||
|
||||
|
||||
@StatisticsRouter.get("/cv/{cv_id}", summary="k-fold CV 结果详情(含折明细)")
|
||||
async def cv_detail(
|
||||
cv_id: int,
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:query"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
result = await StatisticsService(auth, db).cv_detail(cv_id)
|
||||
return SuccessResponse({
|
||||
"result": CvResultOut.model_validate(result["result"]),
|
||||
"folds": [CvFoldOut.model_validate(f) for f in result["folds"]],
|
||||
})
|
||||
|
||||
|
||||
# ---------- 纯 Python 统计分析(不依赖 R) ----------
|
||||
@StatisticsRouter.get("/describe", summary="描述性统计(无需R)")
|
||||
async def describe_stats(
|
||||
trait_codes: list[str] | None = Query(None),
|
||||
group_by: str = Query("none"),
|
||||
year: int | None = Query(None),
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:query"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
return SuccessResponse(await StatisticsService(auth, db).describe_stats(trait_codes, group_by, year))
|
||||
|
||||
|
||||
@StatisticsRouter.get("/correlation", summary="性状相关性矩阵(无需R;mode=pheno表型/genetic遗传)")
|
||||
async def correlation(
|
||||
trait_codes: list[str] | None = Query(None),
|
||||
mode: str = Query("pheno", description="pheno=皮尔逊表型相关;genetic=成对双性状BLUP遗传相关"),
|
||||
g_method: str = Query("mtblup", description="genetic 模式遗传相关来源:mtblup / calo(缺批次回退)"),
|
||||
year: int | None = Query(None, description="年份过滤(仅 genetic 模式生效)"),
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:query"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
return SuccessResponse(await StatisticsService(auth, db).correlation_matrix(
|
||||
trait_codes, mode, g_method, year))
|
||||
|
||||
|
||||
@StatisticsRouter.post("/selection-index", summary="选择指数排名(EBV或表型加权,落库)")
|
||||
async def selection_index(
|
||||
data: SelectionIndexIn,
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:query"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
return SuccessResponse(await StatisticsService(auth, db).selection_index(
|
||||
data.weights, data.year, data.top_n, data.batch_ids, data.use_h2, data.method, data.aggregate,
|
||||
data.stage, data.g_method, data.auto_weights,
|
||||
data.restricted_traits, data.economic_weights,
|
||||
))
|
||||
|
||||
|
||||
@StatisticsRouter.get("/selection-index", summary="选择指数批次列表")
|
||||
async def list_index_batches(
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:query"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
objs = await StatisticsService(auth, db).list_index_batches()
|
||||
return SuccessResponse([SelectionIndexOut.model_validate(o) for o in objs])
|
||||
|
||||
|
||||
@StatisticsRouter.post("/selection-index/{index_id}/apply", summary="选择指数前N名写入决选")
|
||||
async def apply_selection(
|
||||
index_id: int,
|
||||
data: SelectionIndexApplyIn,
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:create"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
return SuccessResponse(
|
||||
await StatisticsService(auth, db).apply_selection(
|
||||
index_id, data.top_n, data.selection_year, data.rule_id, data.min_reliability)
|
||||
)
|
||||
|
||||
|
||||
@StatisticsRouter.post("/kinship", summary="亲缘/近交分析(复用系谱A矩阵)")
|
||||
async def kinship(
|
||||
data: KinshipIn,
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:query"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
return SuccessResponse(await StatisticsService(auth, db).kinship_matrix(data.threshold, data.tree_ids))
|
||||
|
||||
|
||||
@StatisticsRouter.post("/inbreeding-depression", summary="近交衰退分析(F→表型线性回归,纯计算)")
|
||||
async def inbreeding_depression(
|
||||
data: InbreedingDepressionIn,
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:query"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
return SuccessResponse(await StatisticsService(auth, db).inbreeding_depression(
|
||||
data.trait_id, data.trait_code, data.year, data.trial_study_id, data.min_n))
|
||||
|
||||
|
||||
@StatisticsRouter.post("/data-quality", summary="数据质量/异常值诊断(IQR+MAD稳健z)")
|
||||
async def data_quality(
|
||||
data: DataQualityIn,
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:query"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
return SuccessResponse(await StatisticsService(auth, db).data_quality_report(
|
||||
data.trait_id, data.trait_code, data.year, data.trial_study_id, data.dataset_id))
|
||||
|
||||
|
||||
@StatisticsRouter.post("/genetic-gain", summary="ΔG 遗传增益投影(截断选择强度×PA×σ_A)")
|
||||
async def genetic_gain(
|
||||
data: GeneticGainIn,
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:create"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
return SuccessResponse(await StatisticsService(auth, db).genetic_gain(
|
||||
data.trait_id, data.trait_code, data.prediction_id,
|
||||
data.top_p, data.top_n, data.generation_interval))
|
||||
|
||||
|
||||
@StatisticsRouter.post("/mating-recommend", summary="主动选配推荐(EBV互补−近交惩罚,S-等位硬过滤)")
|
||||
async def mating_recommend(
|
||||
data: MatingRecommendIn,
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:create"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
return SuccessResponse(await StatisticsService(auth, db).mating_recommend(
|
||||
data.candidate_germplasm_ids, data.prediction_id, data.kinship_threshold,
|
||||
data.w_ebv, data.w_kin, data.max_pairs))
|
||||
|
||||
|
||||
@StatisticsRouter.post("/ocs", summary="最优贡献选择 OCS(群体配种贡献优化,纯计算)")
|
||||
async def ocs(
|
||||
data: OcsIn,
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:create"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
return SuccessResponse(await StatisticsService(auth, db).ocs(
|
||||
data.candidate_germplasm_ids, data.n_select, data.lam, data.prediction_id))
|
||||
|
||||
|
||||
@StatisticsRouter.post("/mabc-progress", summary="MABC 标记辅助回交进度(前景MAS+背景恢复率+回交代建议,纯计算)")
|
||||
async def mabc_progress(
|
||||
data: MabcIn,
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:create"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
return SuccessResponse(await StatisticsService(auth, db).mabc_progress(
|
||||
data.candidate_tree_ids, data.foreground_panel_ids, data.background_panel_ids,
|
||||
data.recurrent_parent_tree_id, data.foreground_min_hits, data.generation,
|
||||
data.background_target))
|
||||
|
||||
|
||||
@StatisticsRouter.post("/trial-design", summary="试验设计生成:RCBD/增广/α-格子(落库 block_no)")
|
||||
async def trial_design(
|
||||
data: TrialDesignIn,
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:create"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
return SuccessResponse(await StatisticsService(auth, db).trial_design(
|
||||
data.trial_study_id, data.design_type, data.seed,
|
||||
data.check_germplasm_ids, data.block_size, data.reps))
|
||||
|
||||
|
||||
@StatisticsRouter.post("/anova/run", summary="运行 ANOVA / 广义遗传力 H²")
|
||||
async def run_anova(
|
||||
data: AnovaIn,
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:create"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
return SuccessResponse(await StatisticsService(auth, db).run_anova(
|
||||
data.trait_id, data.trait_code, data.year, data.block))
|
||||
|
||||
|
||||
@StatisticsRouter.get("/anova", summary="ANOVA 结果列表")
|
||||
async def list_anova(
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:query"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
objs = await StatisticsService(auth, db).list_anova()
|
||||
return SuccessResponse([AnovaOut.model_validate(o) for o in objs])
|
||||
|
||||
|
||||
@StatisticsRouter.get("/anova/{anova_id}", summary="ANOVA 结果详情")
|
||||
async def anova_detail(
|
||||
anova_id: int,
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:query"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
obj = await StatisticsService(auth, db).anova_detail(anova_id)
|
||||
return SuccessResponse(AnovaOut.model_validate(obj))
|
||||
|
||||
|
||||
@StatisticsRouter.get("/trait-values", summary="统一性状值视图(长表)")
|
||||
async def trait_values(
|
||||
trait_codes: list[str] | None = Query(None),
|
||||
group_by: str = Query("none"),
|
||||
year: int | None = Query(None),
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:query"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
return SuccessResponse(await StatisticsService(auth, db).trait_values(trait_codes, group_by, year))
|
||||
|
||||
|
||||
@StatisticsRouter.get("/fairness", summary="按site EBV偏差/公平性报告(只读)")
|
||||
async def fairness_report(
|
||||
prediction_id: int = Query(..., description="EBV 预测批次 id"),
|
||||
group_by: str = Query("site", description="分组维度(当前仅支持 site)"),
|
||||
threshold_sd: float = Query(1.0, description="偏差标记阈值:|site均值-全体均值| > threshold_sd×全体sd 标记站点"),
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:query"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
return SuccessResponse(await StatisticsService(auth, db).fairness_report(
|
||||
prediction_id, group_by, threshold_sd))
|
||||
|
||||
|
||||
@StatisticsRouter.get("/predictions/{prediction_id}/compare", summary="模型健康监控(相邻两轮ABLUP对比)")
|
||||
async def compare_predictions(
|
||||
prediction_id: int,
|
||||
prev_prediction_id: int | None = Query(None),
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:query"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
return SuccessResponse(await StatisticsService(auth, db).compare_predictions(prediction_id, prev_prediction_id))
|
||||
|
||||
|
||||
@StatisticsRouter.post("/decision-preview", summary="选择规则决策预览(表型+EBV)")
|
||||
async def decision_preview(
|
||||
data: DecisionPreviewIn,
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:query"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
return SuccessResponse(await StatisticsService(
|
||||
auth, db).decision_preview(data.rule_ids, data.prediction_id, data.year, data.min_reliability,
|
||||
data.stage))
|
||||
|
||||
|
||||
@StatisticsRouter.get("/combination-funnel", summary="组合得失漏斗(花→果→种→苗→定植→树→入选,只读)")
|
||||
async def combination_funnel(
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:query"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
return SuccessResponse(await StatisticsService(auth, db).combination_funnel())
|
||||
|
||||
|
||||
@StatisticsRouter.get("/traits", summary="统计可用性状下拉(只读bre_trait)")
|
||||
async def list_traits(
|
||||
core_only: bool = Query(True, description="仅返回核心性状(对应 tree_evaluation 列)"),
|
||||
selection_only: bool = Query(False, description="仅返回选种目标性状(into_ebv=1,供指数/EBV排行/决策候选)"),
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:query"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
return SuccessResponse(await StatisticsService(auth, db).list_traits(core_only, selection_only))
|
||||
|
||||
|
||||
# ---------- AMMI / Finlay-Wilkinson 稳定性(§8.18) ----------
|
||||
@StatisticsRouter.post("/stability/run", summary="运行 AMMI/Finlay-Wilkinson 稳定性分析")
|
||||
async def run_stability(
|
||||
data: StabilityRunIn,
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:create"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
return SuccessResponse(await StatisticsService(auth, db).run_stability(
|
||||
data.trait_id, data.trait_code, data.gxe_env, data.year, tuple(data.methods),
|
||||
))
|
||||
|
||||
|
||||
@StatisticsRouter.get("/stability", summary="稳定性分析结果列表")
|
||||
async def list_stability(
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:query"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
objs = await StatisticsService(auth, db).list_stability()
|
||||
return SuccessResponse([StabilityOut.model_validate(o) for o in objs])
|
||||
|
||||
|
||||
@StatisticsRouter.get("/stability/{sid}", summary="稳定性分析结果详情(AMMI/FW明细)")
|
||||
async def stability_detail(
|
||||
sid: int,
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:query"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
obj = await StatisticsService(auth, db).stability_detail(sid)
|
||||
return SuccessResponse(StabilityOut.model_validate(obj))
|
||||
|
||||
|
||||
# ---------- 遗传相关矩阵(MT-BLUP 成对双性状,§8.18) ----------
|
||||
@StatisticsRouter.post("/genetic-corr/run", summary="运行遗传相关矩阵(MT-BLUP成对双性状)")
|
||||
async def run_genetic_corr(
|
||||
data: GeneticCorrIn,
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:create"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
return SuccessResponse(await StatisticsService(auth, db).run_genetic_corr(
|
||||
data.trait_ids, data.year, data.full_mtblup))
|
||||
|
||||
|
||||
@StatisticsRouter.get("/genetic-corr", summary="遗传相关结果列表")
|
||||
async def list_genetic_corr(
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:query"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
objs = await StatisticsService(auth, db).list_genetic_corr()
|
||||
return SuccessResponse([GeneticCorrOut.model_validate(o) for o in objs])
|
||||
|
||||
|
||||
@StatisticsRouter.get("/genetic-corr/{cid}", summary="遗传相关结果详情")
|
||||
async def genetic_corr_detail(
|
||||
cid: int,
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:query"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
obj = await StatisticsService(auth, db).genetic_corr_detail(cid)
|
||||
return SuccessResponse(GeneticCorrOut.model_validate(obj))
|
||||
|
||||
|
||||
# ---------- Type-B 多环境遗传相关 / UPGMA 聚类(§8.27) ----------
|
||||
@StatisticsRouter.post("/type-b-heredity", summary="运行 Type-B 多环境遗传相关(环境当性状逐对BLUP,落库)")
|
||||
async def run_type_b_heredity(
|
||||
data: TypeBIn,
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:create"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
return SuccessResponse(await StatisticsService(auth, db).run_type_b_heredity(
|
||||
data.trait_id, data.trait_code, data.env_dim, data.method, data.year))
|
||||
|
||||
|
||||
@StatisticsRouter.get("/type-b", summary="Type-B 多环境遗传相关结果列表")
|
||||
async def list_type_b(
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:query"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
objs = await StatisticsService(auth, db).list_type_b()
|
||||
return SuccessResponse([TypeBOut.model_validate(o) for o in objs])
|
||||
|
||||
|
||||
@StatisticsRouter.get("/type-b/{rid}", summary="Type-B 多环境遗传相关结果详情")
|
||||
async def type_b_detail(
|
||||
rid: int,
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:query"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
obj = await StatisticsService(auth, db).type_b_detail(rid)
|
||||
return SuccessResponse(TypeBOut.model_validate(obj))
|
||||
|
||||
|
||||
@StatisticsRouter.post("/cluster", summary="UPGMA 层次聚类(计算端点,不落库)")
|
||||
async def run_cluster(
|
||||
data: ClusterIn,
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:query"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
return SuccessResponse(await StatisticsService(auth, db).run_cluster(
|
||||
data.trait_ids, data.entity_type, data.mode, data.distance, data.k))
|
||||
|
||||
|
||||
# ---------- GBLUP / ssGBLUP 基因组选择(§8.18) ----------
|
||||
@StatisticsRouter.post("/gblup/run", summary="运行 GBLUP/ssGBLUP 基因组选择")
|
||||
async def run_gblup(
|
||||
data: GblupRunIn,
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:create"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
return SuccessResponse(await StatisticsService(auth, db).run_gblup(
|
||||
data.dataset_id, data.trait_id, data.trait_code, data.year, data.method, data.maf_min,
|
||||
data.seed,
|
||||
))
|
||||
|
||||
|
||||
@StatisticsRouter.get("/gblup/datasets", summary="基因型数据集列表(含样本数)")
|
||||
async def list_genotyping_datasets(
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:query"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
datasets = await StatisticsService(auth, db).list_genotyping_datasets()
|
||||
return SuccessResponse([GenotypingDatasetOut(**d) for d in datasets])
|
||||
|
||||
|
||||
# ---------- MLOps 漂移检测 / 手动重训 / 版本回滚(§8.18) ----------
|
||||
@StatisticsRouter.get("/model/drift", summary="模型漂移检测(输入快照哈希比对)")
|
||||
async def model_drift(
|
||||
prediction_id: int = Query(..., description="预测批次 id"),
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:query"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
return SuccessResponse(await StatisticsService(auth, db).model_drift(prediction_id))
|
||||
|
||||
|
||||
@StatisticsRouter.post("/model/retrain", summary="手动重训(漂移才重跑run_ablup,active自动转移)")
|
||||
async def model_retrain(
|
||||
prediction_id: int = Query(..., description="预测批次 id"),
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:create"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
return SuccessResponse(await StatisticsService(auth, db).model_retrain(prediction_id))
|
||||
|
||||
|
||||
@StatisticsRouter.post("/model/activate/{prediction_id}", summary="版本回滚(该批次设为当前生效版本)")
|
||||
async def model_activate(
|
||||
prediction_id: int,
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:create"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
return SuccessResponse(await StatisticsService(auth, db).model_activate(prediction_id))
|
||||
|
||||
|
||||
# ---------- GWAS / QTL / MAS 标记辅助选择(§8.22) ----------
|
||||
@StatisticsRouter.post("/gwas/run", summary="运行 GWAS 关联分析(GLM+PC)")
|
||||
async def run_gwas(
|
||||
data: GwasRunIn,
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:create"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
return SuccessResponse(await StatisticsService(auth, db).run_gwas(
|
||||
data.dataset_id, data.trait_id, data.trait_code, data.year,
|
||||
data.method, data.maf_min, data.n_pc, data.sig_level, data.qtl_window,
|
||||
))
|
||||
|
||||
|
||||
@StatisticsRouter.get("/gwas", summary="GWAS 批次列表")
|
||||
async def list_gwas(
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:query"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
objs = await StatisticsService(auth, db).list_gwas()
|
||||
return SuccessResponse([GwasResultOut.model_validate(o) for o in objs])
|
||||
|
||||
|
||||
@StatisticsRouter.get("/gwas/{gwas_id}", summary="GWAS 批次详情(Manhattan/QQ 数据 + QTL)")
|
||||
async def gwas_detail(
|
||||
gwas_id: int,
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:query"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
result = await StatisticsService(auth, db).gwas_detail(gwas_id)
|
||||
return SuccessResponse({
|
||||
"result": GwasResultOut.model_validate(result["result"]),
|
||||
"snps": [GwasSnpOut.model_validate(s) for s in result["snps"]],
|
||||
"qtls": [QtlOut.model_validate(q) for q in result["qtls"]],
|
||||
})
|
||||
|
||||
|
||||
@StatisticsRouter.post("/gwas-qtl-x-e", summary="QTL×E 分层 GWAS + 稳定性判定(计算端点不建表)")
|
||||
async def gwas_qtl_x_e(
|
||||
data: GwasQtlXEIn,
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:query"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
return SuccessResponse(await StatisticsService(auth, db).gwas_qtl_x_e(
|
||||
data.dataset_id, data.trait_id, data.trait_code, data.year,
|
||||
data.method, data.maf_min, data.n_pc, data.sig_level, data.qtl_window,
|
||||
data.env_dim,
|
||||
))
|
||||
|
||||
|
||||
@StatisticsRouter.post("/qtl", summary="录入已知 QTL")
|
||||
async def qtl_create(
|
||||
data: QtlIn,
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:create"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
return SuccessResponse(await StatisticsService(auth, db).qtl_create(data))
|
||||
|
||||
|
||||
@StatisticsRouter.put("/qtl/{qtl_id}", summary="更新 QTL")
|
||||
async def qtl_update(
|
||||
qtl_id: int,
|
||||
data: QtlIn,
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:create"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
obj = await StatisticsService(auth, db).qtl_update(qtl_id, data)
|
||||
return SuccessResponse(QtlOut.model_validate(obj))
|
||||
|
||||
|
||||
@StatisticsRouter.delete("/qtl/delete", summary="删除 QTL")
|
||||
async def qtl_delete(
|
||||
ids: Annotated[list[int], Body(description="ID列表")],
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:delete"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
await StatisticsService(auth, db).qtl_delete(ids)
|
||||
return SuccessResponse()
|
||||
|
||||
|
||||
@StatisticsRouter.get("/qtl", summary="QTL 列表(可按性状)")
|
||||
async def qtl_list(
|
||||
trait_id: int | None = Query(None, description="按性状过滤"),
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:query"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
objs = await StatisticsService(auth, db).qtl_list(trait_id)
|
||||
return SuccessResponse([QtlOut.model_validate(o) for o in objs])
|
||||
|
||||
|
||||
@StatisticsRouter.get("/qtl/{qtl_id}", summary="QTL 详情")
|
||||
async def qtl_detail(
|
||||
qtl_id: int,
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:query"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
obj = await StatisticsService(auth, db).qtl_detail(qtl_id)
|
||||
return SuccessResponse(QtlOut.model_validate(obj))
|
||||
|
||||
|
||||
@StatisticsRouter.post("/mas-panel", summary="创建 MAS 标记辅助选择面板")
|
||||
async def mas_panel_create(
|
||||
data: MasPanelIn,
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:create"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
return SuccessResponse(await StatisticsService(auth, db).mas_panel_create(data))
|
||||
|
||||
|
||||
@StatisticsRouter.put("/mas-panel/{panel_id}", summary="更新 MAS 面板")
|
||||
async def mas_panel_update(
|
||||
panel_id: int,
|
||||
data: MasPanelIn,
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:create"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
obj = await StatisticsService(auth, db).mas_panel_update(panel_id, data)
|
||||
return SuccessResponse(MasPanelOut.model_validate(obj))
|
||||
|
||||
|
||||
@StatisticsRouter.delete("/mas-panel/delete", summary="删除 MAS 面板")
|
||||
async def mas_panel_delete(
|
||||
ids: Annotated[list[int], Body(description="ID列表")],
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:delete"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
await StatisticsService(auth, db).mas_panel_delete(ids)
|
||||
return SuccessResponse()
|
||||
|
||||
|
||||
@StatisticsRouter.get("/mas-panel", summary="MAS 面板列表(可按性状)")
|
||||
async def mas_panel_list(
|
||||
trait_id: int | None = Query(None, description="按性状过滤"),
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:query"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
objs = await StatisticsService(auth, db).mas_panel_list(trait_id)
|
||||
return SuccessResponse([MasPanelOut.model_validate(o) for o in objs])
|
||||
|
||||
|
||||
@StatisticsRouter.get("/mas-panel/{panel_id}", summary="MAS 面板详情(含标记)")
|
||||
async def mas_panel_detail(
|
||||
panel_id: int,
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:query"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
result = await StatisticsService(auth, db).mas_panel_detail(panel_id)
|
||||
return SuccessResponse({
|
||||
"panel": MasPanelOut.model_validate(result["panel"]),
|
||||
"markers": [m for m in result["markers"]],
|
||||
})
|
||||
|
||||
|
||||
@StatisticsRouter.post("/mas-panel/{panel_id}/markers", summary="设置面板标记(全量替换)")
|
||||
async def mas_panel_set_markers(
|
||||
panel_id: int,
|
||||
data: MasPanelSetMarkersIn,
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["module_bre:statistics:create"])),
|
||||
db: AsyncSession = Depends(db_getter),
|
||||
):
|
||||
n = await StatisticsService(auth, db).mas_panel_set_markers(panel_id, data.markers)
|
||||
return SuccessResponse({"panel_id": panel_id, "n_markers": n})
|
||||
Reference in New Issue
Block a user