Files
digital-psychology/apps/api/internal/portrait/triangle.go
T
jackyu66gitandCursor bd22d9dddd feat: P1 合盘/星座/问答与测测完整设计包及模拟器取证工具
落地 synastry/star/ask API 与 H5 页面,补齐 cece-frontend-re complete-design 证据文档,并加入 Android 模拟器截图抓取脚本。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-03 11:37:53 +08:00

238 lines
5.2 KiB
Go

package portrait
// Triangle is the classic life-number pyramid from js/common.js calc().
type Triangle struct {
AB, CD, EF, GH [2]int
I, J, K, L int
M, N, O int
Q, P, R int
T, S, U int
V, W, X int
}
// Calc builds the digital psychology triangle for a birth date.
func Calc(y, m, d int) Triangle {
dd := digs(d, 2)
mm := digs(m, 2)
yy := digs(y, 4)
a, b := dd[0], dd[1]
c, dd1 := mm[0], mm[1]
e, f, g, h := yy[0], yy[1], yy[2], yy[3]
v := Triangle{
AB: [2]int{a, b},
CD: [2]int{c, dd1},
EF: [2]int{e, f},
GH: [2]int{g, h},
}
v.I = s1(a + b)
v.J = s1(c + dd1)
v.K = s1(e + f)
v.L = s1(g + h)
v.M = s1(v.I + v.J)
v.N = s1(v.K + v.L)
v.O = s1(v.M + v.N)
v.Q = s1(v.N + v.O)
v.P = s1(v.M + v.O)
v.R = s1(v.Q + v.P)
v.T = s1(v.I + v.M)
v.S = s1(v.J + v.M)
v.U = s1(v.T + v.S)
v.V = s1(v.K + v.N)
v.W = s1(v.L + v.N)
v.X = s1(v.V + v.W)
return v
}
// PyramidMap returns grid values for H5 mini-triangle rendering.
func (v Triangle) PyramidMap() map[string]int {
return map[string]int{
"I": v.I, "J": v.J, "K": v.K, "L": v.L,
"M": v.M, "N": v.N, "O": v.O,
"Q": v.Q, "P": v.P, "R": v.R,
"T": v.T, "S": v.S, "U": v.U,
"V": v.V, "W": v.W, "X": v.X,
}
}
func s1(n int) int {
for n >= 10 {
sum := 0
for n > 0 {
sum += n % 10
n /= 10
}
n = sum
}
return n
}
func digs(n, l int) []int {
s := fmtPad(n, l)
out := make([]int, l)
for i := 0; i < l; i++ {
out[i] = int(s[i] - '0')
}
return out
}
func fmtPad(n, l int) string {
if n < 0 {
n = -n
}
buf := make([]byte, l)
for i := l - 1; i >= 0; i-- {
buf[i] = byte('0' + n%10)
n /= 10
}
return string(buf)
}
func cell(v Triangle, key string) int {
switch key {
case "I":
return v.I
case "J":
return v.J
case "K":
return v.K
case "L":
return v.L
case "M":
return v.M
case "N":
return v.N
case "O":
return v.O
case "Q":
return v.Q
case "P":
return v.P
case "R":
return v.R
case "T":
return v.T
case "S":
return v.S
case "U":
return v.U
case "V":
return v.V
case "W":
return v.W
case "X":
return v.X
default:
return 0
}
}
// jointGroups builds age-grouped joint codes (付费层).
func jointGroups(v Triangle) []map[string]any {
pairs := [][2]string{
{"I", "J"}, {"J", "M"}, {"M", "I"}, {"K", "L"},
{"L", "N"}, {"N", "K"}, {"M", "O"}, {"O", "N"},
{"M", "N"}, {"Q", "P"}, {"Q", "O"}, {"P", "O"},
}
joints := make([]map[string]any, 0, len(pairs))
for _, p := range pairs {
code := fmtInt(cell(v, p[0])) + fmtInt(cell(v, p[1]))
label := "—"
for k, desc := range jointCodes {
if len(k) >= len(code) && k[:len(code)] == code {
label = desc
break
}
}
joints = append(joints, map[string]any{"code": code, "label": label})
}
g1Desc := "由潜意识码主导,反映你在职场上升期和社交圈中的表现模式。"
if v.I+v.J+v.M >= 10 {
g1Desc += "能量较强,事业心旺盛,社交活跃。"
} else {
g1Desc += "能量内敛,更注重深度而非广度。"
}
g2Desc := "由内心码和主性格主导,体现你对下一代和下级的培养方式及中年价值观。"
if v.K+v.L+v.N >= 10 {
g2Desc += "有较强的责任感和支配欲。"
} else {
g2Desc += "倾向于给予空间,放手让他人成长。"
}
g3Desc := "由外心码主导,展现你晚年的生活智慧和家庭关系的最终呈现。"
if v.M+v.N+v.O >= 10 {
g3Desc += "晚年活跃,持续发挥影响力。"
} else {
g3Desc += "晚年宁静,享受内在平和。"
}
return []map[string]any{
{"label": "21—40岁 · 工作和朋友", "codes": joints[0:4], "desc": g1Desc},
{"label": "41—60岁 · 儿女和下属", "codes": joints[4:8], "desc": g2Desc},
{"label": "61岁至晚年 · 晚年和家庭", "codes": joints[8:12], "desc": g3Desc},
}
}
func missAndOver(v Triangle) (missing []map[string]any, over []map[string]any) {
innerSet := map[int]bool{v.O: true, v.M: true, v.N: true, v.I: true, v.J: true, v.K: true, v.L: true}
outerVals := []int{v.Q, v.P, v.R, v.T, v.S, v.U, v.V, v.W, v.X}
present := map[int]bool{}
for n := range innerSet {
present[n] = true
}
for _, n := range outerVals {
present[n] = true
}
for _, n := range []int{v.AB[0], v.AB[1], v.CD[0], v.CD[1], v.EF[0], v.EF[1], v.GH[0], v.GH[1]} {
present[n] = true
}
freq := map[int]int{}
for i := 1; i <= 9; i++ {
freq[i] = 0
}
allVals := []int{
v.O, v.M, v.N, v.I, v.J, v.K, v.L, v.Q, v.P, v.R, v.T, v.S, v.U, v.V, v.W, v.X,
v.AB[0], v.AB[1], v.CD[0], v.CD[1], v.EF[0], v.EF[1], v.GH[0], v.GH[1],
}
for _, x := range allVals {
if x >= 1 && x <= 9 {
freq[x]++
}
}
missBoth := []int{}
for n := 0; n <= 9; n++ {
if !present[n] {
missBoth = append(missBoth, n)
}
}
missSet := map[int]bool{}
for _, n := range missBoth {
missSet[n] = true
if n >= 1 && n <= 9 {
missing = append(missing, map[string]any{"n": n, "msg": missMsg[n]})
}
}
for i := 1; i <= 9; i++ {
if freq[i] >= 4 && !missSet[i] {
over = append(over, map[string]any{"n": i, "msg": missStrong[i], "count": freq[i]})
}
}
return missing, over
}
func fmtInt(n int) string {
if n < 0 {
n = -n
}
if n < 10 {
return string(rune('0' + n))
}
return fmtPad(n, 2)
}
func firstSentence(s string) string {
for i, r := range s {
if r == '。' || r == '.' {
return s[:i]
}
}
return s
}