chore(ECR-001,ECR-003): 关闭结构对齐并清理 outlook JSON 旧键

正式关闭 ECR-001/002;删除 fortune 双写与 lucky 字段,H5 改读 outlook/boost。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
jackyu66git
2026-08-05 20:39:34 +08:00
co-authored by Cursor
parent 69fa1b85d6
commit 67701d1f7b
22 changed files with 286 additions and 133 deletions
@@ -78,8 +78,11 @@ func TestFlowStarDeepAccess(t *testing.T) {
if sum["headline"] == nil || sum["headline"] == "" {
t.Fatalf("missing headline: %#v", sum)
}
if sum["fortune"] == nil || sum["planets"] == nil {
t.Fatalf("expected fortune+planets in star summary: %#v", sum)
if sum["outlook"] == nil || sum["planets"] == nil {
t.Fatalf("expected outlook+planets in star summary: %#v", sum)
}
if sum["fortune"] != nil {
t.Fatal("legacy fortune key must be removed (ECR-003)")
}
reportID := rep["id"].(string)
+6 -8
View File
@@ -70,6 +70,7 @@ func BuildWith(opts BuildOpts) (Output, error) {
}
fort := outlook.Build(chart, asOf)
daily := fort.Daily
fortMap := fort.AsMap()
planetsOut := make([]map[string]any, 0, len(chart.Planets))
for _, p := range chart.Planets {
@@ -114,10 +115,8 @@ func BuildWith(opts BuildOpts) (Output, error) {
},
"planets": planetsOut,
"aspects_preview": aspectPreview,
// "fortune" kept for client compat; prefer "outlook" (ECR-002).
"fortune": fort.AsMap(),
"outlook": fort.AsMap(),
"transits": fort.AsMap()["transits"],
"outlook": fortMap,
"transits": fortMap["transits"],
"daily_soft": map[string]any{
"title": daily.Title, "focus": daily.Focus, "tip": daily.Tip,
"energy": daily.Score, "note": daily.Label, "score": daily.Score, "label": daily.Label,
@@ -152,9 +151,9 @@ func BuildWith(opts BuildOpts) (Output, error) {
section("年运详解", fort.Yearly.Tip+" "+fort.Yearly.Caution, []string{
fmt.Sprintf("综合分 %d%s", fort.Yearly.Score, fort.Yearly.Label),
fmt.Sprintf("感情 %d · 事业 %d · 财务 %d · 心情 %d", fort.Yearly.Dims["love"], fort.Yearly.Dims["career"], fort.Yearly.Dims["money"], fort.Yearly.Dims["mood"]),
fort.Yearly.Lucky,
fort.Yearly.Boost,
}),
section("一生运势", fort.Lifetime.Tip, []string{fort.Lifetime.Caution, fort.Lifetime.Lucky}),
section("一生运势", fort.Lifetime.Tip, []string{fort.Lifetime.Caution, fort.Lifetime.Boost}),
section("关系互动", pack.RelationDeep, pack.RelationBullets),
section("事业与学习节奏", pack.CareerDeep, pack.CareerBullets),
section("成长方向", pack.GrowthDeep, pack.GrowthBullets),
@@ -171,8 +170,7 @@ func BuildWith(opts BuildOpts) (Output, error) {
"behavior_pattern": pack.SunDeep,
"relation_style": pack.RelationDeep,
"growth_direction": pack.GrowthDeep,
"fortune_detail": fort.AsMap(), // compat
"outlook_detail": fort.AsMap(),
"outlook_detail": fortMap,
}
return Output{Summary: summary, Detail: detail}, nil
}
+21 -6
View File
@@ -25,8 +25,11 @@ func TestBuildDeterministic(t *testing.T) {
if a.Summary["sun_sign"] == nil || a.Detail["sections"] == nil {
t.Fatal("missing fields")
}
if a.Summary["sign_cards"] == nil || a.Summary["fortune"] == nil || a.Summary["planets"] == nil {
t.Fatal("missing sign_cards, fortune or planets")
if a.Summary["sign_cards"] == nil || a.Summary["outlook"] == nil || a.Summary["planets"] == nil {
t.Fatal("missing sign_cards, outlook or planets")
}
if a.Summary["fortune"] != nil {
t.Fatal("legacy fortune key must be removed (ECR-003)")
}
if a.Summary["aspects_preview"] == nil {
t.Fatal("missing aspects_preview")
@@ -66,18 +69,30 @@ func TestBuildWithBirthTimeChangesRise(t *testing.T) {
_ = b.Summary["rise_sign"]
}
func TestBuildFortuneScores(t *testing.T) {
func TestBuildOutlookScores(t *testing.T) {
birth := time.Date(1990, 5, 12, 0, 0, 0, 0, time.UTC)
out, err := BuildWith(BuildOpts{Birth: birth, Name: "测", AsOf: time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)})
if err != nil {
t.Fatal(err)
}
fort, ok := out.Summary["fortune"].(map[string]any)
bundle, ok := out.Summary["outlook"].(map[string]any)
if !ok {
t.Fatal("fortune missing")
t.Fatal("outlook missing")
}
daily, ok := fort["daily"].(map[string]any)
daily, ok := bundle["daily"].(map[string]any)
if !ok || daily["score"] == nil {
t.Fatal("daily score missing")
}
if daily["lucky"] != nil {
t.Fatal("legacy lucky key must be removed")
}
if daily["boost"] == nil {
t.Fatal("boost missing")
}
if out.Detail["fortune_detail"] != nil {
t.Fatal("legacy fortune_detail must be removed")
}
if out.Detail["outlook_detail"] == nil {
t.Fatal("outlook_detail missing")
}
}
+11 -11
View File
@@ -9,7 +9,7 @@ import (
"github.com/yuxingu/digital-psychology/apps/api/internal/star/natal"
)
// Period is one fortune block.
// Period is one outlook block (daily…lifetime).
type Period struct {
Key string `json:"key"`
Title string `json:"title"`
@@ -17,7 +17,7 @@ type Period struct {
Label string `json:"label"`
Dims map[string]int `json:"dims"`
Tip string `json:"tip"`
Lucky string `json:"lucky"`
Boost string `json:"boost"` // soft accent tip (was lucky; ECR-003)
Caution string `json:"caution"`
Focus string `json:"focus"`
}
@@ -40,7 +40,7 @@ type Bundle struct {
Transits []Transit `json:"transits"`
}
// Build returns fortune for natal chart as of asOf (date matters).
// Build returns period outlook for natal chart as of asOf (date matters).
func Build(chart natal.Chart, asOf time.Time) Bundle {
if asOf.IsZero() {
asOf = time.Now()
@@ -118,7 +118,7 @@ func lifetime(chart natal.Chart) Period {
"money": 48 + (seed*5)%42, "mood": 55 + (seed*7)%35,
},
Tip: tip, Focus: "人生阶段",
Lucky: "长期复盘 · 边界清晰",
Boost: "长期复盘 · 边界清晰",
Caution: "避免把阶段标签当成宿命;可调整节奏与选择。",
}
}
@@ -197,7 +197,7 @@ func period(key, title string, seed int, sun, moon float64, chart natal.Chart, f
"love": love, "career": career, "money": money, "mood": mood,
},
Tip: tips[i], Focus: focuses[i%len(focuses)],
Lucky: luckyFrom(seed),
Boost: boostFrom(seed),
Caution: cautionFrom(seed, chart),
}
}
@@ -205,19 +205,19 @@ func period(key, title string, seed int, sun, moon float64, chart natal.Chart, f
func scoreLabel(s int) string {
switch {
case s >= 85:
return "大吉"
return "高能"
case s >= 75:
return ""
return "顺畅"
case s >= 65:
return "中平偏吉"
return "偏顺"
case s >= 55:
return "平稳"
default:
return "需谨慎"
return "宜缓行"
}
}
func luckyFrom(seed int) string {
func boostFrom(seed int) string {
colors := []string{"红色", "金色", "蓝色", "绿色", "紫色", "白色"}
nums := []string{"3", "6", "7", "8", "9"}
return colors[seed%len(colors)] + " · 数字 " + nums[seed%len(nums)]
@@ -256,6 +256,6 @@ func (b Bundle) AsMap() map[string]any {
func periodMap(p Period) map[string]any {
return map[string]any{
"key": p.Key, "title": p.Title, "score": p.Score, "label": p.Label,
"dims": p.Dims, "tip": p.Tip, "lucky": p.Lucky, "caution": p.Caution, "focus": p.Focus,
"dims": p.Dims, "tip": p.Tip, "boost": p.Boost, "caution": p.Caution, "focus": p.Focus,
}
}