// 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} }