绑定 ESS 双轨治理,拆分超大 H5 页与 Go 引擎,抽出 membership 服务, 并将 star/fortune 重命名为 outlook(JSON 双写兼容);同时修复 /psy API 代理与首页 + 菜单层级。 Co-authored-by: Cursor <cursoragent@cursor.com>
148 lines
5.9 KiB
Go
148 lines
5.9 KiB
Go
// Package relation builds 人格匹配 / 合盘向 content from two profiles.
|
||
package relation
|
||
|
||
import (
|
||
"fmt"
|
||
"math"
|
||
"time"
|
||
|
||
"github.com/yuxingu/digital-psychology/apps/api/internal/portrait"
|
||
"github.com/yuxingu/digital-psychology/apps/api/internal/star"
|
||
"github.com/yuxingu/digital-psychology/apps/api/internal/star/natal"
|
||
"github.com/yuxingu/digital-psychology/apps/api/internal/star/synastry"
|
||
)
|
||
|
||
// Output is free summary + gated detail.
|
||
type Output struct {
|
||
Summary map[string]any
|
||
Detail map[string]any
|
||
}
|
||
|
||
// Build compares two profiles' exploration styles.
|
||
func Build(aBirth, bBirth time.Time, aName, bName string) Output {
|
||
return BuildWithType(aBirth, bBirth, aName, bName, "")
|
||
}
|
||
|
||
// BuildWithType adds relation-type flavored tips (partner/family/friend).
|
||
func BuildWithType(aBirth, bBirth time.Time, aName, bName, relationType string) Output {
|
||
return BuildFull(aBirth, bBirth, nil, nil, nil, nil, aName, bName, relationType)
|
||
}
|
||
|
||
// BuildFull includes birth time/place for natal synastry indices.
|
||
func BuildFull(aBirth, bBirth time.Time, aTime, bTime, aPlace, bPlace *string, aName, bName, relationType string) Output {
|
||
if aName == "" {
|
||
aName = "我"
|
||
}
|
||
if bName == "" {
|
||
bName = "TA"
|
||
}
|
||
pa := portrait.Build(aBirth, aName)
|
||
pb := portrait.Build(bBirth, bName)
|
||
|
||
aLabel := firstNonEmpty(str(pa.Summary["style_label"]), str(pa.Summary["headline"]))
|
||
bLabel := firstNonEmpty(str(pb.Summary["style_label"]), str(pb.Summary["headline"]))
|
||
aKeys := strSlice(pa.Summary["keywords"])
|
||
bKeys := strSlice(pb.Summary["keywords"])
|
||
aDims := dimMap(pa.Summary["dimensions"])
|
||
bDims := dimMap(pb.Summary["dimensions"])
|
||
|
||
compKey := complementarityKey(aLabel, bLabel)
|
||
comp := complementarityCopy(compKey, aName, bName, aLabel, bLabel)
|
||
|
||
dimCompare := []map[string]any{}
|
||
for _, key := range []string{"personality", "communication", "relation", "career", "emotion", "lifestyle"} {
|
||
title := dimTitle(key)
|
||
as, aok := aDims[key]
|
||
bs, bok := bDims[key]
|
||
if !aok || !bok {
|
||
continue
|
||
}
|
||
gap := as.Score - bs.Score
|
||
abs := int(math.Abs(float64(gap)))
|
||
note := dimNote(key, gap, aName, bName)
|
||
dimCompare = append(dimCompare, map[string]any{
|
||
"key": key, "title": title,
|
||
"me_score": as.Score, "other_score": bs.Score,
|
||
"me_teaser": as.Teaser, "other_teaser": bs.Teaser,
|
||
"gap": abs, "note": note,
|
||
})
|
||
}
|
||
|
||
aSun, aMoon, aRise := star.SignLabelsPlace(aBirth, aTime, aPlace)
|
||
bSun, bMoon, bRise := star.SignLabelsPlace(bBirth, bTime, bPlace)
|
||
ca, errA := star.NatalChart(aBirth, aTime, aPlace)
|
||
cb, errB := star.NatalChart(bBirth, bTime, bPlace)
|
||
if errA != nil || errB != nil {
|
||
ca, cb = natal.Chart{}, natal.Chart{}
|
||
}
|
||
match := synastry.Compute(ca, cb, aName, bName)
|
||
harmony := harmonyIndex(dimCompare)
|
||
fitLabel, fitTips := fitFromHarmony(harmony, aLabel, bLabel)
|
||
|
||
summary := map[string]any{
|
||
"title": "人格匹配·基础",
|
||
"me_style": aLabel,
|
||
"other_style": bLabel,
|
||
"me_keywords": aKeys,
|
||
"other_keywords": bKeys,
|
||
"diff_one_liner": comp.OneLiner,
|
||
"overview": comp.Overview,
|
||
"chemistry": comp.Chemistry,
|
||
"watchouts_preview": comp.Watchouts[:min(2, len(comp.Watchouts))],
|
||
"dimension_compare": dimCompare,
|
||
"fit_label": fitLabel,
|
||
"fit_tips": fitTips,
|
||
"harmony_index": harmony,
|
||
"love_index": match.Love,
|
||
"friend_index": match.Friend,
|
||
"marriage_index": match.Marriage,
|
||
"match_indices": match.AsMap(),
|
||
"star_compare": []map[string]any{
|
||
{"key": "sun", "title": "太阳", "me": aSun, "other": bSun, "note": starPairNote(aSun, bSun)},
|
||
{"key": "moon", "title": "月亮", "me": aMoon, "other": bMoon, "note": starPairNote(aMoon, bMoon)},
|
||
{"key": "rise", "title": "上升", "me": aRise, "other": bRise, "note": starPairNote(aRise, bRise)},
|
||
},
|
||
"share_hint": "了解彼此的沟通方式,查看人格匹配",
|
||
}
|
||
|
||
detail := map[string]any{
|
||
"title": "人格匹配·完整分析",
|
||
"sections": []map[string]any{
|
||
{"title": "匹配总览", "body": comp.DeepOverview, "bullets": comp.ChemistryPoints},
|
||
{"title": "星座合盘与匹配指数", "body": fmt.Sprintf("%s。%s太阳%s / %s太阳%s;月亮%s与%s。", match.Summary, aName, aSun, bName, bSun, aMoon, bMoon),
|
||
"bullets": []string{match.LoveNote, match.FriendNote, match.MarriageNote, starPairNote(aSun, bSun)}},
|
||
{"title": "沟通协作", "body": comp.CommDeep, "bullets": comp.CommTips},
|
||
{"title": "冲突修复", "body": comp.ConflictDeep, "bullets": comp.ConflictTips},
|
||
{"title": "亲密与边界", "body": comp.IntimacyDeep, "bullets": comp.IntimacyTips},
|
||
{"title": "共同成长", "body": comp.GrowthDeep, "bullets": comp.GrowthTips},
|
||
},
|
||
// legacy flat lists (tests / older clients)
|
||
"communication": append([]string{
|
||
fmt.Sprintf("%s(%s):%s", aName, aLabel, firstTeaser(aDims, "communication")),
|
||
fmt.Sprintf("%s(%s):%s", bName, bLabel, firstTeaser(bDims, "communication")),
|
||
}, comp.CommTips...),
|
||
"interaction": comp.IntimacyTips,
|
||
"maintenance": comp.GrowthTips,
|
||
"me_behavior": str(pa.Detail["behavior_pattern"]),
|
||
"other_behavior": str(pb.Detail["behavior_pattern"]),
|
||
"strengths_together": comp.ChemistryPoints,
|
||
"friction_points": comp.Watchouts,
|
||
"weekly_practice": comp.Weekly,
|
||
"conversation_scripts": comp.Scripts,
|
||
"faq": []map[string]string{
|
||
{"q": "差异大是不是不合适?", "a": "差异本身不是问题,关键是双方是否愿意把差异当成说明书,而不是评分表。"},
|
||
{"q": "总吵怎么办?", "a": "先停火复述,再谈方案;把「谁对谁错」换成「我们各自需要什么」。"},
|
||
},
|
||
}
|
||
relLabel, relBody, relBullets := relationTypePack(relationType, aName, bName)
|
||
if relLabel != "" {
|
||
summary["relation_type"] = relationType
|
||
summary["relation_type_label"] = relLabel
|
||
secs, _ := detail["sections"].([]map[string]any)
|
||
detail["sections"] = append([]map[string]any{
|
||
{"title": "关系类型:" + relLabel, "body": relBody, "bullets": relBullets},
|
||
}, secs...)
|
||
}
|
||
return Output{Summary: summary, Detail: detail}
|
||
}
|