feat(ECR-012–016): 合规、题库、时辰刷新、头像、MBTI OEJTS 与埋点
落地输入合规、探索题库、报告日/时辰刷新、账号头像、OEJTS 量表,并补齐 H5 埋点与 Admin 漏斗;同步 ESS 工件、切至自建 Git、清理 GitHub Actions。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package scale
|
||||
|
||||
// mbtiPack returns exploration copy for a 4-letter preference code (原创,非官方题库).
|
||||
func mbtiPack(code, fallback string) pack {
|
||||
label := MBTILabel(code)
|
||||
if label == "平衡探索型" && fallback != "" {
|
||||
label = fallback
|
||||
}
|
||||
base := mbtiBase(code, label)
|
||||
dims := []map[string]interface{}{
|
||||
{"title": "能量", "score": dimScore(code, 0, 'E'), "note": axisNote(code, 0)},
|
||||
{"title": "信息", "score": dimScore(code, 1, 'S'), "note": axisNote(code, 1)},
|
||||
{"title": "决策", "score": dimScore(code, 2, 'T'), "note": axisNote(code, 2)},
|
||||
{"title": "节奏", "score": dimScore(code, 3, 'J'), "note": axisNote(code, 3)},
|
||||
}
|
||||
base.Dimensions = dims
|
||||
base.ShareLine = "我的 MBTI 探索:" + code + " · " + label
|
||||
base.FAQ = append(base.FAQ, map[string]string{
|
||||
"q": "这是固定人格吗?",
|
||||
"a": "不是。这是当前情境下的偏好快照,可随经历变化,请当作自我了解工具。",
|
||||
})
|
||||
return base
|
||||
}
|
||||
|
||||
func dimScore(code string, i int, left rune) int {
|
||||
if i >= len(code) {
|
||||
return 70
|
||||
}
|
||||
if rune(code[i]) == left {
|
||||
return 82
|
||||
}
|
||||
return 78
|
||||
}
|
||||
|
||||
func axisNote(code string, i int) string {
|
||||
if i >= len(code) {
|
||||
return ""
|
||||
}
|
||||
switch code[i] {
|
||||
case 'E':
|
||||
return "外向充能偏多"
|
||||
case 'I':
|
||||
return "内向充能偏多"
|
||||
case 'S':
|
||||
return "更抓具体与当下"
|
||||
case 'N':
|
||||
return "更抓可能与意义"
|
||||
case 'T':
|
||||
return "更重逻辑与标准"
|
||||
case 'F':
|
||||
return "更重感受与关系"
|
||||
case 'J':
|
||||
return "更爱结构与收束"
|
||||
case 'P':
|
||||
return "更爱弹性与开放"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func mbtiBase(code, label string) pack {
|
||||
switch code {
|
||||
case "INTJ":
|
||||
return pack{Label: label, Summary: "你习惯先看见长期结构,再一步步落地。独处思考是你的燃料。", Overview: "你擅长把模糊目标拆成路径,讨厌无效社交与反复改口。成长点是:在推进前多留一句对人的确认,让方案更容易被接纳。", Strengths: []string{"战略感强", "独立推进", "标准清晰"}, Watchouts: []string{"显得疏离", "对低效不耐烦", "过度封闭计划"}, Tips: []string{"关键节点先同步再独断", "给情绪留 10 分钟再决策", "用清单外的「弹性项」练习放手"}, Scripts: []string{"我的建议是……,你最担心哪一步?", "我想先对齐目标,再谈细节可以吗?"}, GrowthPlan: []map[string]string{{"phase": "本周", "focus": "一次决策前先问对方感受"}, {"phase": "本月", "focus": "公开一个半成品计划收集反馈"}}}
|
||||
case "INTP":
|
||||
return pack{Label: label, Summary: "你靠好奇与逻辑拆解世界,喜欢把概念想透再行动。", Overview: "分析是你的舒适区,拖延常来自还想再想清楚。成长点是设定「够好就提交」的截止点,把洞见变成可验证的小实验。", Strengths: []string{"抽象思考", "问题拆解", "开放好奇"}, Watchouts: []string{"迟迟不落地", "忽略关系节奏", "过度纠结定义"}, Tips: []string{"每个想法配一个 48 小时小实验", "讨论时先复述对方一句", "用番茄钟切分析与执行"}, Scripts: []string{"我还在梳理,今晚给你一个阶段性结论。", "我理解你的点;我补充一个角度……"}, GrowthPlan: []map[string]string{{"phase": "本周", "focus": "完成一个未收尾的小输出"}, {"phase": "本月", "focus": "建立「思考→验证」双清单"}}}
|
||||
case "ENTJ":
|
||||
return pack{Label: label, Summary: "你天然想把事情推动到结果,目标感强、节奏快。", Overview: "你适合带队攻坚,但速度可能压到他人感受。成长点是:在下达目标时同步「为什么」与「需要的支持」。", Strengths: []string{"决策果断", "组织推进", "结果导向"}, Watchouts: []string{"控制欲过强", "忽视情绪成本", "把慢当成错"}, Tips: []string{"每周一次只听不评判的同步", "目标拆成共同里程碑", "表扬过程不只结果"}, Scripts: []string{"我们要达成……,你卡在哪里我可以支援?", "我的时间表是……,你的约束是什么?"}, GrowthPlan: []map[string]string{{"phase": "本周", "focus": "一次会议先听完再给方案"}, {"phase": "本月", "focus": "把一个目标改成共创版"}}}
|
||||
case "ENTP":
|
||||
return pack{Label: label, Summary: "你爱挑战既有假设,点子多、辩论感强,讨厌无聊重复。", Overview: "灵感是你的超能力,落地与收尾是课题。成长点是选一个点子陪它走完最小闭环。", Strengths: []string{"快速联想", "打破僵局", "说服力"}, Watchouts: []string{"虎头蛇尾", "为辩而辩", "承诺过多"}, Tips: []string{"新点子先写「不做清单」", "找一个落地搭档", "争论前先确认共同目标"}, Scripts: []string{"换个角度看:如果……会怎样?", "我可以先做一版原型,我们再决定。"}, GrowthPlan: []map[string]string{{"phase": "本周", "focus": "只推进一个点子到可演示"}, {"phase": "本月", "focus": "练习结束争论的收束句"}}}
|
||||
case "INFJ":
|
||||
return pack{Label: label, Summary: "你敏感于意义与人心走向,常在安静处看见别人没说出口的事。", Overview: "理想驱动你,也容易负荷过重。成长点是把关怀加上边界:不是所有情绪都要你接住。", Strengths: []string{"洞察力", "长期愿景", "深度共情"}, Watchouts: []string{"过度负责", "理想落差挫败", "表达拐弯"}, Tips: []string{"每天留无负担独处", "重要请求直接说", "区分「关心」与「承包」"}, Scripts: []string{"我感受到你……,我能做的是……,但我需要……", "这件事对我意义很大,我想认真谈一次。"}, GrowthPlan: []map[string]string{{"phase": "本周", "focus": "拒绝一次超出负荷的请求"}, {"phase": "本月", "focus": "把一个愿景写成三步小行动"}}}
|
||||
case "INFP":
|
||||
return pack{Label: label, Summary: "你按内在价值行动,重视真实与温柔,讨厌被强迫表演。", Overview: "当环境违背价值观你会撤退或内耗。成长点是把「我在意什么」说清楚,让他人有机会配合你。", Strengths: []string{"价值感强", "创造力", "真诚"}, Watchouts: []string{"逃避冲突", "理想化他人", "自我怀疑"}, Tips: []string{"冲突时先写三句再谈", "每周做一件对齐价值的小事", "接受「足够好」的交付"}, Scripts: []string{"这件事触及我很在意的……,我们可以一起找折中吗?", "我需要一点时间整理感受,晚点认真回复你。"}, GrowthPlan: []map[string]string{{"phase": "本周", "focus": "一次温和但清楚的表达"}, {"phase": "本月", "focus": "建立个人价值清单并对照日程"}}}
|
||||
case "ENFJ":
|
||||
return pack{Label: label, Summary: "你擅长点燃群体、照顾节奏,常成为大家的「主心骨」。", Overview: "你容易把别人的成长扛在肩上。成长点是学会把责任还回去,并照顾自己的能量账户。", Strengths: []string{"召集力", "鼓励他人", "氛围敏感"}, Watchouts: []string{"自我耗竭", "讨好倾向", "过度介入"}, Tips: []string{"帮助前先问「你需要什么」", "每周固定自我补给", "把表扬与界限一起说"}, Scripts: []string{"我很想支持你;这次我能做到的是……", "我们一起定个节奏,好让彼此都不透支。"}, GrowthPlan: []map[string]string{{"phase": "本周", "focus": "一次帮助改为赋能提问"}, {"phase": "本月", "focus": "建立个人恢复仪式"}}}
|
||||
case "ENFP":
|
||||
return pack{Label: label, Summary: "你热情、好奇、连接人与可能性,讨厌被框死。", Overview: "开始很容易,专注收尾较难。成长点是给热情加一点结构:选少做深。", Strengths: []string{"感染力", "创意联想", "关系活力"}, Watchouts: []string{"分心多线", "承诺膨胀", "情绪起伏大"}, Tips: []string{"同时只养 2 个重点项目", "用伙伴盯收尾", "低能量日允许缩小社交圈"}, Scripts: []string{"我超有感觉!我们先定最小一步……", "今天我状态一般,改天再深聊可以吗?"}, GrowthPlan: []map[string]string{{"phase": "本周", "focus": "砍掉一个分心事项"}, {"phase": "本月", "focus": "完成一个从灵感到交付的闭环"}}}
|
||||
case "ISTJ":
|
||||
return pack{Label: label, Summary: "你可靠、重承诺,喜欢把规则与流程做清楚。", Overview: "稳定是你的礼物,变化可能让你紧绷。成长点是在原则内留一个「可谈判区」。", Strengths: []string{"执行力", "细节准确", "责任感"}, Watchouts: []string{"固执流程", "不善表达情感", "对新法抵触"}, Tips: []string{"变更前先写利弊表", "主动说一句关心", "把例外规则写进清单"}, Scripts: []string{"按计划我们该……;若要改,影响是……", "我在意可靠,所以想先确认时间点。"}, GrowthPlan: []map[string]string{{"phase": "本周", "focus": "接受一次小范围变更"}, {"phase": "本月", "focus": "练习情绪用词清单"}}}
|
||||
case "ISFJ":
|
||||
return pack{Label: label, Summary: "你细心照顾日常与人情,默默把安全感建起来。", Overview: "你常先顾别人。成长点是让需要被看见:被照顾也是关系的一部分。", Strengths: []string{"体贴", "记忆细节", "稳定支持"}, Watchouts: []string{"压抑需求", "怕冲突", "过度付出"}, Tips: []string{"每周提出一个真实偏好", "把付出写成可轮换的任务", "疲惫时直接说停"}, Scripts: []string{"我一直在帮忙……,这次我也需要……", "我有点累了,今晚想安静一下。"}, GrowthPlan: []map[string]string{{"phase": "本周", "focus": "一次主动提出需要"}, {"phase": "本月", "focus": "建立付出与回收的平衡表"}}}
|
||||
case "ESTJ":
|
||||
return pack{Label: label, Summary: "你讲效率、抓标准,擅长把混乱整理成秩序。", Overview: "你推动系统运转,也可能被看成强硬。成长点是解释标准背后的公平意图。", Strengths: []string{"组织力", "决断", "落地快"}, Watchouts: []string{"命令口吻", "忽视个别情况", "急于纠正"}, Tips: []string{"指令后加一句「你怎么看」", "例外审批透明化", "表扬合规与创新并重"}, Scripts: []string{"标准是为了……,特殊情况我们可以……", "先按这个做一版,再一起复盘。"}, GrowthPlan: []map[string]string{{"phase": "本周", "focus": "一次决策征求异议"}, {"phase": "本月", "focus": "写清团队共同规则"}}}
|
||||
case "ESFJ":
|
||||
return pack{Label: label, Summary: "你重视和谐与归属,善于把人连接成互相照顾的圈。", Overview: "和谐重要,但回避真实分歧会累积委屈。成长点是温和地谈不同意。", Strengths: []string{"协调", "热心", "仪式感"}, Watchouts: []string{"过度在意评价", "讨好", "压抑不满"}, Tips: []string{"分歧用「我观察/我需要」句式", "减少即时回消息压力", "为自己保留无角色时间"}, Scripts: []string{"我很在乎我们关系,所以想说一下我的不适……", "大家开心很重要,我也需要……被考虑。"}, GrowthPlan: []map[string]string{{"phase": "本周", "focus": "一次诚实但不伤人的反馈"}, {"phase": "本月", "focus": "减少一次为和谐的妥协"}}}
|
||||
case "ISTP":
|
||||
return pack{Label: label, Summary: "你冷静拆解问题,动手能力强,讨厌空洞说教。", Overview: "你用行动证明理解。成长点是在关系里多给一点过程说明,避免被当成冷淡。", Strengths: []string{"实操", "危机冷静", "独立"}, Watchouts: []string{"话少被误解", "回避情绪话题", "承诺随意"}, Tips: []string{"行动前后各说一句意图", "定期短同步代替长会议", "练习命名当下感受"}, Scripts: []string{"我去处理……,大概多久回来。", "我不一定能立刻共情,但我可以帮忙做……"}, GrowthPlan: []map[string]string{{"phase": "本周", "focus": "三次行动前口头同步"}, {"phase": "本月", "focus": "一次情绪向长谈不逃开"}}}
|
||||
case "ISFP":
|
||||
return pack{Label: label, Summary: "你活在感受与美感里,用体验表达自己,讨厌被强迫。", Overview: "你需要空间与节奏。成长点是在被压时及时说「停」,而不是默默消失。", Strengths: []string{"审美敏感", "温和", "当下投入"}, Watchouts: []string{"逃避压力", "需求不清", "突然抽离"}, Tips: []string{"压力信号出现就说", "用创作或散步调节", "重要约定写成双方可见"}, Scripts: []string{"我现在有点满,需要安静恢复一下。", "这件事的感觉对我很重要,我们慢慢谈。"}, GrowthPlan: []map[string]string{{"phase": "本周", "focus": "一次及时表达边界"}, {"phase": "本月", "focus": "建立个人恢复清单"}}}
|
||||
case "ESTP":
|
||||
return pack{Label: label, Summary: "你抓当下机会、行动快,喜欢真刀真枪地试。", Overview: "冲劲带来结果,也可能低估后果。成长点是重大行动前留 10 分钟风险评估。", Strengths: []string{"应变", "胆识", "现实感"}, Watchouts: []string{"冲动", "忽略长远", "听不进劝"}, Tips: []string{"大决定用「最坏情况」三问", "找一个踩刹车的伙伴", "运动发泄代替硬刚冲突"}, Scripts: []string{"我想先试一版,失败了我们立刻收。", "你提醒的风险我记下了,我的底线是……"}, GrowthPlan: []map[string]string{{"phase": "本周", "focus": "一次冲动决定延后 24 小时"}, {"phase": "本月", "focus": "建立行动前检查卡"}}}
|
||||
case "ESFP":
|
||||
return pack{Label: label, Summary: "你把活力带给现场,重视体验、分享与即时快乐。", Overview: "你点亮气氛,也可能回避沉闷的深度议题。成长点是留下处理难聊话题的固定时段。", Strengths: []string{"感染力", "体贴当下", "乐观"}, Watchouts: []string{"回避严肃议题", "过度取悦", "财务/计划松散"}, Tips: []string{"每周一次「认真聊」预约", "快乐预算设上限", "用日历保护恢复日"}, Scripts: []string{"今天先开心,明天我们认真谈……可以吗?", "我很想让大家轻松,但这件事我也有压力……"}, GrowthPlan: []map[string]string{{"phase": "本周", "focus": "完成一次被拖延的认真对话"}, {"phase": "本月", "focus": "建立简单收支或计划习惯"}}}
|
||||
default:
|
||||
return pack{Label: label, Summary: "你的四维偏好组合较均衡或尚未充分显现主导面。", Overview: "可把本次结果当作起点,过一段时间在不同情境再测,观察哪一维更稳定。", Strengths: []string{"灵活", "可塑", "情境适应"}, Watchouts: []string{"自我描述模糊", "随大流", "缺少稳定策略"}, Tips: []string{"记录一周高能/耗能场景", "对争议维多做情景题自问", "与信任的人对照描述"}, Scripts: []string{"我还在认识自己,目前更接近……", "在……情境里我更像……"}, GrowthPlan: []map[string]string{{"phase": "本周", "focus": "日记标注能量来源"}, {"phase": "本月", "focus": "复测并对比变化"}}}
|
||||
}
|
||||
}
|
||||
@@ -32,11 +32,43 @@ func resultPack(slug, key, fallbackLabel string) pack {
|
||||
switch slug {
|
||||
case "emotion-pattern":
|
||||
return emotionPack(key, fallbackLabel)
|
||||
case "mbti-lite":
|
||||
return mbtiPack(key, fallbackLabel)
|
||||
default:
|
||||
if key == "high" || key == "mid" || key == "low" {
|
||||
return bankExplorePack(fallbackLabel)
|
||||
}
|
||||
return communicationPack(key, fallbackLabel)
|
||||
}
|
||||
}
|
||||
|
||||
func bankExplorePack(label string) pack {
|
||||
if label == "" {
|
||||
label = "节奏适中型"
|
||||
}
|
||||
return pack{
|
||||
Label: label, ShareLine: "我的探索结果:" + label,
|
||||
Summary: "这是基于题库作答的自我探索速览,用于觉察倾向与日常参考,不是能力定论,更不是心理或医学诊断。",
|
||||
Overview: "题库结果会随作答波动。你可以把它当成一面镜子:哪些选项更容易选中、身体与情绪有什么反应,比「分数高低」更重要。",
|
||||
Dimensions: []map[string]interface{}{
|
||||
{"title": "自我觉察", "score": 72, "note": "留意答题时的身体感受"},
|
||||
{"title": "可行动性", "score": 68, "note": "挑一条小习惯试一周"},
|
||||
{"title": "非定论", "score": 90, "note": "结果可随阶段变化"},
|
||||
},
|
||||
Strengths: []string{"愿意自我观察", "愿意花时间完成题库"},
|
||||
Watchouts: []string{"不要把结果当标签钉死", "不适情绪请寻求专业支持"},
|
||||
Tips: []string{"截图保存一句对你有感的描述", "把一条建议写成今日小行动", "可与问答助手聊聊具体情境"},
|
||||
Scripts: []string{"这份结果让我想到……", "我想先观察一周再决定要不要改习惯。"},
|
||||
GrowthPlan: []map[string]string{
|
||||
{"phase": "本周", "focus": "选一条建议做一次小实验"},
|
||||
{"phase": "本月", "focus": "复盘:哪些描述仍然贴切"},
|
||||
},
|
||||
FAQ: []map[string]string{
|
||||
{"q": "这是心理诊断吗?", "a": "不是。愈心谷题库只做自我探索参考,不能替代专业评估或治疗。"},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func communicationPack(key, fallback string) pack {
|
||||
switch key {
|
||||
case "A":
|
||||
|
||||
@@ -40,3 +40,45 @@ func EmotionLabels() map[string]string {
|
||||
"C": "行动调节型",
|
||||
}
|
||||
}
|
||||
|
||||
// ScoreMBTI tallies E/I · S/N · T/F · J/P and returns a 4-letter type code + Chinese label.
|
||||
func ScoreMBTI(answers map[string]string) (typeCode, label string) {
|
||||
count := func(a, b string) (na, nb int) {
|
||||
for _, v := range answers {
|
||||
switch v {
|
||||
case a:
|
||||
na++
|
||||
case b:
|
||||
nb++
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
pick := func(a, b string, na, nb int) string {
|
||||
if nb > na {
|
||||
return b
|
||||
}
|
||||
return a
|
||||
}
|
||||
e, i := count("E", "I")
|
||||
s, n := count("S", "N")
|
||||
t, f := count("T", "F")
|
||||
j, p := count("J", "P")
|
||||
code := pick("E", "I", e, i) + pick("S", "N", s, n) + pick("T", "F", t, f) + pick("J", "P", j, p)
|
||||
return code, MBTILabel(code)
|
||||
}
|
||||
|
||||
// MBTILabel maps 4-letter exploration code to a lexicon-safe Chinese label.
|
||||
func MBTILabel(code string) string {
|
||||
if l, ok := mbtiLabels[code]; ok {
|
||||
return l
|
||||
}
|
||||
return "平衡探索型"
|
||||
}
|
||||
|
||||
var mbtiLabels = map[string]string{
|
||||
"INTJ": "战略建构者", "INTP": "概念探路者", "ENTJ": "目标推动者", "ENTP": "灵感挑战者",
|
||||
"INFJ": "愿景洞察者", "INFP": "价值守护者", "ENFJ": "共鸣召集者", "ENFP": "热情启发者",
|
||||
"ISTJ": "稳健执行者", "ISFJ": "细致守护者", "ESTJ": "秩序统筹者", "ESFJ": "温暖协调者",
|
||||
"ISTP": "务实拆解者", "ISFP": "感受体验者", "ESTP": "当下行动者", "ESFP": "活力分享者",
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package scale
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestScoreMajority_communication(t *testing.T) {
|
||||
key, label := ScoreMajority(map[string]string{
|
||||
@@ -20,6 +23,30 @@ func TestScoreMajority_emotion(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestScoreJungian_ENFP(t *testing.T) {
|
||||
// 8 Qs per dim: EI low→E, SN high→N, TF low→F, JP high→P
|
||||
meta := map[string]JungianQuestionMeta{}
|
||||
answers := map[string]string{}
|
||||
add := func(dim string, ids []string, vals []int) {
|
||||
for i, id := range ids {
|
||||
meta[id] = JungianQuestionMeta{Dimension: dim}
|
||||
answers[id] = strconv.Itoa(vals[i])
|
||||
}
|
||||
}
|
||||
add("EI", []string{"e1", "e2", "e3", "e4", "e5", "e6", "e7", "e8"}, []int{1, 1, 2, 1, 2, 1, 1, 2}) // sum 11 → E
|
||||
add("SN", []string{"s1", "s2", "s3", "s4", "s5", "s6", "s7", "s8"}, []int{5, 5, 4, 5, 4, 5, 5, 4}) // sum 37 → N
|
||||
add("TF", []string{"t1", "t2", "t3", "t4", "t5", "t6", "t7", "t8"}, []int{1, 2, 1, 2, 1, 1, 2, 1}) // sum 11 → F
|
||||
add("JP", []string{"j1", "j2", "j3", "j4", "j5", "j6", "j7", "j8"}, []int{5, 4, 5, 5, 4, 5, 5, 4}) // sum 37 → P
|
||||
code, _, pct := ScoreJungian(answers, meta, 8)
|
||||
if code != "ENFP" {
|
||||
t.Fatalf("code=%s want ENFP", code)
|
||||
}
|
||||
r := BuildJungianResult(code, pct)
|
||||
if r["type_code"] != "ENFP" {
|
||||
t.Fatalf("type_code=%v", r["type_code"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestScoreMajority_empty(t *testing.T) {
|
||||
_, label := ScoreMajority(nil, CommunicationLabels(), "平衡探索型")
|
||||
if label != "平衡探索型" {
|
||||
|
||||
Reference in New Issue
Block a user