Files
digital-psychology/apps/api/internal/scale/jungian.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

123 lines
3.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package scale
import (
"encoding/json"
"strconv"
)
// JungianQuestionMeta is parsed from scale_questions.body for OEJTS-style items.
type JungianQuestionMeta struct {
Dimension string `json:"dimension"`
Format string `json:"format"`
Left string `json:"left"`
Right string `json:"right"`
Prompt string `json:"prompt"`
}
// ScoreJungian tallies 15 Likert answers per EI/SN/TF/JP (OEJTS rules).
// Left poles: E, S, F, J · Right poles: I, N, T, P · threshold = perDim*3.
func ScoreJungian(answers map[string]string, qMeta map[string]JungianQuestionMeta, perDim int) (typeCode string, raw map[string]int, pct map[string]int) {
raw = map[string]int{"EI": 0, "SN": 0, "TF": 0, "JP": 0}
if perDim <= 0 {
perDim = 8
}
for qid, ans := range answers {
meta, ok := qMeta[qid]
if !ok || meta.Dimension == "" {
continue
}
v, err := strconv.Atoi(ans)
if err != nil || v < 1 || v > 5 {
v = 3
}
raw[meta.Dimension] += v
}
thr := perDim * 3
pick := func(score int, left, right string) string {
if score > thr {
return right
}
return left
}
typeCode = pick(raw["EI"], "E", "I") +
pick(raw["SN"], "S", "N") +
pick(raw["TF"], "F", "T") +
pick(raw["JP"], "J", "P")
pct = map[string]int{}
minS, maxS := perDim, perDim*5
span := float64(maxS - minS)
rightPct := func(score int) int {
p := int(float64(score-minS)/span*100 + 0.5)
if p < 0 {
return 0
}
if p > 100 {
return 100
}
return p
}
eiR := rightPct(raw["EI"])
snR := rightPct(raw["SN"])
tfR := rightPct(raw["TF"])
jpR := rightPct(raw["JP"])
pct["E"], pct["I"] = 100-eiR, eiR
pct["S"], pct["N"] = 100-snR, snR
pct["F"], pct["T"] = 100-tfR, tfR
pct["J"], pct["P"] = 100-jpR, jpR
return typeCode, raw, pct
}
// ParseJungianMeta extracts dimension metadata from question body JSON.
func ParseJungianMeta(body json.RawMessage) JungianQuestionMeta {
var m JungianQuestionMeta
_ = json.Unmarshal(body, &m)
return m
}
// BuildJungianResult builds rich result with 4-letter code + preference bars.
func BuildJungianResult(typeCode string, pct map[string]int) map[string]interface{} {
label := MBTILabel(typeCode)
base := mbtiPack(typeCode, label)
base.Label = typeCode + " · " + label
base.ShareLine = "我的类型探索:" + typeCode + " · " + label
base.Dimensions = []map[string]interface{}{
{"title": "E/I 能量", "score": maxPct(pct, "E", "I"), "note": prefNote("E", "I", pct, "外向互动", "内向思考")},
{"title": "S/N 信息", "score": maxPct(pct, "S", "N"), "note": prefNote("S", "N", pct, "具体事实", "模式可能")},
{"title": "T/F 决策", "score": maxPct(pct, "T", "F"), "note": prefNote("T", "F", pct, "逻辑一致", "价值与人际")},
{"title": "J/P 节奏", "score": maxPct(pct, "J", "P"), "note": prefNote("J", "P", pct, "计划结构", "灵活开放")},
}
out := map[string]interface{}{
"title": "MBTI 探索结果",
"style_key": typeCode,
"type_code": typeCode,
"label": base.Label,
"summary": base.Summary,
"overview": base.Overview,
"share_line": base.ShareLine,
"dimensions": base.Dimensions,
"preferences": pct,
"strengths": base.Strengths,
"watchouts": base.Watchouts,
"tips": base.Tips,
"scripts": base.Scripts,
"growth_plan": base.GrowthPlan,
"faq": base.FAQ,
}
return out
}
func maxPct(pct map[string]int, a, b string) int {
if pct[a] >= pct[b] {
return pct[a]
}
return pct[b]
}
func prefNote(a, b string, pct map[string]int, aDesc, bDesc string) string {
if pct[a] >= pct[b] {
return a + " " + strconv.Itoa(pct[a]) + "% · " + aDesc
}
return b + " " + strconv.Itoa(pct[b]) + "% · " + bDesc
}