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 1–5 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 }