落地 synastry/star/ask API 与 H5 页面,补齐 cece-frontend-re complete-design 证据文档,并加入 Android 模拟器截图抓取脚本。 Co-authored-by: Cursor <cursoragent@cursor.com>
134 lines
3.5 KiB
Go
134 lines
3.5 KiB
Go
// Package synastry computes love/friend/marriage match indices from two natal charts.
|
||
package synastry
|
||
|
||
import (
|
||
"fmt"
|
||
|
||
"github.com/yuxingu/digital-psychology/apps/api/internal/star/natal"
|
||
)
|
||
|
||
// Indices are 0–100 match scores.
|
||
type Indices struct {
|
||
Love int `json:"love_index"`
|
||
Friend int `json:"friend_index"`
|
||
Marriage int `json:"marriage_index"`
|
||
LoveNote string `json:"love_note"`
|
||
FriendNote string `json:"friend_note"`
|
||
MarriageNote string `json:"marriage_note"`
|
||
Summary string `json:"summary"`
|
||
}
|
||
|
||
// Compute returns match indices for charts A and B.
|
||
func Compute(a, b natal.Chart, aName, bName string) Indices {
|
||
if aName == "" {
|
||
aName = "我"
|
||
}
|
||
if bName == "" {
|
||
bName = "TA"
|
||
}
|
||
sunDiff := natal.AngleDiff(a.Sun.Lon, b.Sun.Lon)
|
||
moonDiff := natal.AngleDiff(a.Moon.Lon, b.Moon.Lon)
|
||
venA := bodyLon(a, "venus")
|
||
venB := bodyLon(b, "venus")
|
||
marsA := bodyLon(a, "mars")
|
||
marsB := bodyLon(b, "mars")
|
||
venDiff := natal.AngleDiff(venA, venB)
|
||
marsVen := natal.AngleDiff(marsA, venB)
|
||
venMars := natal.AngleDiff(venA, marsB)
|
||
|
||
// Closer aspects (0/60/120 soft; 90/180 hard but charged) → higher score blended
|
||
love := clamp(58+aspectBoost(sunDiff)+aspectBoost(moonDiff)+aspectBoost(venDiff)+aspectBoost(marsVen)/2+aspectBoost(venMars)/2, 42, 98)
|
||
friend := clamp(60+aspectBoost(sunDiff)+aspectBoost(moonDiff)*3/2+elemBoost(a.Sun, b.Sun), 45, 97)
|
||
marriage := clamp(55+aspectBoost(sunDiff)+aspectBoost(moonDiff)+aspectBoost(venDiff)+houseBoost(a, b), 40, 96)
|
||
|
||
return Indices{
|
||
Love: love, Friend: friend, Marriage: marriage,
|
||
LoveNote: fmt.Sprintf("%s与%s恋爱指数 %d:太阳相距约%.0f°,金星互动影响吸引力。", aName, bName, love, sunDiff),
|
||
FriendNote: fmt.Sprintf("友情指数 %d:月亮与太阳元素是否合拍,决定轻松感。", friend),
|
||
MarriageNote: fmt.Sprintf("婚姻/长期指数 %d:看重稳定与共同节奏,而非一时火花。", marriage),
|
||
Summary: fmt.Sprintf("%s × %s:恋爱 %d · 友情 %d · 婚姻 %d", aName, bName, love, friend, marriage),
|
||
}
|
||
}
|
||
|
||
func bodyLon(c natal.Chart, key string) float64 {
|
||
for _, p := range c.Planets {
|
||
if p.Key == key {
|
||
return p.Lon
|
||
}
|
||
}
|
||
return 0
|
||
}
|
||
|
||
func aspectBoost(diff float64) int {
|
||
// reward conjunction, sextile, trine; mild for square/opposition
|
||
targets := []float64{0, 60, 90, 120, 180}
|
||
best := 180.0
|
||
for _, t := range targets {
|
||
d := abs(diff - t)
|
||
if d < best {
|
||
best = d
|
||
}
|
||
}
|
||
switch {
|
||
case best <= 8:
|
||
if near(diff, 90) || near(diff, 180) {
|
||
return 8
|
||
}
|
||
return 14
|
||
case best <= 12:
|
||
return 8
|
||
default:
|
||
return int(6 - best/30)
|
||
}
|
||
}
|
||
|
||
func near(diff, target float64) bool {
|
||
return abs(diff-target) <= 10
|
||
}
|
||
|
||
func elemBoost(a, b natal.Body) int {
|
||
if a.Element == b.Element {
|
||
return 6
|
||
}
|
||
// fire-air / earth-water traditionally supportive
|
||
pair := a.Element + b.Element
|
||
if pair == "火风" || pair == "风火" || pair == "土水" || pair == "水土" {
|
||
return 4
|
||
}
|
||
return 0
|
||
}
|
||
|
||
func houseBoost(a, b natal.Chart) int {
|
||
// same rising modality → slight boost
|
||
if a.Rise.Modality == b.Rise.Modality {
|
||
return 4
|
||
}
|
||
return 0
|
||
}
|
||
|
||
func clamp(v, lo, hi int) int {
|
||
if v < lo {
|
||
return lo
|
||
}
|
||
if v > hi {
|
||
return hi
|
||
}
|
||
return v
|
||
}
|
||
|
||
func abs(x float64) float64 {
|
||
if x < 0 {
|
||
return -x
|
||
}
|
||
return x
|
||
}
|
||
|
||
// AsMap for JSON.
|
||
func (i Indices) AsMap() map[string]any {
|
||
return map[string]any{
|
||
"love_index": i.Love, "friend_index": i.Friend, "marriage_index": i.Marriage,
|
||
"love_note": i.LoveNote, "friend_note": i.FriendNote, "marriage_note": i.MarriageNote,
|
||
"summary": i.Summary,
|
||
}
|
||
}
|