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 }