B 設備管理(feature-device-mgmt-tdd): - POST /api/devices/:id/register + /unregister(owner 檢查 + representative 擋 + 已註冊擋 + SetRegistered 單欄翻轉,不碰 unpair 軟刪) - error codes ALREADY_REGISTERED / REPRESENTATIVE_DEVICE(409) - 不需 migration(registered_at 欄/index/讀寫已在 0005) C 模型共享(feature-model-sharing-tdd,security 深審 APPROVE): - migration 0006:models.visibility enum DEFAULT 'private'(零行為改變)+ model_shares 表 - canAccessModel single source(owner ∪ share ∪ public ∪ tenant):profile + download 共用 - GET /library(cursor keyset)/ GET /:id/profile(404 防列舉、GetWithOwner join name 不洩 email) / PATCH /:id/visibility(owner-only)/ shares CRUD / download 放寬 - tenant 因 OIDC 無 org claim 留 stub(恆空、安全預設;補 org claim 需重送 security 深審) reviewer 通過(B 三條紅線 / C security APPROVE 無 C/M)。130 dbtest 全綠、gosec 新檔 0。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
193 lines
8.1 KiB
Go
193 lines
8.1 KiB
Go
// presets.go — 7 個系統預設模型(B8)。
|
||
//
|
||
// 設計決策(使用者拍板「簡單版」):
|
||
// - metadata 寫死成 Go 常數,**不進 DB**。理由:preset 是固定公用資料,不該有 owner
|
||
// 隔離、不該能被刪、也無 seed 重複問題(多實例 / 重啟結果一致)。
|
||
// - 對應的 .nef 檔打包進 visionA-backend image(assets/preset-models/{id}.nef),
|
||
// download 走 visionA 自己的 storage handler(簡單版不簽 token,preset 公用、本不需授權)。
|
||
//
|
||
// 資料來源:local-tool/server/data/models.json(7 筆 metadata + nef 檔)。對映到 Model
|
||
// struct 既有欄位;models.json 有但 Model 無對應欄位的 metadata(quantization / license /
|
||
// version / author / taskType / categories / thumbnail)暫不落地(見 B8 回報)。
|
||
//
|
||
// StorageKey 格式 `models/preset/{id}.nef`:對 download handler 而言只是「preset 識別 +
|
||
// 對外 URL 檔名」用途,實際檔案由 PresetAssetsDir(api 層)提供,不經 LocalFS storage root。
|
||
package model
|
||
|
||
import "time"
|
||
|
||
// presetCreatedAt 是 7 個 preset 的固定建立時間(對齊 models.json 的 createdAt)。
|
||
// 寫死成常數讓 List/Get 回應在重啟 / 多實例間穩定(不用 time.Now(),避免每次不同)。
|
||
var presetCreatedAt = time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)
|
||
|
||
// presetStorageKey 依 preset id 組出對外用的 storage key(檔名穩定、URL 穩定)。
|
||
func presetStorageKey(id string) string {
|
||
return "models/preset/" + id + ".nef"
|
||
}
|
||
|
||
// presetModels 是寫死的 7 個系統預設模型(公用、無 owner、不可刪)。
|
||
//
|
||
// 欄位對映(models.json → Model):
|
||
// - name/description/framework 直接對映
|
||
// - InputShape:inputSize{width,height} → []int{1, 3, height, width}(NCHW,對齊
|
||
// conversion 端與 PG INT[] 既有慣例 [1,3,H,W])。preset 皆 RGB 影像模型,batch=1、channel=3。
|
||
// - Classes:labels 直接對映
|
||
// - TargetChip:supportedHardware[0](每筆只支援單一晶片)
|
||
// - FileSize:modelSize
|
||
// - Source = SourcePreset、OwnerUserID = ""(公用,非任何 user)
|
||
// - StorageKey = models/preset/{id}.nef(不設 FAAObjectKey;download 走 visionA 自己)
|
||
var presetModels = []*Model{
|
||
{
|
||
ID: "kl520-yolov5-detection",
|
||
Name: "YOLOv5 Detection (KL520)",
|
||
Description: "YOLOv5 object detection model compiled for Kneron KL520. No upsample variant optimized for NPU inference at 640x640 resolution.",
|
||
StorageKey: presetStorageKey("kl520-yolov5-detection"),
|
||
FileSize: 7200000,
|
||
TargetChip: "KL520",
|
||
InputShape: []int{1, 3, 640, 640},
|
||
Classes: []string{"person", "bicycle", "car", "motorcycle", "airplane", "bus", "train", "truck", "boat", "traffic light"},
|
||
Framework: "NEF",
|
||
Source: SourcePreset,
|
||
CreatedAt: presetCreatedAt,
|
||
UpdatedAt: presetCreatedAt,
|
||
UploadedAt: &presetCreatedAt,
|
||
},
|
||
{
|
||
ID: "kl520-fcos-detection",
|
||
Name: "FCOS Detection (KL520)",
|
||
Description: "FCOS (Fully Convolutional One-Stage) object detection with DarkNet53s backbone, compiled for KL520. Anchor-free detection at 512x512.",
|
||
StorageKey: presetStorageKey("kl520-fcos-detection"),
|
||
FileSize: 8900000,
|
||
TargetChip: "KL520",
|
||
InputShape: []int{1, 3, 512, 512},
|
||
Classes: []string{"person", "bicycle", "car", "motorcycle", "airplane", "bus", "train", "truck", "boat", "traffic light"},
|
||
Framework: "NEF",
|
||
Source: SourcePreset,
|
||
CreatedAt: presetCreatedAt,
|
||
UpdatedAt: presetCreatedAt,
|
||
UploadedAt: &presetCreatedAt,
|
||
},
|
||
{
|
||
ID: "kl520-ssd-face-detection",
|
||
Name: "SSD Face Detection (KL520)",
|
||
Description: "SSD-based face detection with landmark localization, compiled for KL520. Lightweight model suitable for face detection and alignment tasks.",
|
||
StorageKey: presetStorageKey("kl520-ssd-face-detection"),
|
||
FileSize: 1000000,
|
||
TargetChip: "KL520",
|
||
InputShape: []int{1, 3, 240, 320},
|
||
Classes: []string{"face"},
|
||
Framework: "NEF",
|
||
Source: SourcePreset,
|
||
CreatedAt: presetCreatedAt,
|
||
UpdatedAt: presetCreatedAt,
|
||
UploadedAt: &presetCreatedAt,
|
||
},
|
||
{
|
||
ID: "kl520-tiny-yolov3",
|
||
Name: "Tiny YOLOv3 (KL520)",
|
||
Description: "Tiny YOLOv3 object detection model compiled for KL520. Compact and fast model for general-purpose multi-object detection on edge devices.",
|
||
StorageKey: presetStorageKey("kl520-tiny-yolov3"),
|
||
FileSize: 9400000,
|
||
TargetChip: "KL520",
|
||
InputShape: []int{1, 3, 416, 416},
|
||
Classes: []string{"person", "bicycle", "car", "motorcycle", "airplane", "bus", "train", "truck", "boat", "traffic light"},
|
||
Framework: "NEF",
|
||
Source: SourcePreset,
|
||
CreatedAt: presetCreatedAt,
|
||
UpdatedAt: presetCreatedAt,
|
||
UploadedAt: &presetCreatedAt,
|
||
},
|
||
{
|
||
ID: "kl720-yolov5-detection",
|
||
Name: "YOLOv5 Detection (KL720)",
|
||
Description: "YOLOv5 object detection model compiled for Kneron KL720. No upsample variant optimized for KL720 NPU inference at 640x640 resolution with USB 3.0 throughput.",
|
||
StorageKey: presetStorageKey("kl720-yolov5-detection"),
|
||
FileSize: 10168348,
|
||
TargetChip: "KL720",
|
||
InputShape: []int{1, 3, 640, 640},
|
||
Classes: []string{"person", "bicycle", "car", "motorcycle", "airplane", "bus", "train", "truck", "boat", "traffic light"},
|
||
Framework: "NEF",
|
||
Source: SourcePreset,
|
||
CreatedAt: presetCreatedAt,
|
||
UpdatedAt: presetCreatedAt,
|
||
UploadedAt: &presetCreatedAt,
|
||
},
|
||
{
|
||
ID: "kl720-resnet18-classification",
|
||
Name: "ImageNet Classification ResNet18 (KL720)",
|
||
Description: "ResNet18-based image classification compiled for KL720. Supports 1000 ImageNet categories with fast inference via USB 3.0.",
|
||
StorageKey: presetStorageKey("kl720-resnet18-classification"),
|
||
FileSize: 12826804,
|
||
TargetChip: "KL720",
|
||
InputShape: []int{1, 3, 224, 224},
|
||
Classes: []string{"airplane", "automobile", "bird", "cat", "deer", "dog", "frog", "horse", "ship", "truck"},
|
||
Framework: "NEF",
|
||
Source: SourcePreset,
|
||
CreatedAt: presetCreatedAt,
|
||
UpdatedAt: presetCreatedAt,
|
||
UploadedAt: &presetCreatedAt,
|
||
},
|
||
{
|
||
ID: "kl720-fcos-detection",
|
||
Name: "FCOS Detection (KL720)",
|
||
Description: "FCOS (Fully Convolutional One-Stage) object detection with DarkNet53s backbone, compiled for KL720. Anchor-free detection at 512x512.",
|
||
StorageKey: presetStorageKey("kl720-fcos-detection"),
|
||
FileSize: 13004640,
|
||
TargetChip: "KL720",
|
||
InputShape: []int{1, 3, 512, 512},
|
||
Classes: []string{"person", "bicycle", "car", "motorcycle", "airplane", "bus", "train", "truck", "boat", "traffic light"},
|
||
Framework: "NEF",
|
||
Source: SourcePreset,
|
||
CreatedAt: presetCreatedAt,
|
||
UpdatedAt: presetCreatedAt,
|
||
UploadedAt: &presetCreatedAt,
|
||
},
|
||
}
|
||
|
||
// PresetModels 回傳所有系統預設模型的深拷貝(避免呼叫端意外改到共用常數)。
|
||
//
|
||
// 回傳順序固定(與宣告順序一致),讓 List 回應穩定、測試可確定性斷言。
|
||
func PresetModels() []*Model {
|
||
out := make([]*Model, 0, len(presetModels))
|
||
for _, m := range presetModels {
|
||
out = append(out, clonePreset(m))
|
||
}
|
||
return out
|
||
}
|
||
|
||
// PresetByID 依 id 取單一 preset 的深拷貝;非 preset id 回 (nil, false)。
|
||
func PresetByID(id string) (*Model, bool) {
|
||
for _, m := range presetModels {
|
||
if m.ID == id {
|
||
return clonePreset(m), true
|
||
}
|
||
}
|
||
return nil, false
|
||
}
|
||
|
||
// IsPresetID 判斷 id 是否為系統預設模型 id。
|
||
func IsPresetID(id string) bool {
|
||
_, ok := PresetByID(id)
|
||
return ok
|
||
}
|
||
|
||
// clonePreset 深拷貝一筆 preset(含 slice 欄位),避免外部修改污染共用常數。
|
||
func clonePreset(m *Model) *Model {
|
||
cp := *m
|
||
if m.InputShape != nil {
|
||
cp.InputShape = append([]int(nil), m.InputShape...)
|
||
}
|
||
if m.Classes != nil {
|
||
cp.Classes = append([]string(nil), m.Classes...)
|
||
}
|
||
// UploadedAt 指向共用的 presetCreatedAt;複製一份新指標避免別名共享。
|
||
if m.UploadedAt != nil {
|
||
t := *m.UploadedAt
|
||
cp.UploadedAt = &t
|
||
}
|
||
// preset 是公用模型,語意等同全平台可見(visibility=public)。
|
||
// 在此統一標記,preset 宣告區不必逐筆設 Visibility。
|
||
cp.Visibility = VisibilityPublic
|
||
return &cp
|
||
}
|