Files
digital-psychology/apps/api/internal/portrait/wuxing.go
T
jackyu66gitandCursor 89756f65b4 feat(ECR-012–016): 合规、题库、时辰刷新、头像、MBTI OEJTS 与埋点
落地输入合规、探索题库、报告日/时辰刷新、账号头像、OEJTS 量表,并补齐 H5 埋点与 Admin 漏斗;同步 ESS 工件、切至自建 Git、清理 GitHub Actions。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-13 01:27:58 +08:00

160 lines
4.4 KiB
Go

package portrait
import (
"time"
"github.com/yuxingu/digital-psychology/apps/api/internal/companion"
)
var (
tg = []string{"甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸"}
dz = []string{"子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥"}
tge = map[string]string{"甲": "木", "乙": "木", "丙": "火", "丁": "火", "戊": "土", "己": "土", "庚": "金", "辛": "金", "壬": "水", "癸": "水"}
dze = map[string]string{"子": "水", "丑": "土", "寅": "木", "卯": "木", "辰": "土", "巳": "火", "午": "火", "未": "土", "申": "金", "酉": "金", "戌": "土", "亥": "水"}
eo = []string{"木", "火", "土", "金", "水"}
ec = map[string]string{"木": "#5CB85C", "火": "#E54D42", "土": "#E8985A", "金": "#C8923A", "水": "#4A90E2"}
)
var yiJi = map[string]struct{ Yi, Ji string }{
"木": {"早睡养肝血 · 舒展拉伸 · 绿色蔬菜", "久坐 · 生闷气 · 熬夜"},
"火": {"午休15分钟 · 菊花/莲子 · 平心静气", "暴晒 · 辛辣油腻 · 过度兴奋"},
"土": {"规律三餐 · 健脾山药 · 饭后散步", "思虑过度 · 生冷 · 暴饮暴食"},
"金": {"滋润养肺 · 白色食物 · 深呼吸", "干燥受凉 · 悲忧 · 剧烈耗气"},
"水": {"早卧晚起 · 温补黑色食物 · 护腰腹", "受寒 · 过劳 · 惊恐紧张"},
}
// seasonEl maps month index (0=Jan) → 时令五行 (decode.js SEASON_EL).
var seasonEl = []string{"水", "水", "木", "木", "木", "火", "火", "火", "土", "金", "金", "水"}
type pillar struct{ tg, dz string }
type bazi struct {
year, month, day, hour pillar
}
// getBazi mirrors js/common.js getBazi (hour default 12 = 午时).
// Day-pillar index uses civil days since 1900-01-01 minus 1, matching legacy
// browser Date local-midnight flooring under historical CN TZ/DST (decode.js).
func getBazi(y, m, d, h int) bazi {
gzY := pillar{tg: tg[(y-4+10000)%10], dz: dz[(y-4+12000)%12]}
birth := time.Date(y, time.Month(m), d, 0, 0, 0, 0, time.UTC)
dayOfYear := birth.YearDay()
lcDay := 35
mi := int(float64((dayOfYear-lcDay+365)%365) / 30.44)
if mi < 0 {
mi += 12
}
if mi >= 12 {
mi = 11
}
mTg := tg[(((y-4+10000)%10)*2+mi)%10]
mDz := dz[(mi+2)%12]
base := time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC)
dd := int(birth.Sub(base).Hours()/24) - 1
dTg := tg[((dd%10)+10)%10]
dDz := dz[((dd%12)+12)%12]
hDz := dz[((h+1)/2)%12]
hTg := tg[(indexOf(tg, dTg)*2+indexOf(dz, hDz))%10]
return bazi{
year: gzY,
month: pillar{tg: mTg, dz: mDz},
day: pillar{tg: dTg, dz: dDz},
hour: pillar{tg: hTg, dz: hDz},
}
}
func calc5E(bz bazi) map[string]int {
c := map[string]int{"木": 0, "火": 0, "土": 0, "金": 0, "水": 0}
for _, p := range []pillar{bz.year, bz.month, bz.day, bz.hour} {
c[tge[p.tg]]++
c[dze[p.dz]]++
}
t := 0
for _, e := range eo {
t += c[e]
}
r := map[string]int{}
for _, e := range eo {
if t == 0 {
r[e] = 0
} else {
r[e] = int(float64(c[e])/float64(t)*100 + 0.5) // Math.round
}
}
return r
}
func strongestEl(p map[string]int) string {
best := eo[0]
for _, e := range eo[1:] {
if p[e] > p[best] {
best = e
}
}
return best
}
func weakestEl(p map[string]int) string {
worst := eo[0]
for _, e := range eo[1:] {
if p[e] < p[worst] {
worst = e
}
}
return worst
}
func wuxingBars(p map[string]int) []map[string]any {
bars := make([]map[string]any, 0, 5)
for _, e := range eo {
bars = append(bars, map[string]any{
"el": e, "pct": p[e], "color": ec[e],
})
}
return bars
}
// WuxingBlock builds the shared 五行 summary block (bars + tendency labels).
func WuxingBlock(birth time.Time) map[string]any {
y, m, d := birth.Date()
bz := getBazi(y, int(m), d, 12)
pct := calc5E(bz)
strong := strongestEl(pct)
weak := weakestEl(pct)
cst := constitutionByElement[strong]
emo := emotionByElement[strong]
return map[string]any{
"bars": wuxingBars(pct),
"primary": cst.Primary,
"strong": cst.Strong,
"weak": cst.Weak,
"strong_el": strong,
"weak_el": weak,
"emotion": emo.Strength,
"care_dir": "养" + cst.Strong + " · 需呵护" + cst.Weak,
}
}
func todayTip(now time.Time) map[string]any {
st := companion.TodaySolar(now)
el := seasonEl[int(now.Month())-1]
yj := yiJi[el]
return map[string]any{
"solar_term": st.Name,
"season_el": el,
"yi": yj.Yi,
"ji": yj.Ji,
"tip": st.Tip,
}
}
func indexOf(arr []string, s string) int {
for i, v := range arr {
if v == s {
return i
}
}
return 0
}