feat: 可扩展档案模式框架 + 内置 3C(电子) 模式
CI / Go (api) (pull_request) Successful in 13s
CI / Python (ingestion) (pull_request) Successful in 9s
CI / Migrations (postgres) (pull_request) Successful in 14s

- 新增 category.archive_kind 与 kind_field 字段模板表(迁移 0010)
- 种子 electronics 字段模板 + 3C 品类树(手机/笔记本/平板等)
- 后端按档案模式动态计算完整度/合格:食品沿用 food_detail,
  其它模式走 product.attributes + kind_field
- 新增 GET /api/kind-fields?kind= 接口
- 后台编辑页按品类模式动态渲染规格参数表单
- 公开详情页/接口输出带标签的规格表

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
sulaimaannaasif6866
2026-06-21 06:13:42 +00:00
parent 98eb01f4ea
commit 916bdd9c7c
15 changed files with 695 additions and 55 deletions
+37 -15
View File
@@ -2,6 +2,7 @@ package adminstore
import (
"context"
"encoding/json"
"math"
"time"
@@ -58,35 +59,56 @@ func (s *Store) computeQuality(ctx context.Context, q queryer, productID string)
var netCanonical *float64
var ingredients *string
var hasNutri, hasImage bool
var kind string
var attributes []byte
err := q.QueryRow(ctx, `
SELECT p.name, p.gtin, p.brand_id, p.category_id, p.net_content_canonical,
p.country_of_origin, f.ingredients_text,
p.country_of_origin, COALESCE(c.archive_kind, 'generic'), p.attributes,
f.ingredients_text,
(f.nutriments IS NOT NULL AND f.nutriments::text <> '{}'),
EXISTS (SELECT 1 FROM product_image pi WHERE pi.product_id = p.id)
FROM product p LEFT JOIN food_detail f ON f.product_id = p.id
FROM product p
LEFT JOIN category c ON c.id = p.category_id
LEFT JOIN food_detail f ON f.product_id = p.id
WHERE p.id = $1`, productID).Scan(
&name, &gtin, &brandID, &categoryID, &netCanonical, &country,
&ingredients, &hasNutri, &hasImage)
&kind, &attributes, &ingredients, &hasNutri, &hasImage)
if err != nil {
return 0, err
}
attrs := map[string]any{}
if len(attributes) > 0 {
_ = json.Unmarshal(attributes, &attrs)
}
qualified, err := s.kindQualifiedKeys(ctx, s.pool)
if err != nil {
return 0, err
}
qkeys := qualified[kind]
known := map[string]bool{
"name": name != nil && *name != "",
"gtin": gtin != nil && *gtin != "",
"brand": brandID != nil,
"category": categoryID != nil,
"net_content": netCanonical != nil,
"country_of_origin": country != nil && *country != "",
"nutriments": hasNutri,
"ingredients": ingredients != nil && *ingredients != "",
"image": hasImage,
}
for _, k := range qkeys {
known[k] = attrPresent(attrs, k)
}
keys := completenessKeys(kind, qkeys)
present := 0
bump := func(ok bool) {
if ok {
for _, k := range keys {
if known[k] {
present++
}
}
bump(name != nil && *name != "")
bump(gtin != nil && *gtin != "")
bump(brandID != nil)
bump(categoryID != nil)
bump(netCanonical != nil)
bump(country != nil && *country != "")
bump(hasNutri)
bump(ingredients != nil && *ingredients != "")
bump(hasImage)
completeness := float64(present) / float64(len(CompletenessFields))
completeness := float64(present) / float64(len(keys))
var sourceCount int
var sourceTrust *float64