Files
digital-psychology/apps/api/internal/star/engine.go
T
jackyu66gitandCursor 19d3cd5945 refactor(ECR-001): 接入 ESS 并完成结构对齐 Phase A–E
绑定 ESS 双轨治理,拆分超大 H5 页与 Go 引擎,抽出 membership 服务,
并将 star/fortune 重命名为 outlook(JSON 双写兼容);同时修复 /psy API 代理与首页 + 菜单层级。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-05 17:51:40 +08:00

179 lines
6.9 KiB
Go
Raw 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 star builds 星座 reports (natal chart · period outlook · deep copy).
package star
import (
"fmt"
"time"
"github.com/yuxingu/digital-psychology/apps/api/internal/star/natal"
"github.com/yuxingu/digital-psychology/apps/api/internal/star/outlook"
)
// Output is free summary + gated detail.
type Output struct {
Summary map[string]any `json:"summary"`
Detail map[string]any `json:"detail"`
}
// BuildOpts configures report generation.
type BuildOpts struct {
Birth time.Time
BirthTime *string
BirthPlace *string
Name string
AsOf time.Time // period outlook anchor; zero = now
}
// Build generates StarProfile from birth date (compat wrapper).
func Build(birth time.Time, birthTime *string, displayName string) (Output, error) {
return BuildWith(BuildOpts{Birth: birth, BirthTime: birthTime, Name: displayName})
}
// BuildWith generates a full star report.
func BuildWith(opts BuildOpts) (Output, error) {
name := opts.Name
if name == "" {
name = "你"
}
chart, err := natal.Compute(opts.Birth, opts.BirthTime, opts.BirthPlace)
if err != nil {
return Output{}, err
}
sun := chart.Sun
moon := chart.Moon
rise := chart.Rise
pack := packs[sun.SignKey]
if pack.Label == "" {
pack = defaultPack(signMeta{Key: sun.SignKey, Label: sun.Sign, Element: sun.Element, Modality: sun.Modality})
}
moonPack := packs[moon.SignKey]
risePack := packs[rise.SignKey]
signCards := []map[string]any{
signCard("sun", "太阳星座", sun, pack.SunTeaser, pack.Keywords),
signCard("moon", "月亮星座", moon, fmt.Sprintf("情绪底色更偏「%s」", moon.Sign), moonPack.Keywords),
signCard("rise", "上升星座", rise, fmt.Sprintf("第一印象更偏「%s」", rise.Sign), risePack.Keywords),
}
dims := []map[string]any{
{"key": "sun", "title": "太阳风格", "teaser": pack.SunTeaser, "score": pack.Scores["sun"]},
{"key": "moon", "title": "月亮节奏", "teaser": fmt.Sprintf("情绪底色更偏「%s」", moon.Sign), "score": pack.Scores["moon"]},
{"key": "rise", "title": "上升表达", "teaser": fmt.Sprintf("第一印象更偏「%s」", rise.Sign), "score": pack.Scores["rise"]},
{"key": "relation", "title": "关系互动", "teaser": pack.RelationTeaser, "score": pack.Scores["relation"]},
{"key": "career", "title": "事业节奏", "teaser": pack.CareerTeaser, "score": pack.Scores["career"]},
{"key": "growth", "title": "成长方向", "teaser": pack.GrowthTeaser, "score": pack.Scores["growth"]},
}
asOf := opts.AsOf
if asOf.IsZero() {
asOf = time.Now()
}
fort := outlook.Build(chart, asOf)
daily := fort.Daily
planetsOut := make([]map[string]any, 0, len(chart.Planets))
for _, p := range chart.Planets {
planetsOut = append(planetsOut, map[string]any{
"key": p.Key, "title": p.Title, "sign": p.Sign, "sign_key": p.SignKey,
"degree": fmt.Sprintf("%.1f°", p.Degree), "house": p.House,
"element": p.Element, "modality": p.Modality, "lon": p.Lon,
})
}
housesOut := make([]map[string]any, 0, len(chart.Houses))
for _, h := range chart.Houses {
// Whole-sign house cusp ≈ asc sign start + (n-1)*30
cusp := float64((signIndexOf(h.Key)) * 30)
housesOut = append(housesOut, map[string]any{
"num": h.Num, "sign": h.Sign, "sign_key": h.Key, "cusp_lon": cusp,
})
}
aspects := natal.Aspects(chart)
aspectMaps := natal.AspectsAsMaps(aspects)
previewN := min(4, len(aspectMaps))
aspectPreview := aspectMaps[:previewN]
summary := map[string]any{
"title": "星座·基础",
"style_label": pack.Label,
"headline": fmt.Sprintf("%s的星座更偏「%s」", name, pack.Label),
"one_liner": pack.OneLiner,
"overview": fmt.Sprintf("%s%s(太阳:%s;月亮:%s;上升:%s)。", name, pack.Overview, sun.Sign, moon.Sign, rise.Sign),
"life_tip": pack.LifeTip,
"keywords": pack.Keywords,
"dimensions": dims,
"sign_cards": signCards,
"sun_sign": sun.Sign,
"moon_sign": moon.Sign,
"rise_sign": rise.Sign,
"chart": map[string]any{
"note": chart.Note, "place": chart.PlaceLabel,
"has_time": chart.HasTime, "has_place": chart.HasPlace,
"lat": chart.Lat, "lng": chart.Lng,
"asc_lon": rise.Lon, "houses": housesOut,
},
"planets": planetsOut,
"aspects_preview": aspectPreview,
// "fortune" kept for client compat; prefer "outlook" (ECR-002).
"fortune": fort.AsMap(),
"outlook": fort.AsMap(),
"transits": fort.AsMap()["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,
},
"strengths_preview": pack.Strengths[:min(3, len(pack.Strengths))],
"blind_spots_preview": []string{"完整相位与年运详解见深度版。"},
"interaction_tags": []string{
fmt.Sprintf("太阳·%s", sun.Sign),
fmt.Sprintf("月亮·%s", moon.Sign),
fmt.Sprintf("上升·%s", rise.Sign),
pack.Label,
},
}
aspectBullets := make([]string, 0, min(8, len(aspects)))
for i, a := range aspects {
if i >= 8 {
break
}
aspectBullets = append(aspectBullets, a.Label)
}
detail := map[string]any{
"title": "星座·完整分析",
"aspects": aspectMaps,
"sections": []map[string]any{
section("太阳星座 · "+sun.Sign, pack.SunDeep, pack.SunBullets),
section("月亮星座 · "+moon.Sign, fmt.Sprintf("情绪底色带有「%s」特质:%s", moon.Sign, moonPack.MoonDeep), moonPack.MoonBullets),
section("上升星座 · "+rise.Sign, fmt.Sprintf("对外第一印象偏「%s」:%s", rise.Sign, risePack.RiseDeep), risePack.RiseBullets),
section("行星相位要点", "主要相位帮助理解行动、情感与责任节奏之间的张力与互补。", aspectBullets),
section("行星落座", "落座与宫位是日常节奏的参照。", planetBullets(chart)),
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,
}),
section("一生运势", fort.Lifetime.Tip, []string{fort.Lifetime.Caution, fort.Lifetime.Lucky}),
section("关系互动", pack.RelationDeep, pack.RelationBullets),
section("事业与学习节奏", pack.CareerDeep, pack.CareerBullets),
section("成长方向", pack.GrowthDeep, pack.GrowthBullets),
},
"strengths": pack.Strengths,
"blind_spots": pack.BlindSpots,
"growth_plan": []map[string]any{
{"phase": "本周", "focus": pack.PlanWeek},
{"phase": "本月", "focus": pack.PlanMonth},
{"phase": "长期", "focus": pack.PlanLong},
},
"conversation_scripts": pack.Scripts,
"faq": pack.FAQ,
"behavior_pattern": pack.SunDeep,
"relation_style": pack.RelationDeep,
"growth_direction": pack.GrowthDeep,
"fortune_detail": fort.AsMap(), // compat
"outlook_detail": fort.AsMap(),
}
return Output{Summary: summary, Detail: detail}, nil
}