feat: P1 合盘/星座/问答与测测完整设计包及模拟器取证工具

落地 synastry/star/ask API 与 H5 页面,补齐 cece-frontend-re complete-design 证据文档,并加入 Android 模拟器截图抓取脚本。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
jackyu66git
2026-08-03 11:37:53 +08:00
co-authored by Cursor
parent 15a9db374a
commit bd22d9dddd
248 changed files with 26309 additions and 842 deletions
+133
View File
@@ -0,0 +1,133 @@
// 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 0100 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,
}
}