Files
admin 87ab06221c 综合训练(多物种合并模型)全链路:合并打包/独立版本序列/App 覆盖互斥 + 管理端发布入口
- 后端:/admin/trainings/combined 发起(≥2 数据集、类别重映射、防重名、负样本单份);
  model_training/model_version 加 kind+dataset_ids(迁移 v14),综合任务 dataset_id=0、
  文件基名 combined(_n)、版本序列独立;训练列表补 published 标记
- 管理端:数据训练页工具栏发起综合训练;横幅常驻进行中任务 + 每档最近一条已结束任务,
  成功未发布给「发布模型」入口(可关闭收起)
- App:目录解析 kind/datasetIds、激活覆盖互斥、自动更新退场改目标档待办横幅手动一键下载
2026-09-09 15:45:51 +08:00

88 lines
3.0 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package service
import (
"context"
"encoding/json"
"net/url"
"observer-server/biz/consts"
"observer-server/biz/dao"
"observer-server/biz/model/dto"
)
// modelVersionService 模型版本业务:管理端无模型管理界面(2026-08-26 决策),
// 版本记录仅支撑客户端模型目录(App 多模型下载热更新)。文件布局见 common/workspace.go。
type modelVersionService struct{}
var ModelVersion = &modelVersionService{}
// ClientCatalog 客户端模型目录:全部数据集当前生效模型 + 综合模型(App 多模型下载热更新)。
// 双档位(2026-09-03):每数据集 s/n 两档 is_latest=1 各一条;downloadUrl 复用 /download 静态托管
// /download/trainings/<基名>.tfliten 档带 _n 后缀,基名前缀空回退数据集名)。
// 综合模型(2026-09-09):kind=combined 行 dataset_id=0,条目 datasetName=综合、fileBase=combined(+_n)、
// 带 datasetIds 覆盖列表,无封面。
func (s *modelVersionService) ClientCatalog(ctx context.Context) (*dto.ModelCatalogRes, error) {
list, err := dao.ModelVersion.ListAllLatest(ctx)
if err != nil {
return nil, err
}
names := Training.datasetNameMap(ctx)
modelNames := Training.datasetModelNameMap(ctx)
items := make([]*dto.ModelCatalogItem, 0, len(list))
for _, v := range list {
kind := v.Kind
if kind == "" {
kind = consts.TrainingKindSpecies
}
var datasetName, fileBase, coverUrl string
var datasetIds []int64
if kind == consts.TrainingKindCombined {
datasetName = "综合"
fileBase = consts.TrainingCombinedBase
if v.Variant == consts.TrainingVariantN {
fileBase += consts.TrainingVariantNFileSuffix
}
if err := json.Unmarshal([]byte(v.DatasetIds), &datasetIds); err != nil || datasetIds == nil {
datasetIds = []int64{}
}
} else {
datasetName = names[v.DatasetId]
if datasetName == "" {
continue
}
// 双档位:每数据集最多 2 条生效记录(is_latest 按 (数据集,档位) 各一),n 档 downloadUrl 带 _n 后缀;
// cover 属数据集(两档同物种同图源),沿用基名前缀
fileBase = modelNames[v.DatasetId]
if v.Variant == consts.TrainingVariantN {
fileBase += consts.TrainingVariantNFileSuffix
}
coverUrl = "/api/v1/app/cover?namePrefix=" + url.PathEscape(modelNames[v.DatasetId])
}
items = append(items, &dto.ModelCatalogItem{
DatasetId: v.DatasetId,
DatasetName: datasetName,
Variant: v.Variant,
Kind: kind,
DatasetIds: datasetIds,
Version: v.Version,
Labels: parseLabels(v.Labels),
SizeBytes: v.SizeBytes,
Sha256: v.Sha256,
Notes: v.Notes,
PublishedAt: v.CreatedAt,
DownloadUrl: "/download/trainings/" + fileBase + ".tflite",
CoverUrl: coverUrl,
})
}
return &dto.ModelCatalogRes{List: items}, nil
}
// parseLabels 解析类别名 JSON 数组
func parseLabels(s string) []string {
var out []string
if err := json.Unmarshal([]byte(s), &out); err != nil || len(out) == 0 {
return []string{"class0", "class1"}
}
return out
}