落地输入合规、探索题库、报告日/时辰刷新、账号头像、OEJTS 量表,并补齐 H5 埋点与 Admin 漏斗;同步 ESS 工件、切至自建 Git、清理 GitHub Actions。 Co-authored-by: Cursor <cursoragent@cursor.com>
183 lines
7.1 KiB
Go
183 lines
7.1 KiB
Go
// 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().In(time.FixedZone("CST", 8*3600))
|
||
} else {
|
||
asOf = asOf.In(time.FixedZone("CST", 8*3600))
|
||
}
|
||
fort := outlook.Build(chart, asOf)
|
||
daily := fort.Daily
|
||
fortMap := fort.AsMap()
|
||
dayKey := asOf.Format("2006-01-02")
|
||
validUntil := time.Date(asOf.Year(), asOf.Month(), asOf.Day()+1, 0, 0, 0, 0, asOf.Location())
|
||
|
||
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,
|
||
"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,
|
||
},
|
||
"as_of": dayKey,
|
||
"valid_until": validUntil.Format(time.RFC3339),
|
||
"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.Boost,
|
||
}),
|
||
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),
|
||
},
|
||
"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,
|
||
"outlook_detail": fortMap,
|
||
}
|
||
return Output{Summary: summary, Detail: detail}, nil
|
||
}
|