补全页的类别候选取「库里在用的 ∪ 静态表」
这一页原来只列静态表(30 个 MapKit 枚举),库里真在用的值不在候选里—— 真机自己写的中文类别(一般道路 / 彩票店 / 租车点 / 美食街)只能手打, 一不小心就挑个名字相近的静态枚举,跟已有数据并存成两种,同一个意思分两处统计。 现在三段拼,同一个值只出现一次: 1. 数据集里在用的,按地点数降序,标「中文名(在用 N)」 2. 真机用过的 12 种(当前数据里还没有的),标「真机用过」 3. 其余常见 MapKit 类别 在用的那份信息更准,所以顶掉静态表里的说明——既在用又在静态表里的只出现一次。 真实数据实测:36 项 = 16 在用 + 18 静态 +(不动)+(其它「自己写」),零重复; 排在前面的是 餐厅(在用 51)/ 商店(在用 29)/ … / 一般道路(在用 5)。
This commit is contained in:
+46
-6
@@ -206,6 +206,11 @@ func (s *Server) adminFillupPage(w http.ResponseWriter, r *http.Request) {
|
|||||||
s.serverError(w, r, err)
|
s.serverError(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
usedCats, err := s.usedCategories(ctx, ds.ID)
|
||||||
|
if err != nil {
|
||||||
|
s.serverError(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
total := len(rows)
|
total := len(rows)
|
||||||
hinted := 0
|
hinted := 0
|
||||||
for _, row := range rows {
|
for _, row := range rows {
|
||||||
@@ -223,7 +228,7 @@ func (s *Server) adminFillupPage(w http.ResponseWriter, r *http.Request) {
|
|||||||
rows = rows[from:to]
|
rows = rows[from:to]
|
||||||
|
|
||||||
s.render(w, "admin/fillup", FillupData{
|
s.render(w, "admin/fillup", FillupData{
|
||||||
Page: p, Rows: rows, Activities: acts, Categories: fillupCategories(),
|
Page: p, Rows: rows, Activities: acts, Categories: fillupCategories(usedCats),
|
||||||
Total: total, WithHint: hinted, PageNo: pageNo, PageCount: pageCount,
|
Total: total, WithHint: hinted, PageNo: pageNo, PageCount: pageCount,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -255,8 +260,32 @@ type fillupCategory struct {
|
|||||||
Label string
|
Label string
|
||||||
}
|
}
|
||||||
|
|
||||||
// fillupCategories 是类别下拉的选项:真机用过的 12 种在前,其余常见 MapKit 类别在后。
|
// usedCategories 列出这个数据集里真在用的类别(按地点数从多到少),排在静态表前面。
|
||||||
func fillupCategories() []fillupCategory {
|
// 候选得先跟已有数据对得上——否则用户会在这一页挑一个名字相近的静态枚举,
|
||||||
|
// 跟库里已有的值并存成两种,同一个意思分两处统计。
|
||||||
|
func (s *Server) usedCategories(ctx context.Context, datasetID int64) ([]fillupCategory, error) {
|
||||||
|
rows, err := s.db.QueryContext(ctx, `SELECT poi_category, count(*) FROM places
|
||||||
|
WHERE dataset_id=$1 AND COALESCE(poi_category,'')<>'' GROUP BY 1 ORDER BY 2 DESC, 1`, datasetID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var out []fillupCategory
|
||||||
|
for rows.Next() {
|
||||||
|
var v string
|
||||||
|
var n int
|
||||||
|
if err := rows.Scan(&v, &n); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// 值可能是 MKPOICategory 枚举,也可能是真机自己写的中文——poiCategoryLabel 两种都认
|
||||||
|
out = append(out, fillupCategory{v, fmt.Sprintf("%s(在用 %d)", poiCategoryLabel(v), n)})
|
||||||
|
}
|
||||||
|
return out, rows.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
// fillupCategories 是类别下拉的选项:数据集里真在用的排最前面,再跟真机用过的 12 种、
|
||||||
|
// 其余常见 MapKit 类别。同一个值只出现一次——在用的那份信息更准,就顶掉静态表里的说明。
|
||||||
|
func fillupCategories(inUse []fillupCategory) []fillupCategory {
|
||||||
known := []string{
|
known := []string{
|
||||||
"MKPOICategoryRestaurant", "MKPOICategoryStore", "MKPOICategoryPublicTransport",
|
"MKPOICategoryRestaurant", "MKPOICategoryStore", "MKPOICategoryPublicTransport",
|
||||||
"MKPOICategoryHotel", "MKPOICategoryLandmark", "MKPOICategoryFoodMarket",
|
"MKPOICategoryHotel", "MKPOICategoryLandmark", "MKPOICategoryFoodMarket",
|
||||||
@@ -271,12 +300,23 @@ func fillupCategories() []fillupCategory {
|
|||||||
"MKPOICategoryNationalPark", "MKPOICategoryAquarium", "MKPOICategoryAmusementPark",
|
"MKPOICategoryNationalPark", "MKPOICategoryAquarium", "MKPOICategoryAmusementPark",
|
||||||
"MKPOICategoryGasStation", "MKPOICategoryCarRental", "MKPOICategoryMarina",
|
"MKPOICategoryGasStation", "MKPOICategoryCarRental", "MKPOICategoryMarina",
|
||||||
}
|
}
|
||||||
out := make([]fillupCategory, 0, len(known)+len(extra))
|
seen := make(map[string]bool, len(inUse)+len(known)+len(extra))
|
||||||
|
out := make([]fillupCategory, 0, len(inUse)+len(known)+len(extra))
|
||||||
|
push := func(c fillupCategory) {
|
||||||
|
if c.Value == "" || seen[c.Value] {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
seen[c.Value] = true
|
||||||
|
out = append(out, c)
|
||||||
|
}
|
||||||
|
for _, c := range inUse {
|
||||||
|
push(c)
|
||||||
|
}
|
||||||
for _, c := range known {
|
for _, c := range known {
|
||||||
out = append(out, fillupCategory{c, poiName(c) + "(真机用过)"})
|
push(fillupCategory{c, poiName(c) + "(真机用过)"})
|
||||||
}
|
}
|
||||||
for _, c := range extra {
|
for _, c := range extra {
|
||||||
out = append(out, fillupCategory{c, poiName(c)})
|
push(fillupCategory{c, poiName(c)})
|
||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -322,3 +322,57 @@ func TestFillupKeepsHandwrittenCandidates(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 补全页的类别候选要「数据里在用的 ∪ 静态表」:库里真在用的值(尤其是真机自己写的中文)
|
||||||
|
// 原来在这一页根本选不到,只能手打,跟静态枚举并存成两种,同一个意思分两处统计。
|
||||||
|
func TestFillupCategoriesIncludeUsedOnes(t *testing.T) {
|
||||||
|
f := newMovementFixture(t)
|
||||||
|
fillupPlace(t, f) // 补全页得有行才会渲染出下拉
|
||||||
|
for _, p := range []struct {
|
||||||
|
pk int
|
||||||
|
cat string
|
||||||
|
}{
|
||||||
|
{691001, "测试类别戊"}, {691002, "测试类别戊"}, {691003, "MKPOICategoryRestaurant"},
|
||||||
|
} {
|
||||||
|
f.mustExec(t, `INSERT INTO places (dataset_id, src_pk, name, lat, lon, poi_category)
|
||||||
|
VALUES ($1,$2,'类别候选测试点',30,120,$3)`, f.dsID, p.pk, p.cat)
|
||||||
|
}
|
||||||
|
t.Cleanup(func() {
|
||||||
|
f.pg.ExecContext(f.ctx, `DELETE FROM places WHERE dataset_id=$1 AND src_pk BETWEEN 691001 AND 691003`, f.dsID)
|
||||||
|
})
|
||||||
|
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
req := httptest.NewRequest("GET", "/admin/fillup", nil).WithContext(f.ctx)
|
||||||
|
f.srv.adminFillupPage(rec, req)
|
||||||
|
if rec.Code != http.StatusOK {
|
||||||
|
t.Fatalf("渲染失败: %d", rec.Code)
|
||||||
|
}
|
||||||
|
body := rec.Body.String()
|
||||||
|
|
||||||
|
// 下拉每行渲染一份,要数重复就得只看其中一个
|
||||||
|
seg := body[strings.Index(body, `<select name="cat_`):]
|
||||||
|
if end := strings.Index(seg, "</select>"); end > 0 {
|
||||||
|
seg = seg[:end]
|
||||||
|
} else {
|
||||||
|
t.Fatal("没找到类别下拉")
|
||||||
|
}
|
||||||
|
if !strings.Contains(seg, `value="测试类别戊"`) {
|
||||||
|
t.Fatal("库里在用的中文类别应出现在下拉里")
|
||||||
|
}
|
||||||
|
if !strings.Contains(seg, "测试类别戊(在用 2)") {
|
||||||
|
t.Error("在用类别应标出它被几个地点用着")
|
||||||
|
}
|
||||||
|
// 既在用又躺在静态表里的,只能出现一次(在用那份信息更准)
|
||||||
|
if n := strings.Count(seg, `value="MKPOICategoryRestaurant"`); n != 1 {
|
||||||
|
t.Fatalf("既在用又在静态表的值应只出现一次,实际 %d 次", n)
|
||||||
|
}
|
||||||
|
// 静态表里的值一旦真在用,就该显示成「在用」而不是「真机用过」。
|
||||||
|
// 不钉具体数字:夹具导的是真实备份,那个类别本来就有几十个地点在用。
|
||||||
|
if !strings.Contains(seg, `value="MKPOICategoryRestaurant">餐厅(在用 `) {
|
||||||
|
t.Error("静态表里的值一旦真在用,就该显示成「在用」而不是「真机用过」")
|
||||||
|
}
|
||||||
|
// 在用的排最前面:排在静态表里那些还没用过的值之前
|
||||||
|
if a, b := strings.Index(body, `value="测试类别戊"`), strings.Index(body, `value="MKPOICategoryCafe"`); a < 0 || b < 0 || a > b {
|
||||||
|
t.Errorf("在用类别应排在静态表前面(在用=%d 静态=%d)", a, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user