feat: 完善中文心理测评平台
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
import { QuestionType } from "@/types";
|
||||
|
||||
/**
|
||||
* ADHD Self-Report Scale calculator parameters interface
|
||||
*/
|
||||
interface ADHDCalculatorProps {
|
||||
/** Answer data, key is question ID, value is selected score */
|
||||
answers: { [key: number]: string };
|
||||
/** Questions list */
|
||||
questions: QuestionType[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate ADHD Self-Report Scale (ASRS-v1.1) results
|
||||
*
|
||||
* @param answers - User answer data, containing question ID and selected score
|
||||
* @returns Calculation results, including total score, factor scores, severity level and other information
|
||||
*/
|
||||
export const calculateADHDResults = ({ answers }: ADHDCalculatorProps): any => {
|
||||
// ADHD ASRS calculation logic
|
||||
let totalScore = 0;
|
||||
|
||||
// Subscale score initialization: inattention and hyperactivity-impulsivity
|
||||
let inattentionScore = 0; // Inattention subscale score (questions 1-9)
|
||||
let hyperactivityScore = 0; // Hyperactivity-Impulsivity subscale score (questions 10-18)
|
||||
|
||||
// Part A screening questions (questions 1-6) - critical for screening
|
||||
let partAScore = 0;
|
||||
let partAPositive = 0; // Count of positive responses in Part A
|
||||
|
||||
// Calculate total score and subscale scores
|
||||
Object.entries(answers).forEach(([questionId, score]) => {
|
||||
const questionNum = parseInt(questionId);
|
||||
const scoreValue = parseInt(score);
|
||||
|
||||
totalScore += scoreValue;
|
||||
|
||||
// Part A screening questions (questions 1-6)
|
||||
if (questionNum >= 1 && questionNum <= 6) {
|
||||
partAScore += scoreValue;
|
||||
// Part A positive criteria: questions 1-4 score ≥2, questions 5-6 score ≥3
|
||||
if ((questionNum >= 1 && questionNum <= 4 && scoreValue >= 2) ||
|
||||
(questionNum >= 5 && questionNum <= 6 && scoreValue >= 3)) {
|
||||
partAPositive++;
|
||||
}
|
||||
}
|
||||
|
||||
// Inattention subscale (questions 1-9)
|
||||
if (questionNum >= 1 && questionNum <= 9) {
|
||||
inattentionScore += scoreValue;
|
||||
}
|
||||
// Hyperactivity-Impulsivity subscale (questions 10-18)
|
||||
else if (questionNum >= 10 && questionNum <= 18) {
|
||||
hyperactivityScore += scoreValue;
|
||||
}
|
||||
});
|
||||
|
||||
// Determine screening result based on Part A
|
||||
const screeningPositive = partAPositive >= 4;
|
||||
|
||||
// Determine severity level based on total score
|
||||
let severity = "low";
|
||||
if (totalScore >= 24 && totalScore <= 36) {
|
||||
severity = "mild";
|
||||
} else if (totalScore >= 37 && totalScore <= 54) {
|
||||
severity = "moderate";
|
||||
} else if (totalScore >= 55) {
|
||||
severity = "high";
|
||||
}
|
||||
|
||||
// Calculate factor scores
|
||||
const factorScores: { [key: string]: number } = {
|
||||
"inattention": inattentionScore,
|
||||
"hyperactivity": hyperactivityScore,
|
||||
"partA": partAScore
|
||||
};
|
||||
|
||||
// Return complete calculation results
|
||||
return {
|
||||
totalScore,
|
||||
factorScores,
|
||||
positiveItemCount: Object.values(answers).filter(score => parseInt(score) >= 2).length,
|
||||
positiveItemAverage: totalScore / Object.keys(answers).length,
|
||||
isSevere: severity === "high",
|
||||
severity,
|
||||
screeningPositive,
|
||||
partAPositive,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
const avoidanceItems = [1, 2, 3, 4, 5, 6];
|
||||
const anxietyItems = [7, 8, 9];
|
||||
const reverseAvoidanceItems = new Set([1, 2, 3, 4]);
|
||||
|
||||
function scoreItem(answers: string[], item: number) {
|
||||
const raw = Number(answers[item - 1] || 1);
|
||||
return reverseAvoidanceItems.has(item) ? 8 - raw : raw;
|
||||
}
|
||||
|
||||
function average(values: number[]) {
|
||||
return values.reduce((sum, value) => sum + value, 0) / values.length;
|
||||
}
|
||||
|
||||
export function calculateAttachmentResults(answers: string[]) {
|
||||
const avoidance = average(avoidanceItems.map((item) => scoreItem(answers, item)));
|
||||
const anxiety = average(anxietyItems.map((item) => Number(answers[item - 1] || 1)));
|
||||
|
||||
let pattern:
|
||||
| 'secure'
|
||||
| 'preoccupied'
|
||||
| 'dismissive'
|
||||
| 'fearful' = 'secure';
|
||||
|
||||
const highAvoidance = avoidance >= 4;
|
||||
const highAnxiety = anxiety >= 4;
|
||||
|
||||
if (highAvoidance && highAnxiety) {
|
||||
pattern = 'fearful';
|
||||
} else if (highAvoidance) {
|
||||
pattern = 'dismissive';
|
||||
} else if (highAnxiety) {
|
||||
pattern = 'preoccupied';
|
||||
}
|
||||
|
||||
return {
|
||||
avoidance,
|
||||
anxiety,
|
||||
pattern,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
import { QuestionType } from "@/types";
|
||||
|
||||
interface BDI2CalculatorProps {
|
||||
answers: { [key: number]: string };
|
||||
questions: QuestionType[];
|
||||
}
|
||||
|
||||
export const calculateBDI2Results = ({ answers }: BDI2CalculatorProps): any => {
|
||||
// BDI-II calculation logic
|
||||
let totalScore = 0;
|
||||
let suicidalIdeation = false;
|
||||
|
||||
// Calculate total score (simple sum)
|
||||
Object.entries(answers).forEach(([questionId, score]) => {
|
||||
const scoreValue = parseInt(score);
|
||||
totalScore += scoreValue;
|
||||
|
||||
// Check question 9 (suicidal ideation)
|
||||
if (parseInt(questionId) === 9 && scoreValue >= 1) {
|
||||
suicidalIdeation = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Determine depression severity level
|
||||
let severity = "minimal";
|
||||
if (totalScore >= 29) {
|
||||
severity = "severe";
|
||||
} else if (totalScore >= 20) {
|
||||
severity = "moderate";
|
||||
} else if (totalScore >= 14) {
|
||||
severity = "mild";
|
||||
}
|
||||
|
||||
// Analyze item scores
|
||||
const itemAnalysis = Object.entries(answers).map(([questionId, score]) => ({
|
||||
questionId: parseInt(questionId),
|
||||
score: parseInt(score),
|
||||
isHigh: parseInt(score) >= 2 // Scores of 2 or above are considered high score items
|
||||
}));
|
||||
|
||||
const highScoreItems = itemAnalysis.filter(item => item.isHigh);
|
||||
|
||||
// Categorize analysis of different symptom domains
|
||||
const emotionalItems = [1, 2, 4, 5, 10, 14]; // Emotional symptoms
|
||||
const cognitiveItems = [3, 6, 8, 13, 19]; // Cognitive symptoms
|
||||
const somaticItems = [15, 16, 18, 20, 21]; // Somatic symptoms
|
||||
const behavioralItems = [7, 9, 11, 12, 17]; // Behavioral symptoms
|
||||
|
||||
const getSubscaleScore = (items: number[]) => {
|
||||
return items.reduce((sum, itemId) => {
|
||||
return sum + (answers[itemId] ? parseInt(answers[itemId]) : 0);
|
||||
}, 0);
|
||||
};
|
||||
|
||||
const emotionalScore = getSubscaleScore(emotionalItems);
|
||||
const cognitiveScore = getSubscaleScore(cognitiveItems);
|
||||
const somaticScore = getSubscaleScore(somaticItems);
|
||||
const behavioralScore = getSubscaleScore(behavioralItems);
|
||||
|
||||
return {
|
||||
totalScore,
|
||||
severity,
|
||||
suicidalIdeation,
|
||||
itemAnalysis,
|
||||
highScoreItemCount: highScoreItems.length,
|
||||
emotionalScore,
|
||||
cognitiveScore,
|
||||
somaticScore,
|
||||
behavioralScore,
|
||||
factorScores: {
|
||||
"emotional": emotionalScore,
|
||||
"cognitive": cognitiveScore,
|
||||
"somatic": somaticScore,
|
||||
"behavioral": behavioralScore
|
||||
},
|
||||
positiveItemCount: highScoreItems.length,
|
||||
positiveItemAverage: highScoreItems.length > 0
|
||||
? highScoreItems.reduce((sum, item) => sum + item.score, 0) / highScoreItems.length
|
||||
: 0,
|
||||
isSevere: severity === "severe" || suicidalIdeation
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,85 @@
|
||||
import {
|
||||
BigFiveDomain,
|
||||
IpipNeoItem,
|
||||
ipipNeo120Items,
|
||||
ipipNeo300Items,
|
||||
} from '@/questionairies/bigfive/neo-data';
|
||||
|
||||
const reverseItems = new Set([
|
||||
2, 4, 6, 8, 10, 12, 16, 18, 19, 20, 22, 24, 26, 28, 29, 30, 32, 34,
|
||||
36, 38, 39, 44, 46, 48, 49,
|
||||
]);
|
||||
|
||||
const dimensions = {
|
||||
extraversion: [1, 6, 11, 16, 21, 26, 31, 36, 41, 46],
|
||||
agreeableness: [2, 7, 12, 17, 22, 27, 32, 37, 42, 47],
|
||||
conscientiousness: [3, 8, 13, 18, 23, 28, 33, 38, 43, 48],
|
||||
emotionalStability: [4, 9, 14, 19, 24, 29, 34, 39, 44, 49],
|
||||
openness: [5, 10, 15, 20, 25, 30, 35, 40, 45, 50],
|
||||
};
|
||||
|
||||
export function calculateBigFiveResults(answers: string[]) {
|
||||
const scoreItem = (questionId: number) => {
|
||||
const raw = Number(answers[questionId - 1] || 0);
|
||||
if (!raw) return 0;
|
||||
return reverseItems.has(questionId) ? 6 - raw : raw;
|
||||
};
|
||||
|
||||
return Object.fromEntries(
|
||||
Object.entries(dimensions).map(([key, items]) => {
|
||||
const score = items.reduce((sum, item) => sum + scoreItem(item), 0);
|
||||
return [
|
||||
key,
|
||||
{
|
||||
score,
|
||||
average: score / items.length,
|
||||
},
|
||||
];
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export const ipipNeoItemsByVersion = {
|
||||
120: ipipNeo120Items,
|
||||
300: ipipNeo300Items,
|
||||
} as const;
|
||||
|
||||
export function calculateIpipNeoResults(
|
||||
answers: string[],
|
||||
items: IpipNeoItem[],
|
||||
) {
|
||||
const domainTotals = new Map<BigFiveDomain, { score: number; count: number }>();
|
||||
const facetTotals = new Map<string, { score: number; count: number }>();
|
||||
|
||||
items.forEach((item, index) => {
|
||||
const raw = Number(answers[index] || 0);
|
||||
const score = raw ? (item.reverse ? 6 - raw : raw) : 0;
|
||||
|
||||
const domain = domainTotals.get(item.domain) || { score: 0, count: 0 };
|
||||
domain.score += score;
|
||||
domain.count += 1;
|
||||
domainTotals.set(item.domain, domain);
|
||||
|
||||
const facet = facetTotals.get(item.facet) || { score: 0, count: 0 };
|
||||
facet.score += score;
|
||||
facet.count += 1;
|
||||
facetTotals.set(item.facet, facet);
|
||||
});
|
||||
|
||||
const toResults = (totals: Map<string, { score: number; count: number }>) =>
|
||||
Object.fromEntries(
|
||||
[...totals.entries()].map(([key, value]) => [
|
||||
key,
|
||||
{
|
||||
score: value.score,
|
||||
average: value.count ? value.score / value.count : 0,
|
||||
itemCount: value.count,
|
||||
},
|
||||
]),
|
||||
);
|
||||
|
||||
return {
|
||||
domains: toResults(domainTotals),
|
||||
facets: toResults(facetTotals),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
const correctAnswers: Record<number, string> = {
|
||||
1: 'B',
|
||||
2: 'A',
|
||||
3: 'B',
|
||||
4: 'A',
|
||||
5: 'B',
|
||||
6: 'B',
|
||||
7: 'B',
|
||||
};
|
||||
|
||||
export function calculateCRTResults(answers: string[]) {
|
||||
const items = answers.map((answer, index) => {
|
||||
const questionId = index + 1;
|
||||
return {
|
||||
questionId,
|
||||
selected: answer,
|
||||
correct: correctAnswers[questionId],
|
||||
isCorrect: answer === correctAnswers[questionId],
|
||||
};
|
||||
});
|
||||
|
||||
const score = items.filter((item) => item.isCorrect).length;
|
||||
|
||||
return {
|
||||
score,
|
||||
total: Object.keys(correctAnswers).length,
|
||||
items,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
export type CareerAnchor =
|
||||
| "technical"
|
||||
| "managerial"
|
||||
| "autonomy"
|
||||
| "security"
|
||||
| "entrepreneurial"
|
||||
| "service"
|
||||
| "challenge"
|
||||
| "lifestyle";
|
||||
|
||||
const anchorItems: Record<CareerAnchor, number[]> = {
|
||||
technical: [1, 2, 3, 4, 5],
|
||||
managerial: [6, 7, 8, 9, 10],
|
||||
autonomy: [11, 12, 13, 14, 15],
|
||||
security: [16, 17, 18, 19, 20],
|
||||
entrepreneurial: [21, 22, 23, 24, 25],
|
||||
service: [26, 27, 28, 29, 30],
|
||||
challenge: [31, 32, 33, 34, 35],
|
||||
lifestyle: [36, 37, 38, 39, 40],
|
||||
};
|
||||
|
||||
export const anchorNames: Record<CareerAnchor, string> = {
|
||||
technical: "技术/职能能力",
|
||||
managerial: "综合管理能力",
|
||||
autonomy: "自主/独立",
|
||||
security: "安全/稳定",
|
||||
entrepreneurial: "创业创造",
|
||||
service: "服务/奉献",
|
||||
challenge: "纯粹挑战",
|
||||
lifestyle: "生活方式",
|
||||
};
|
||||
|
||||
function average(values: number[]) {
|
||||
return values.reduce((sum, value) => sum + value, 0) / values.length;
|
||||
}
|
||||
|
||||
export function calculateCareerAnchorsResults(answers: string[]) {
|
||||
const scores = Object.fromEntries(
|
||||
Object.entries(anchorItems).map(([anchor, items]) => [
|
||||
anchor,
|
||||
average(items.map((item) => Number(answers[item - 1] || 1))),
|
||||
]),
|
||||
) as Record<CareerAnchor, number>;
|
||||
|
||||
const ranked = (Object.keys(scores) as CareerAnchor[])
|
||||
.map((id) => ({ id, name: anchorNames[id], score: scores[id] }))
|
||||
.sort((a, b) => b.score - a.score);
|
||||
|
||||
return {
|
||||
scores,
|
||||
ranked,
|
||||
primary: ranked[0],
|
||||
secondary: ranked[1],
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
import { QuestionType } from "@/types";
|
||||
|
||||
interface DASS21CalculatorProps {
|
||||
answers: { [key: number]: string };
|
||||
questions: QuestionType[];
|
||||
}
|
||||
|
||||
export const calculateDASS21Results = ({ answers }: DASS21CalculatorProps): any => {
|
||||
// DASS-21 calculation logic
|
||||
// Depression dimension items: 3, 5, 10, 13, 16, 17, 21
|
||||
// Anxiety dimension items: 2, 4, 7, 9, 15, 19, 20
|
||||
// Stress dimension items: 1, 6, 8, 11, 12, 14, 18
|
||||
|
||||
const depressionItems = [3, 5, 10, 13, 16, 17, 21];
|
||||
const anxietyItems = [2, 4, 7, 9, 15, 19, 20];
|
||||
const stressItems = [1, 6, 8, 11, 12, 14, 18];
|
||||
|
||||
let depressionScore = 0;
|
||||
let anxietyScore = 0;
|
||||
let stressScore = 0;
|
||||
let totalScore = 0;
|
||||
|
||||
// Calculate dimension scores
|
||||
Object.entries(answers).forEach(([questionId, score]) => {
|
||||
const questionNum = parseInt(questionId);
|
||||
const scoreValue = parseInt(score);
|
||||
|
||||
totalScore += scoreValue;
|
||||
|
||||
if (depressionItems.includes(questionNum)) {
|
||||
depressionScore += scoreValue;
|
||||
} else if (anxietyItems.includes(questionNum)) {
|
||||
anxietyScore += scoreValue;
|
||||
} else if (stressItems.includes(questionNum)) {
|
||||
stressScore += scoreValue;
|
||||
}
|
||||
});
|
||||
|
||||
// DASS-21 scores need to be multiplied by 2 for comparison with DASS-42
|
||||
const finalDepressionScore = depressionScore * 2;
|
||||
const finalAnxietyScore = anxietyScore * 2;
|
||||
const finalStressScore = stressScore * 2;
|
||||
|
||||
// Determine severity level for each dimension
|
||||
const getDepressionSeverity = (score: number) => {
|
||||
if (score <= 9) return "normal";
|
||||
if (score <= 13) return "mild";
|
||||
if (score <= 20) return "moderate";
|
||||
if (score <= 27) return "severe";
|
||||
return "extremely_severe";
|
||||
};
|
||||
|
||||
const getAnxietySeverity = (score: number) => {
|
||||
if (score <= 7) return "normal";
|
||||
if (score <= 9) return "mild";
|
||||
if (score <= 14) return "moderate";
|
||||
if (score <= 19) return "severe";
|
||||
return "extremely_severe";
|
||||
};
|
||||
|
||||
const getStressSeverity = (score: number) => {
|
||||
if (score <= 14) return "normal";
|
||||
if (score <= 18) return "mild";
|
||||
if (score <= 25) return "moderate";
|
||||
if (score <= 33) return "severe";
|
||||
return "extremely_severe";
|
||||
};
|
||||
|
||||
const depressionSeverity = getDepressionSeverity(finalDepressionScore);
|
||||
const anxietySeverity = getAnxietySeverity(finalAnxietyScore);
|
||||
const stressSeverity = getStressSeverity(finalStressScore);
|
||||
|
||||
// Determine overall severity level
|
||||
const isAnySevere = depressionSeverity === "severe" || depressionSeverity === "extremely_severe" ||
|
||||
anxietySeverity === "severe" || anxietySeverity === "extremely_severe" ||
|
||||
stressSeverity === "severe" || stressSeverity === "extremely_severe";
|
||||
|
||||
return {
|
||||
totalScore,
|
||||
depressionScore: finalDepressionScore,
|
||||
anxietyScore: finalAnxietyScore,
|
||||
stressScore: finalStressScore,
|
||||
depressionSeverity,
|
||||
anxietySeverity,
|
||||
stressSeverity,
|
||||
factorScores: {
|
||||
"depression": finalDepressionScore,
|
||||
"anxiety": finalAnxietyScore,
|
||||
"stress": finalStressScore
|
||||
},
|
||||
positiveItemCount: Object.values(answers).filter(score => parseInt(score) >= 2).length,
|
||||
positiveItemAverage: totalScore / Object.keys(answers).length,
|
||||
isSevere: isAnySevere
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
const dimensions = {
|
||||
machiavellianism: [1, 2, 3, 4, 5, 6, 7, 8, 9],
|
||||
narcissism: [10, 11, 12, 13, 14, 15, 16, 17, 18],
|
||||
psychopathy: [19, 20, 21, 22, 23, 24, 25, 26, 27],
|
||||
} as const;
|
||||
|
||||
const reverseItems = new Set([4, 8, 11, 15, 17, 20, 25]);
|
||||
|
||||
function scoreItem(answers: string[], item: number) {
|
||||
const raw = Number(answers[item - 1] || 1);
|
||||
return reverseItems.has(item) ? 6 - raw : raw;
|
||||
}
|
||||
|
||||
function average(scores: number[]) {
|
||||
return scores.reduce((sum, score) => sum + score, 0) / scores.length;
|
||||
}
|
||||
|
||||
function dimensionAverage(answers: string[], items: readonly number[]) {
|
||||
return average(items.map((item) => scoreItem(answers, item)));
|
||||
}
|
||||
|
||||
export function calculateDarkTriadResults(answers: string[]) {
|
||||
return {
|
||||
machiavellianism: dimensionAverage(answers, dimensions.machiavellianism),
|
||||
narcissism: dimensionAverage(answers, dimensions.narcissism),
|
||||
psychopathy: dimensionAverage(answers, dimensions.psychopathy),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
const subscales = {
|
||||
perspectiveTaking: [1, 2, 3, 4, 5, 6, 7],
|
||||
empathicConcern: [8, 9, 10, 11, 12, 13, 14],
|
||||
personalDistress: [15, 16, 17, 18, 19, 20, 21],
|
||||
fantasy: [22, 23, 24, 25, 26, 27, 28],
|
||||
} as const;
|
||||
|
||||
const reverseItems = new Set([2, 4, 6, 10, 13, 17, 20, 24, 26, 28]);
|
||||
|
||||
function scoreItem(answers: string[], item: number) {
|
||||
const raw = Number(answers[item - 1] || 1);
|
||||
return reverseItems.has(item) ? 6 - raw : raw;
|
||||
}
|
||||
|
||||
function average(scores: number[]) {
|
||||
return scores.reduce((sum, score) => sum + score, 0) / scores.length;
|
||||
}
|
||||
|
||||
function subscaleAverage(answers: string[], items: readonly number[]) {
|
||||
return average(items.map((item) => scoreItem(answers, item)));
|
||||
}
|
||||
|
||||
export function calculateEmpathyResults(answers: string[]) {
|
||||
return {
|
||||
perspectiveTaking: subscaleAverage(answers, subscales.perspectiveTaking),
|
||||
empathicConcern: subscaleAverage(answers, subscales.empathicConcern),
|
||||
personalDistress: subscaleAverage(answers, subscales.personalDistress),
|
||||
fantasy: subscaleAverage(answers, subscales.fantasy),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
const dimensions = {
|
||||
explorer: [1, 5, 9, 13, 17, 21, 25, 29, 33, 37],
|
||||
builder: [2, 6, 10, 14, 18, 22, 26, 30, 34, 38],
|
||||
director: [3, 7, 11, 15, 19, 23, 27, 31, 35, 39],
|
||||
negotiator: [4, 8, 12, 16, 20, 24, 28, 32, 36, 40],
|
||||
} as const;
|
||||
|
||||
export type FisherDimension = keyof typeof dimensions;
|
||||
|
||||
const dimensionNames: Record<FisherDimension, string> = {
|
||||
explorer: "探索者",
|
||||
builder: "建设者",
|
||||
director: "指挥者",
|
||||
negotiator: "协商者",
|
||||
};
|
||||
|
||||
function scoreDimension(answers: string[], items: readonly number[]) {
|
||||
return items.reduce((sum, item) => sum + Number(answers[item - 1] || 1), 0);
|
||||
}
|
||||
|
||||
export function calculateFisherResults(answers: string[]) {
|
||||
const scores = Object.fromEntries(
|
||||
Object.entries(dimensions).map(([dimension, items]) => [
|
||||
dimension,
|
||||
scoreDimension(answers, items),
|
||||
]),
|
||||
) as Record<FisherDimension, number>;
|
||||
|
||||
const ranked = (Object.keys(scores) as FisherDimension[])
|
||||
.map((id) => ({
|
||||
id,
|
||||
name: dimensionNames[id],
|
||||
score: scores[id],
|
||||
percentage: Math.round(((scores[id] - 10) / 30) * 100),
|
||||
}))
|
||||
.sort((a, b) => b.score - a.score);
|
||||
|
||||
return {
|
||||
scores,
|
||||
ranked,
|
||||
primary: ranked[0],
|
||||
secondary: ranked[1],
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { QuestionType } from "@/types";
|
||||
|
||||
interface GAD7CalculatorProps {
|
||||
answers: { [key: number]: string };
|
||||
questions: QuestionType[];
|
||||
}
|
||||
|
||||
export const calculateGAD7Results = ({ answers }: GAD7CalculatorProps): any => {
|
||||
// GAD-7 calculation logic
|
||||
let totalScore = 0;
|
||||
|
||||
// Calculate total score (simple sum)
|
||||
Object.entries(answers).forEach(([, score]) => {
|
||||
const scoreValue = parseInt(score);
|
||||
totalScore += scoreValue;
|
||||
});
|
||||
|
||||
// Determine anxiety severity level
|
||||
let severity = "minimal";
|
||||
if (totalScore >= 15) {
|
||||
severity = "severe";
|
||||
} else if (totalScore >= 10) {
|
||||
severity = "moderate";
|
||||
} else if (totalScore >= 5) {
|
||||
severity = "mild";
|
||||
}
|
||||
|
||||
// Analyze item scores
|
||||
const itemAnalysis = Object.entries(answers).map(([questionId, score]) => ({
|
||||
questionId: parseInt(questionId),
|
||||
score: parseInt(score),
|
||||
isHigh: parseInt(score) >= 2 // Scores of 2 or above are considered high score items
|
||||
}));
|
||||
|
||||
const highScoreItems = itemAnalysis.filter(item => item.isHigh);
|
||||
|
||||
return {
|
||||
totalScore,
|
||||
severity,
|
||||
itemAnalysis,
|
||||
highScoreItemCount: highScoreItems.length,
|
||||
factorScores: {}, // GAD-7 is a single-factor scale
|
||||
positiveItemCount: highScoreItems.length,
|
||||
positiveItemAverage: highScoreItems.length > 0
|
||||
? highScoreItems.reduce((sum, item) => sum + item.score, 0) / highScoreItems.length
|
||||
: 0,
|
||||
isSevere: severity === "severe" || severity === "moderate"
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,109 @@
|
||||
import { QuestionType } from "@/types";
|
||||
|
||||
/**
|
||||
* Gender Dysphoria Questionnaire calculator parameters interface
|
||||
*/
|
||||
interface GDCalculatorProps {
|
||||
/** Answer data, key is question ID, value is selected score */
|
||||
answers: { [key: number]: string };
|
||||
/** Questions list */
|
||||
questions: QuestionType[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate Gender Dysphoria Questionnaire (GDQ) results
|
||||
*
|
||||
* @param answers - User answer data, containing question ID and selected score
|
||||
* @returns Calculation results, including total score, factor scores, and other information
|
||||
*/
|
||||
export const calculateGDResults = ({ answers }: GDCalculatorProps): any => {
|
||||
// Gender Dysphoria calculation logic
|
||||
let totalRawScore = 0;
|
||||
|
||||
// Reverse-scored items (questions where agreement indicates comfort with assigned gender)
|
||||
const reverseItems = [1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25];
|
||||
|
||||
// Calculate total score with reverse scoring
|
||||
Object.entries(answers).forEach(([questionId, score]) => {
|
||||
const questionNum = parseInt(questionId);
|
||||
const scoreValue = parseInt(score);
|
||||
|
||||
if (reverseItems.includes(questionNum)) {
|
||||
// Reverse score: 7 becomes 1, 6 becomes 2, etc.
|
||||
totalRawScore += (8 - scoreValue);
|
||||
} else {
|
||||
totalRawScore += scoreValue;
|
||||
}
|
||||
});
|
||||
|
||||
// Calculate factor scores based on conceptual dimensions
|
||||
let genderIdentityScore = 0; // Questions about internal gender identity
|
||||
let socialRoleScore = 0; // Questions about social gender roles
|
||||
let physicalDysphoriaScore = 0; // Questions about physical characteristics
|
||||
let genderExpressionScore = 0; // Questions about gender expression
|
||||
|
||||
Object.entries(answers).forEach(([questionId, score]) => {
|
||||
const questionNum = parseInt(questionId);
|
||||
let scoreValue = parseInt(score);
|
||||
|
||||
// Apply reverse scoring if needed
|
||||
if (reverseItems.includes(questionNum)) {
|
||||
scoreValue = 8 - scoreValue;
|
||||
}
|
||||
|
||||
// Assign to factor scores based on question content
|
||||
if ([2, 4, 6, 17, 18, 20, 22, 24, 27].includes(questionNum)) {
|
||||
genderIdentityScore += scoreValue;
|
||||
} else if ([5, 7, 12, 19, 21, 26].includes(questionNum)) {
|
||||
socialRoleScore += scoreValue;
|
||||
} else if ([3, 9, 10, 13, 15, 16].includes(questionNum)) {
|
||||
physicalDysphoriaScore += scoreValue;
|
||||
} else if ([1, 8, 11, 14, 23, 25].includes(questionNum)) {
|
||||
genderExpressionScore += scoreValue;
|
||||
}
|
||||
});
|
||||
|
||||
// Determine general interpretation level
|
||||
const totalPossibleScore = Object.keys(answers).length * 7;
|
||||
const scorePercentage = (totalRawScore / totalPossibleScore) * 100;
|
||||
|
||||
let interpretation = "low";
|
||||
if (scorePercentage >= 30 && scorePercentage < 50) {
|
||||
interpretation = "mild";
|
||||
} else if (scorePercentage >= 50 && scorePercentage < 70) {
|
||||
interpretation = "moderate";
|
||||
} else if (scorePercentage >= 70) {
|
||||
interpretation = "high";
|
||||
}
|
||||
|
||||
// Calculate factor scores
|
||||
const factorScores: { [key: string]: number } = {
|
||||
"genderIdentity": genderIdentityScore,
|
||||
"socialRole": socialRoleScore,
|
||||
"physicalDysphoria": physicalDysphoriaScore,
|
||||
"genderExpression": genderExpressionScore
|
||||
};
|
||||
|
||||
// Count items with scores above neutral (>4 after reverse scoring)
|
||||
const elevatedItems = Object.entries(answers).filter(([questionId, score]) => {
|
||||
const questionNum = parseInt(questionId);
|
||||
let scoreValue = parseInt(score);
|
||||
|
||||
if (reverseItems.includes(questionNum)) {
|
||||
scoreValue = 8 - scoreValue;
|
||||
}
|
||||
|
||||
return scoreValue > 4;
|
||||
}).length;
|
||||
|
||||
// Return complete calculation results
|
||||
return {
|
||||
totalScore: totalRawScore,
|
||||
factorScores,
|
||||
positiveItemCount: elevatedItems,
|
||||
positiveItemAverage: totalRawScore / Object.keys(answers).length,
|
||||
isSevere: interpretation === "high",
|
||||
interpretation,
|
||||
scorePercentage: Math.round(scorePercentage),
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
const reverseItems = new Set([1, 3, 5, 6]);
|
||||
const perseveranceItems = [2, 4, 7, 8];
|
||||
const consistencyItems = [1, 3, 5, 6];
|
||||
|
||||
function scoreItem(answers: string[], item: number) {
|
||||
const raw = Number(answers[item - 1] || 1);
|
||||
return reverseItems.has(item) ? 6 - raw : raw;
|
||||
}
|
||||
|
||||
function average(values: number[]) {
|
||||
return values.reduce((sum, value) => sum + value, 0) / values.length;
|
||||
}
|
||||
|
||||
export function calculateGritResults(answers: string[]) {
|
||||
const itemScores = Array.from({ length: 8 }, (_, index) =>
|
||||
scoreItem(answers, index + 1),
|
||||
);
|
||||
const totalAverage = average(itemScores);
|
||||
const perseveranceAverage = average(
|
||||
perseveranceItems.map((item) => scoreItem(answers, item)),
|
||||
);
|
||||
const consistencyAverage = average(
|
||||
consistencyItems.map((item) => scoreItem(answers, item)),
|
||||
);
|
||||
|
||||
let level: 'low' | 'moderate' | 'high' = 'moderate';
|
||||
if (totalAverage < 3) {
|
||||
level = 'low';
|
||||
} else if (totalAverage >= 4) {
|
||||
level = 'high';
|
||||
}
|
||||
|
||||
return {
|
||||
itemScores,
|
||||
totalAverage,
|
||||
perseveranceAverage,
|
||||
consistencyAverage,
|
||||
level,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
const domains = {
|
||||
honestyHumility: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
|
||||
emotionality: [11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
|
||||
extraversion: [21, 22, 23, 24, 25, 26, 27, 28, 29, 30],
|
||||
agreeableness: [31, 32, 33, 34, 35, 36, 37, 38, 39, 40],
|
||||
conscientiousness: [41, 42, 43, 44, 45, 46, 47, 48, 49, 50],
|
||||
openness: [51, 52, 53, 54, 55, 56, 57, 58, 59, 60],
|
||||
} as const;
|
||||
|
||||
const reverseItems = new Set([
|
||||
2, 4, 6, 8, 10,
|
||||
12, 14, 16, 18, 20,
|
||||
22, 24, 26, 28, 30,
|
||||
32, 34, 36, 38, 40,
|
||||
42, 44, 46, 48, 50,
|
||||
52, 54, 56, 58, 60,
|
||||
]);
|
||||
|
||||
function scoreItem(answers: string[], item: number) {
|
||||
const raw = Number(answers[item - 1] || 1);
|
||||
return reverseItems.has(item) ? 6 - raw : raw;
|
||||
}
|
||||
|
||||
function average(scores: number[]) {
|
||||
return scores.reduce((sum, score) => sum + score, 0) / scores.length;
|
||||
}
|
||||
|
||||
function domainAverage(answers: string[], items: readonly number[]) {
|
||||
return average(items.map((item) => scoreItem(answers, item)));
|
||||
}
|
||||
|
||||
export function calculateHEXACOResults(answers: string[]) {
|
||||
return {
|
||||
honestyHumility: domainAverage(answers, domains.honestyHumility),
|
||||
emotionality: domainAverage(answers, domains.emotionality),
|
||||
extraversion: domainAverage(answers, domains.extraversion),
|
||||
agreeableness: domainAverage(answers, domains.agreeableness),
|
||||
conscientiousness: domainAverage(answers, domains.conscientiousness),
|
||||
openness: domainAverage(answers, domains.openness),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { QuestionType } from "@/types";
|
||||
|
||||
interface ISICalculatorProps {
|
||||
answers: { [key: number]: string };
|
||||
questions: QuestionType[];
|
||||
}
|
||||
|
||||
export const calculateISIResults = ({ answers }: ISICalculatorProps): any => {
|
||||
// ISI calculation logic
|
||||
let totalScore = 0;
|
||||
|
||||
// Calculate total score (simple sum)
|
||||
Object.entries(answers).forEach(([, score]) => {
|
||||
const scoreValue = parseInt(score);
|
||||
totalScore += scoreValue;
|
||||
});
|
||||
|
||||
// Determine insomnia severity level
|
||||
let severity = "no_insomnia";
|
||||
if (totalScore >= 22) {
|
||||
severity = "severe";
|
||||
} else if (totalScore >= 15) {
|
||||
severity = "moderate";
|
||||
} else if (totalScore >= 8) {
|
||||
severity = "subthreshold";
|
||||
}
|
||||
|
||||
// Analyze item scores
|
||||
const itemAnalysis = Object.entries(answers).map(([questionId, score]) => ({
|
||||
questionId: parseInt(questionId),
|
||||
score: parseInt(score),
|
||||
isHigh: parseInt(score) >= 3 // Scores of 3 or above are considered high score items
|
||||
}));
|
||||
|
||||
const highScoreItems = itemAnalysis.filter(item => item.isHigh);
|
||||
|
||||
return {
|
||||
totalScore,
|
||||
severity,
|
||||
itemAnalysis,
|
||||
highScoreItemCount: highScoreItems.length,
|
||||
factorScores: {},
|
||||
positiveItemCount: highScoreItems.length,
|
||||
positiveItemAverage: highScoreItems.length > 0
|
||||
? highScoreItems.reduce((sum, item) => sum + item.score, 0) / highScoreItems.length
|
||||
: 0,
|
||||
isSevere: severity === "severe" || severity === "moderate"
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
const highStandardItems = [1, 4, 5, 10, 13];
|
||||
const searchItems = [2, 3, 6, 8, 9, 12];
|
||||
const difficultyItems = [7, 11];
|
||||
|
||||
function average(scores: number[]) {
|
||||
return scores.reduce((sum, score) => sum + score, 0) / scores.length;
|
||||
}
|
||||
|
||||
function scoreItems(answers: string[], items: number[]) {
|
||||
return average(items.map((item) => Number(answers[item - 1] || 1)));
|
||||
}
|
||||
|
||||
export function calculateMaximizerResults(answers: string[]) {
|
||||
const itemScores = answers.slice(0, 13).map((answer) => Number(answer || 1));
|
||||
const averageScore = average(itemScores);
|
||||
|
||||
let level: 'satisficer' | 'balanced' | 'maximizer' = 'balanced';
|
||||
if (averageScore < 3.25) {
|
||||
level = 'satisficer';
|
||||
} else if (averageScore >= 4.75) {
|
||||
level = 'maximizer';
|
||||
}
|
||||
|
||||
return {
|
||||
itemScores,
|
||||
average: averageScore,
|
||||
highStandards: scoreItems(answers, highStandardItems),
|
||||
search: scoreItems(answers, searchItems),
|
||||
difficulty: scoreItems(answers, difficultyItems),
|
||||
level,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import { QuestionType } from "@/types";
|
||||
|
||||
/**
|
||||
* Narcissistic Personality Inventory calculator parameters interface
|
||||
*/
|
||||
interface NPDCalculatorProps {
|
||||
/** Answer data, key is question ID, value is selected score */
|
||||
answers: { [key: number]: string };
|
||||
/** Questions list */
|
||||
questions: QuestionType[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate Narcissistic Personality Inventory (NPI-16) results
|
||||
*
|
||||
* @param answers - User answer data, containing question ID and selected score
|
||||
* @returns Calculation results, including total score, factor scores, severity level and other information
|
||||
*/
|
||||
export const calculateNPDResults = ({ answers }: NPDCalculatorProps): any => {
|
||||
// NPI-16 calculation logic
|
||||
let totalScore = 0;
|
||||
|
||||
// Subscale score initialization based on NPI factors
|
||||
let leadershipAuthorityScore = 0; // Leadership/Authority subscale
|
||||
let grandioseExhibitionismScore = 0; // Grandiose Exhibitionism subscale
|
||||
let entitlementScore = 0; // Entitlement subscale
|
||||
|
||||
// Calculate total score and subscale scores
|
||||
Object.entries(answers).forEach(([questionId, score]) => {
|
||||
const questionNum = parseInt(questionId);
|
||||
const scoreValue = parseInt(score);
|
||||
|
||||
totalScore += scoreValue;
|
||||
|
||||
// Assign to subscales based on NPI-16 factor structure
|
||||
// Leadership/Authority: questions 1, 8, 10, 11, 12
|
||||
if ([1, 8, 10, 11, 12].includes(questionNum)) {
|
||||
leadershipAuthorityScore += scoreValue;
|
||||
}
|
||||
// Grandiose Exhibitionism: questions 2, 3, 7, 15
|
||||
else if ([2, 3, 7, 15].includes(questionNum)) {
|
||||
grandioseExhibitionismScore += scoreValue;
|
||||
}
|
||||
// Entitlement: questions 4, 5, 6, 9, 13, 14, 16
|
||||
else if ([4, 5, 6, 9, 13, 14, 16].includes(questionNum)) {
|
||||
entitlementScore += scoreValue;
|
||||
}
|
||||
});
|
||||
|
||||
// Determine interpretation level based on total score
|
||||
// Based on research, average scores typically range from 2-8 in general population
|
||||
let interpretation = "low";
|
||||
if (totalScore >= 4 && totalScore <= 7) {
|
||||
interpretation = "average";
|
||||
} else if (totalScore >= 8 && totalScore <= 11) {
|
||||
interpretation = "above_average";
|
||||
} else if (totalScore >= 12) {
|
||||
interpretation = "high";
|
||||
}
|
||||
|
||||
// Calculate percentile approximation
|
||||
let percentile = 0;
|
||||
if (totalScore <= 2) percentile = 25;
|
||||
else if (totalScore <= 5) percentile = 50;
|
||||
else if (totalScore <= 8) percentile = 75;
|
||||
else if (totalScore <= 11) percentile = 90;
|
||||
else percentile = 95;
|
||||
|
||||
// Calculate factor scores
|
||||
const factorScores: { [key: string]: number } = {
|
||||
"leadership": leadershipAuthorityScore,
|
||||
"exhibitionism": grandioseExhibitionismScore,
|
||||
"entitlement": entitlementScore
|
||||
};
|
||||
|
||||
// Identify dominant traits
|
||||
const dominantTrait = Object.entries(factorScores).reduce((a, b) =>
|
||||
factorScores[a[0]] > factorScores[b[0]] ? a : b
|
||||
)[0];
|
||||
|
||||
// Return complete calculation results
|
||||
return {
|
||||
totalScore,
|
||||
factorScores,
|
||||
positiveItemCount: totalScore, // For NPI, each item is either 0 or 1
|
||||
positiveItemAverage: totalScore / Object.keys(answers).length,
|
||||
isSevere: interpretation === "high",
|
||||
interpretation,
|
||||
percentile,
|
||||
dominantTrait,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
const reverseItems = new Set([3, 4, 5, 7, 8, 9, 12, 16, 17]);
|
||||
|
||||
export function calculateNeedForCognitionResults(answers: string[]) {
|
||||
const itemScores = answers.slice(0, 18).map((answer, index) => {
|
||||
const raw = Number(answer || 1);
|
||||
const item = index + 1;
|
||||
return reverseItems.has(item) ? 6 - raw : raw;
|
||||
});
|
||||
|
||||
const total = itemScores.reduce((sum, score) => sum + score, 0);
|
||||
const average = total / itemScores.length;
|
||||
|
||||
let level: 'low' | 'moderate' | 'high' = 'moderate';
|
||||
if (average < 3) {
|
||||
level = 'low';
|
||||
} else if (average >= 4) {
|
||||
level = 'high';
|
||||
}
|
||||
|
||||
const thinkingItems = [1, 2, 6, 10, 11, 18].map(
|
||||
(item) => itemScores[item - 1],
|
||||
);
|
||||
const challengeItems = [3, 4, 5, 7, 8, 9, 12, 13, 14, 15, 16, 17].map(
|
||||
(item) => itemScores[item - 1],
|
||||
);
|
||||
|
||||
return {
|
||||
itemScores,
|
||||
total,
|
||||
average,
|
||||
thinkingAverage:
|
||||
thinkingItems.reduce((sum, score) => sum + score, 0) /
|
||||
thinkingItems.length,
|
||||
challengeAverage:
|
||||
challengeItems.reduce((sum, score) => sum + score, 0) /
|
||||
challengeItems.length,
|
||||
level,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
type EnneagramType = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
|
||||
|
||||
const directKeys: Record<number, EnneagramType> = {
|
||||
1: 1, 10: 1, 19: 1, 28: 1,
|
||||
2: 2, 11: 2, 20: 2, 29: 2,
|
||||
3: 3, 12: 3, 21: 3, 30: 3,
|
||||
4: 4, 13: 4, 22: 4, 31: 4,
|
||||
5: 5, 14: 5, 23: 5, 32: 5,
|
||||
6: 6, 15: 6, 24: 6, 33: 6,
|
||||
7: 7, 16: 7, 25: 7, 34: 7,
|
||||
8: 8, 17: 8, 26: 8, 35: 8,
|
||||
9: 9, 18: 9, 27: 9, 36: 9,
|
||||
};
|
||||
|
||||
const bipolarKeys: Record<number, { left?: EnneagramType[]; right?: EnneagramType[] }> = {
|
||||
37: { left: [8], right: [6] },
|
||||
38: { left: [1], right: [4, 7] },
|
||||
39: { left: [9], right: [3] },
|
||||
40: { left: [7], right: [5] },
|
||||
41: { left: [1], right: [7] },
|
||||
42: { left: [2], right: [5] },
|
||||
43: { left: [7, 8], right: [9] },
|
||||
44: { left: [5], right: [2, 4] },
|
||||
45: { left: [8], right: [2, 9] },
|
||||
46: { right: [1] },
|
||||
47: { right: [2] },
|
||||
48: { left: [3] },
|
||||
49: { right: [4] },
|
||||
50: { right: [5] },
|
||||
51: { right: [6] },
|
||||
52: { left: [7] },
|
||||
53: { left: [8] },
|
||||
54: { left: [9] },
|
||||
};
|
||||
|
||||
export const enneagramTypeInfo: Record<EnneagramType, { name: string; shortName: string; description: string }> = {
|
||||
1: { name: '1号 改革者', shortName: '改革者', description: '追求正确、负责、自律和改进。' },
|
||||
2: { name: '2号 助人者', shortName: '助人者', description: '重视关系、照顾他人和被需要。' },
|
||||
3: { name: '3号 成就者', shortName: '成就者', description: '重视目标、效率、表现和认可。' },
|
||||
4: { name: '4号 自我型', shortName: '自我型', description: '重视真实、独特、情绪深度和个人意义。' },
|
||||
5: { name: '5号 探索者', shortName: '探索者', description: '重视知识、理解、独立和能力感。' },
|
||||
6: { name: '6号 忠诚者', shortName: '忠诚者', description: '重视安全、支持、可靠性和风险预判。' },
|
||||
7: { name: '7号 享乐者', shortName: '享乐者', description: '重视自由、可能性、新体验和快乐。' },
|
||||
8: { name: '8号 挑战者', shortName: '挑战者', description: '重视力量、直接、掌控和保护边界。' },
|
||||
9: { name: '9号 调停者', shortName: '调停者', description: '重视和谐、稳定、舒适和避免冲突。' },
|
||||
};
|
||||
|
||||
function addScore(
|
||||
scores: Record<EnneagramType, { total: number; count: number }>,
|
||||
type: EnneagramType,
|
||||
value: number,
|
||||
) {
|
||||
scores[type].total += value;
|
||||
scores[type].count += 1;
|
||||
}
|
||||
|
||||
export function calculateOEPSResults(answers: string[]) {
|
||||
const scores = Object.fromEntries(
|
||||
([1, 2, 3, 4, 5, 6, 7, 8, 9] as EnneagramType[]).map((type) => [
|
||||
type,
|
||||
{ total: 0, count: 0 },
|
||||
]),
|
||||
) as Record<EnneagramType, { total: number; count: number }>;
|
||||
|
||||
answers.forEach((answer, index) => {
|
||||
const questionId = index + 1;
|
||||
const value = Number(answer || 0);
|
||||
if (!value) return;
|
||||
|
||||
const directType = directKeys[questionId];
|
||||
if (directType) {
|
||||
addScore(scores, directType, value);
|
||||
return;
|
||||
}
|
||||
|
||||
const key = bipolarKeys[questionId];
|
||||
if (!key) return;
|
||||
|
||||
key.left?.forEach((type) => addScore(scores, type, 6 - value));
|
||||
key.right?.forEach((type) => addScore(scores, type, value));
|
||||
});
|
||||
|
||||
const ranked = (Object.entries(scores) as [string, { total: number; count: number }][])
|
||||
.map(([type, score]) => ({
|
||||
type: Number(type) as EnneagramType,
|
||||
total: score.total,
|
||||
count: score.count,
|
||||
average: score.count ? score.total / score.count : 0,
|
||||
...enneagramTypeInfo[Number(type) as EnneagramType],
|
||||
}))
|
||||
.sort((a, b) => b.average - a.average);
|
||||
|
||||
return {
|
||||
ranked,
|
||||
top: ranked[0],
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import { QuestionType } from "@/types";
|
||||
|
||||
interface PHQ9CalculatorProps {
|
||||
answers: { [key: number]: string };
|
||||
questions: QuestionType[];
|
||||
}
|
||||
|
||||
export const calculatePHQ9Results = ({ answers }: PHQ9CalculatorProps): any => {
|
||||
// PHQ-9 calculation logic
|
||||
let totalScore = 0;
|
||||
let suicidalIdeation = false;
|
||||
|
||||
// Calculate total score (simple sum)
|
||||
Object.entries(answers).forEach(([questionId, score]) => {
|
||||
const scoreValue = parseInt(score);
|
||||
totalScore += scoreValue;
|
||||
|
||||
// Check question 9 (suicidal ideation)
|
||||
if (parseInt(questionId) === 9 && scoreValue >= 1) {
|
||||
suicidalIdeation = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Determine depression severity level
|
||||
let severity = "minimal";
|
||||
if (totalScore >= 20) {
|
||||
severity = "severe";
|
||||
} else if (totalScore >= 15) {
|
||||
severity = "moderately_severe";
|
||||
} else if (totalScore >= 10) {
|
||||
severity = "moderate";
|
||||
} else if (totalScore >= 5) {
|
||||
severity = "mild";
|
||||
}
|
||||
|
||||
// Analyze item scores
|
||||
const itemAnalysis = Object.entries(answers).map(([questionId, score]) => ({
|
||||
questionId: parseInt(questionId),
|
||||
score: parseInt(score),
|
||||
isHigh: parseInt(score) >= 2 // Scores of 2 or above are considered high score items
|
||||
}));
|
||||
|
||||
const highScoreItems = itemAnalysis.filter(item => item.isHigh);
|
||||
|
||||
// Determine possibility of major depressive episode (at least 5 symptoms, including at least one of the first two)
|
||||
const coreSymptoms = itemAnalysis.slice(0, 2).filter(item => item.score >= 2);
|
||||
const otherSymptoms = itemAnalysis.slice(2).filter(item => item.score >= 2);
|
||||
const majorDepressionCriteria = coreSymptoms.length >= 1 && (coreSymptoms.length + otherSymptoms.length) >= 5;
|
||||
|
||||
return {
|
||||
totalScore,
|
||||
severity,
|
||||
suicidalIdeation,
|
||||
itemAnalysis,
|
||||
highScoreItemCount: highScoreItems.length,
|
||||
majorDepressionCriteria,
|
||||
factorScores: {}, // PHQ-9 is a single-factor scale
|
||||
positiveItemCount: highScoreItems.length,
|
||||
positiveItemAverage: highScoreItems.length > 0
|
||||
? highScoreItems.reduce((sum, item) => sum + item.score, 0) / highScoreItems.length
|
||||
: 0,
|
||||
isSevere: severity === "severe" || severity === "moderately_severe" || suicidalIdeation
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,86 @@
|
||||
import { QuestionType } from "@/types";
|
||||
|
||||
interface PSS10CalculatorProps {
|
||||
answers: { [key: number]: string };
|
||||
questions: QuestionType[];
|
||||
}
|
||||
|
||||
export const calculatePSS10Results = ({ answers }: PSS10CalculatorProps): any => {
|
||||
// PSS-10 calculation logic
|
||||
let totalScore = 0;
|
||||
|
||||
// Reverse scoring items (4, 5, 7, 8)
|
||||
const reverseItems = [4, 5, 7, 8];
|
||||
|
||||
// Calculate total score
|
||||
Object.entries(answers).forEach(([questionId, score]) => {
|
||||
const questionNum = parseInt(questionId);
|
||||
const scoreValue = parseInt(score);
|
||||
|
||||
if (reverseItems.includes(questionNum)) {
|
||||
// Reverse scoring: 0->4, 1->3, 2->2, 3->1, 4->0
|
||||
totalScore += (4 - scoreValue);
|
||||
} else {
|
||||
totalScore += scoreValue;
|
||||
}
|
||||
});
|
||||
|
||||
// Determine stress level (based on reference values from research literature)
|
||||
let severity = "low";
|
||||
if (totalScore >= 27) {
|
||||
severity = "high";
|
||||
} else if (totalScore >= 14) {
|
||||
severity = "moderate";
|
||||
}
|
||||
|
||||
// Analyze item scores
|
||||
const itemAnalysis = Object.entries(answers).map(([questionId, score]) => {
|
||||
const questionNum = parseInt(questionId);
|
||||
const scoreValue = parseInt(score);
|
||||
const isReverse = reverseItems.includes(questionNum);
|
||||
const actualScore = isReverse ? (4 - scoreValue) : scoreValue;
|
||||
|
||||
return {
|
||||
questionId: questionNum,
|
||||
originalScore: scoreValue,
|
||||
actualScore: actualScore,
|
||||
isReverse: isReverse,
|
||||
isHigh: actualScore >= 3 // Scores of 3 or above are considered high score items
|
||||
};
|
||||
});
|
||||
|
||||
const highScoreItems = itemAnalysis.filter(item => item.isHigh);
|
||||
|
||||
// Calculate subscale scores
|
||||
const stressPerceptionItems = [1, 2, 3, 6, 9, 10]; // Stress perception items
|
||||
const copingAbilityItems = [4, 5, 7, 8]; // Coping ability items
|
||||
|
||||
let stressPerceptionScore = 0;
|
||||
let copingAbilityScore = 0;
|
||||
|
||||
itemAnalysis.forEach(item => {
|
||||
if (stressPerceptionItems.includes(item.questionId)) {
|
||||
stressPerceptionScore += item.actualScore;
|
||||
} else if (copingAbilityItems.includes(item.questionId)) {
|
||||
copingAbilityScore += item.actualScore;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
totalScore,
|
||||
severity,
|
||||
itemAnalysis,
|
||||
highScoreItemCount: highScoreItems.length,
|
||||
stressPerceptionScore, // Stress perception score (0-24 points)
|
||||
copingAbilityScore, // Coping ability score (0-16 points)
|
||||
factorScores: {
|
||||
"stress_perception": stressPerceptionScore,
|
||||
"coping_ability": copingAbilityScore
|
||||
},
|
||||
positiveItemCount: highScoreItems.length,
|
||||
positiveItemAverage: highScoreItems.length > 0
|
||||
? highScoreItems.reduce((sum, item) => sum + item.actualScore, 0) / highScoreItems.length
|
||||
: 0,
|
||||
isSevere: severity === "high"
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
export const riasecTypes = {
|
||||
R: { name: '现实型', items: [1, 2, 3, 4, 5, 6, 7, 8] },
|
||||
I: { name: '研究型', items: [9, 10, 11, 12, 13, 14, 15, 16] },
|
||||
A: { name: '艺术型', items: [17, 18, 19, 20, 21, 22, 23, 24] },
|
||||
S: { name: '社会型', items: [25, 26, 27, 28, 29, 30, 31, 32] },
|
||||
E: { name: '企业型', items: [33, 34, 35, 36, 37, 38, 39, 40] },
|
||||
C: { name: '常规型', items: [41, 42, 43, 44, 45, 46, 47, 48] },
|
||||
} as const;
|
||||
|
||||
export function calculateRIASECResults(answers: string[]) {
|
||||
const scores = Object.fromEntries(
|
||||
Object.entries(riasecTypes).map(([code, type]) => {
|
||||
const score = type.items.reduce(
|
||||
(sum, item) => sum + Number(answers[item - 1] || 0),
|
||||
0,
|
||||
);
|
||||
return [code, { score, average: score / type.items.length }];
|
||||
}),
|
||||
);
|
||||
|
||||
const ranking = Object.entries(scores).sort(
|
||||
([codeA, resultA], [codeB, resultB]) =>
|
||||
resultB.score - resultA.score || codeA.localeCompare(codeB),
|
||||
);
|
||||
|
||||
return {
|
||||
scores,
|
||||
ranking,
|
||||
hollandCode: ranking.slice(0, 3).map(([code]) => code).join(''),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import { QuestionType } from "@/types";
|
||||
|
||||
interface SCL90CalculatorProps {
|
||||
answers: { [key: number]: string };
|
||||
questions: QuestionType[];
|
||||
}
|
||||
|
||||
export const calculateSCL90Results = ({ answers }: SCL90CalculatorProps): any => {
|
||||
// SCL-90 calculation logic
|
||||
const totalScore = Object.values(answers).reduce((sum, score) => sum + parseInt(score), 0);
|
||||
const positiveItemCount = Object.values(answers).filter(score => parseInt(score) >= 2).length;
|
||||
const negativeItemCount = Object.values(answers).filter(score => parseInt(score) === 1).length;
|
||||
const positiveItemAverage = positiveItemCount > 0
|
||||
? (totalScore - negativeItemCount) / positiveItemCount
|
||||
: 0;
|
||||
|
||||
// SCL-90 factor grouping (based on standard factor structure)
|
||||
const factorMapping: { [key: string]: number[] } = {
|
||||
"somatization": [1, 4, 12, 27, 40, 42, 48, 49, 52, 53, 56, 58], // Somatization
|
||||
"obsessive": [3, 9, 10, 28, 38, 45, 46, 51, 55, 65], // Obsessive-compulsive
|
||||
"interpersonal": [6, 21, 34, 36, 37, 41, 61, 69, 73], // Interpersonal sensitivity
|
||||
"depression": [5, 14, 15, 20, 22, 26, 29, 30, 31, 32, 54, 71, 79], // Depression
|
||||
"anxiety": [2, 17, 23, 33, 39, 57, 72, 78, 80, 86], // Anxiety
|
||||
"hostility": [11, 24, 63, 67, 74, 81], // Hostility
|
||||
"phobic": [13, 25, 47, 50, 70, 75, 82], // Phobic anxiety
|
||||
"paranoid": [8, 18, 43, 68, 76, 83], // Paranoid ideation
|
||||
"psychotic": [7, 16, 35, 62, 77, 84, 85, 87, 88, 90], // Psychoticism
|
||||
"other": [19, 44, 59, 60, 64, 66, 89] // Other
|
||||
};
|
||||
|
||||
// Calculate factor scores
|
||||
const factorScores: { [key: string]: number } = {};
|
||||
|
||||
Object.entries(factorMapping).forEach(([factor, questionIndexes]) => {
|
||||
let factorSum = 0;
|
||||
let validQuestionCount = 0;
|
||||
|
||||
questionIndexes.forEach((index: number) => {
|
||||
if (answers[index]) {
|
||||
factorSum += parseInt(answers[index]);
|
||||
validQuestionCount++;
|
||||
}
|
||||
});
|
||||
|
||||
factorScores[factor] = validQuestionCount > 0
|
||||
? factorSum / validQuestionCount
|
||||
: 0;
|
||||
});
|
||||
|
||||
const isSevere = totalScore > 160 || positiveItemCount > 43;
|
||||
|
||||
// Determine overall severity level
|
||||
let severity = "normal";
|
||||
if (totalScore >= 160 && positiveItemCount >= 43) {
|
||||
severity = "severe";
|
||||
} else if (totalScore >= 120 || positiveItemCount >= 30) {
|
||||
severity = "moderate";
|
||||
} else if (totalScore >= 90 || positiveItemCount >= 20) {
|
||||
severity = "mild";
|
||||
}
|
||||
|
||||
return {
|
||||
totalScore,
|
||||
factorScores,
|
||||
positiveItemCount,
|
||||
positiveItemAverage,
|
||||
isSevere,
|
||||
severity
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,47 @@
|
||||
import { QuestionType } from "@/types";
|
||||
|
||||
interface SDSCalculatorProps {
|
||||
answers: { [key: number]: string };
|
||||
questions: QuestionType[];
|
||||
}
|
||||
|
||||
export const calculateSDSResults = ({ answers }: SDSCalculatorProps): any => {
|
||||
// SDS Depression Self-rating Scale calculation logic
|
||||
const reverseItems = [2, 5, 6, 11, 12, 14, 16, 17, 18, 20]; // Reverse scoring items
|
||||
let totalScore = 0;
|
||||
|
||||
// Calculate total score
|
||||
Object.entries(answers).forEach(([questionId, score]) => {
|
||||
const questionNum = parseInt(questionId);
|
||||
const scoreValue = parseInt(score);
|
||||
|
||||
if (reverseItems.includes(questionNum)) {
|
||||
// Reverse scoring: 1->4, 2->3, 3->2, 4->1
|
||||
totalScore += (5 - scoreValue);
|
||||
} else {
|
||||
totalScore += scoreValue;
|
||||
}
|
||||
});
|
||||
|
||||
// Calculate standard score
|
||||
const standardScore = Math.round(totalScore * 1.25);
|
||||
|
||||
// Determine depression level
|
||||
let severity = "normal";
|
||||
if (standardScore >= 53 && standardScore <= 62) {
|
||||
severity = "mild";
|
||||
} else if (standardScore >= 63 && standardScore <= 72) {
|
||||
severity = "moderate";
|
||||
} else if (standardScore > 72) {
|
||||
severity = "severe";
|
||||
}
|
||||
|
||||
return {
|
||||
totalScore: standardScore,
|
||||
factorScores: {},
|
||||
positiveItemCount: 0,
|
||||
positiveItemAverage: 0,
|
||||
isSevere: severity === "severe",
|
||||
severity: severity
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,112 @@
|
||||
export type SchwartzValue =
|
||||
| "selfDirection"
|
||||
| "stimulation"
|
||||
| "hedonism"
|
||||
| "achievement"
|
||||
| "power"
|
||||
| "security"
|
||||
| "conformity"
|
||||
| "tradition"
|
||||
| "benevolence"
|
||||
| "universalism";
|
||||
|
||||
export type SchwartzHigherOrder =
|
||||
| "opennessToChange"
|
||||
| "conservation"
|
||||
| "selfEnhancement"
|
||||
| "selfTranscendence";
|
||||
|
||||
const valueItems: Record<SchwartzValue, number[]> = {
|
||||
selfDirection: [1, 11, 21],
|
||||
stimulation: [2, 12, 22],
|
||||
hedonism: [3, 13, 23],
|
||||
achievement: [4, 14, 24],
|
||||
power: [5, 15, 25],
|
||||
security: [6, 16, 26],
|
||||
conformity: [7, 17, 27],
|
||||
tradition: [8, 18, 28],
|
||||
benevolence: [9, 19, 29],
|
||||
universalism: [10, 20, 30],
|
||||
};
|
||||
|
||||
export const valueNames: Record<SchwartzValue, string> = {
|
||||
selfDirection: "自主",
|
||||
stimulation: "刺激",
|
||||
hedonism: "享乐",
|
||||
achievement: "成就",
|
||||
power: "权力",
|
||||
security: "安全",
|
||||
conformity: "遵从",
|
||||
tradition: "传统",
|
||||
benevolence: "仁慈",
|
||||
universalism: "普世主义",
|
||||
};
|
||||
|
||||
export const higherOrderNames: Record<SchwartzHigherOrder, string> = {
|
||||
opennessToChange: "开放变化",
|
||||
conservation: "保守稳定",
|
||||
selfEnhancement: "自我提升",
|
||||
selfTranscendence: "自我超越",
|
||||
};
|
||||
|
||||
function average(values: number[]) {
|
||||
return values.reduce((sum, value) => sum + value, 0) / values.length;
|
||||
}
|
||||
|
||||
function scoreItems(answers: string[], items: number[]) {
|
||||
return average(items.map((item) => Number(answers[item - 1] || 1)));
|
||||
}
|
||||
|
||||
export function calculateSchwartzResults(answers: string[]) {
|
||||
const valueScores = Object.fromEntries(
|
||||
Object.entries(valueItems).map(([value, items]) => [
|
||||
value,
|
||||
scoreItems(answers, items),
|
||||
]),
|
||||
) as Record<SchwartzValue, number>;
|
||||
|
||||
const higherOrderScores: Record<SchwartzHigherOrder, number> = {
|
||||
opennessToChange: average([
|
||||
valueScores.selfDirection,
|
||||
valueScores.stimulation,
|
||||
valueScores.hedonism,
|
||||
]),
|
||||
conservation: average([
|
||||
valueScores.security,
|
||||
valueScores.conformity,
|
||||
valueScores.tradition,
|
||||
]),
|
||||
selfEnhancement: average([
|
||||
valueScores.achievement,
|
||||
valueScores.power,
|
||||
valueScores.hedonism,
|
||||
]),
|
||||
selfTranscendence: average([
|
||||
valueScores.benevolence,
|
||||
valueScores.universalism,
|
||||
]),
|
||||
};
|
||||
|
||||
const rankedValues = (Object.keys(valueScores) as SchwartzValue[])
|
||||
.map((id) => ({
|
||||
id,
|
||||
name: valueNames[id],
|
||||
score: valueScores[id],
|
||||
}))
|
||||
.sort((a, b) => b.score - a.score);
|
||||
|
||||
const rankedHigherOrders = (Object.keys(higherOrderScores) as SchwartzHigherOrder[])
|
||||
.map((id) => ({
|
||||
id,
|
||||
name: higherOrderNames[id],
|
||||
score: higherOrderScores[id],
|
||||
}))
|
||||
.sort((a, b) => b.score - a.score);
|
||||
|
||||
return {
|
||||
valueScores,
|
||||
higherOrderScores,
|
||||
rankedValues,
|
||||
rankedHigherOrders,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
const reverseItems = new Set([2, 3, 5, 6, 7, 8, 10, 11, 12]);
|
||||
|
||||
export function calculateSelfControlResults(answers: string[]) {
|
||||
const itemScores = answers.slice(0, 13).map((answer, index) => {
|
||||
const raw = Number(answer || 1);
|
||||
const item = index + 1;
|
||||
return reverseItems.has(item) ? 6 - raw : raw;
|
||||
});
|
||||
|
||||
const total = itemScores.reduce((sum, score) => sum + score, 0);
|
||||
const average = total / itemScores.length;
|
||||
|
||||
let level: 'low' | 'moderate' | 'high' = 'moderate';
|
||||
if (average < 3) {
|
||||
level = 'low';
|
||||
} else if (average >= 4) {
|
||||
level = 'high';
|
||||
}
|
||||
|
||||
const impulseItems = [1, 2, 5, 10, 11].map((item) => itemScores[item - 1]);
|
||||
const executionItems = [3, 4, 6, 7, 8, 9, 12, 13].map(
|
||||
(item) => itemScores[item - 1],
|
||||
);
|
||||
|
||||
return {
|
||||
itemScores,
|
||||
total,
|
||||
average,
|
||||
impulseAverage:
|
||||
impulseItems.reduce((sum, score) => sum + score, 0) / impulseItems.length,
|
||||
executionAverage:
|
||||
executionItems.reduce((sum, score) => sum + score, 0) /
|
||||
executionItems.length,
|
||||
level,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
const reverseItems = new Set([2, 5, 6, 8, 9]);
|
||||
|
||||
export function calculateSelfEsteemResults(answers: string[]) {
|
||||
const itemScores = answers.slice(0, 10).map((answer, index) => {
|
||||
const raw = Number(answer || 0);
|
||||
const item = index + 1;
|
||||
return reverseItems.has(item) ? 3 - raw : raw;
|
||||
});
|
||||
|
||||
const total = itemScores.reduce((sum, score) => sum + score, 0);
|
||||
|
||||
let level: 'low' | 'moderate' | 'high' = 'moderate';
|
||||
if (total <= 14) {
|
||||
level = 'low';
|
||||
} else if (total >= 25) {
|
||||
level = 'high';
|
||||
}
|
||||
|
||||
return {
|
||||
total,
|
||||
itemScores,
|
||||
level,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
export type VIAStrength =
|
||||
| "creativity" | "curiosity" | "judgment" | "loveOfLearning" | "perspective"
|
||||
| "bravery" | "perseverance" | "honesty" | "zest"
|
||||
| "love" | "kindness" | "socialIntelligence"
|
||||
| "teamwork" | "fairness" | "leadership"
|
||||
| "forgiveness" | "humility" | "prudence" | "selfRegulation"
|
||||
| "appreciation" | "gratitude" | "hope" | "humor" | "spirituality";
|
||||
|
||||
export type VIAVirtue = "wisdom" | "courage" | "humanity" | "justice" | "temperance" | "transcendence";
|
||||
|
||||
const strengthItems: Record<VIAStrength, number[]> = {
|
||||
creativity: [1, 2],
|
||||
curiosity: [3, 4],
|
||||
judgment: [5, 6],
|
||||
loveOfLearning: [7, 8],
|
||||
perspective: [9, 10],
|
||||
bravery: [11, 12],
|
||||
perseverance: [13, 14],
|
||||
honesty: [15, 16],
|
||||
zest: [17, 18],
|
||||
love: [19, 20],
|
||||
kindness: [21, 22],
|
||||
socialIntelligence: [23, 24],
|
||||
teamwork: [25, 26],
|
||||
fairness: [27, 28],
|
||||
leadership: [29, 30],
|
||||
forgiveness: [31, 32],
|
||||
humility: [33, 34],
|
||||
prudence: [35, 36],
|
||||
selfRegulation: [37, 38],
|
||||
appreciation: [39, 40],
|
||||
gratitude: [41, 42],
|
||||
hope: [43, 44],
|
||||
humor: [45, 46],
|
||||
spirituality: [47, 48],
|
||||
};
|
||||
|
||||
const virtueStrengths: Record<VIAVirtue, VIAStrength[]> = {
|
||||
wisdom: ["creativity", "curiosity", "judgment", "loveOfLearning", "perspective"],
|
||||
courage: ["bravery", "perseverance", "honesty", "zest"],
|
||||
humanity: ["love", "kindness", "socialIntelligence"],
|
||||
justice: ["teamwork", "fairness", "leadership"],
|
||||
temperance: ["forgiveness", "humility", "prudence", "selfRegulation"],
|
||||
transcendence: ["appreciation", "gratitude", "hope", "humor", "spirituality"],
|
||||
};
|
||||
|
||||
export const strengthNames: Record<VIAStrength, string> = {
|
||||
creativity: "创造力",
|
||||
curiosity: "好奇心",
|
||||
judgment: "判断力",
|
||||
loveOfLearning: "热爱学习",
|
||||
perspective: "洞察力",
|
||||
bravery: "勇敢",
|
||||
perseverance: "坚持",
|
||||
honesty: "真诚",
|
||||
zest: "热情",
|
||||
love: "爱",
|
||||
kindness: "善良",
|
||||
socialIntelligence: "社交智慧",
|
||||
teamwork: "团队精神",
|
||||
fairness: "公平",
|
||||
leadership: "领导力",
|
||||
forgiveness: "宽恕",
|
||||
humility: "谦逊",
|
||||
prudence: "审慎",
|
||||
selfRegulation: "自我调节",
|
||||
appreciation: "审美",
|
||||
gratitude: "感恩",
|
||||
hope: "希望",
|
||||
humor: "幽默",
|
||||
spirituality: "灵性",
|
||||
};
|
||||
|
||||
export const virtueNames: Record<VIAVirtue, string> = {
|
||||
wisdom: "智慧",
|
||||
courage: "勇气",
|
||||
humanity: "仁爱",
|
||||
justice: "正义",
|
||||
temperance: "节制",
|
||||
transcendence: "超越",
|
||||
};
|
||||
|
||||
function average(values: number[]) {
|
||||
return values.reduce((sum, value) => sum + value, 0) / values.length;
|
||||
}
|
||||
|
||||
export function calculateVIAResults(answers: string[]) {
|
||||
const strengthScores = Object.fromEntries(
|
||||
Object.entries(strengthItems).map(([strength, items]) => [
|
||||
strength,
|
||||
average(items.map((item) => Number(answers[item - 1] || 1))),
|
||||
]),
|
||||
) as Record<VIAStrength, number>;
|
||||
|
||||
const virtueScores = Object.fromEntries(
|
||||
Object.entries(virtueStrengths).map(([virtue, strengths]) => [
|
||||
virtue,
|
||||
average(strengths.map((strength) => strengthScores[strength])),
|
||||
]),
|
||||
) as Record<VIAVirtue, number>;
|
||||
|
||||
const rankedStrengths = (Object.keys(strengthScores) as VIAStrength[])
|
||||
.map((id) => ({ id, name: strengthNames[id], score: strengthScores[id] }))
|
||||
.sort((a, b) => b.score - a.score);
|
||||
|
||||
const rankedVirtues = (Object.keys(virtueScores) as VIAVirtue[])
|
||||
.map((id) => ({ id, name: virtueNames[id], score: virtueScores[id] }))
|
||||
.sort((a, b) => b.score - a.score);
|
||||
|
||||
return { strengthScores, virtueScores, rankedStrengths, rankedVirtues };
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
interface WHO5CalculatorProps {
|
||||
answers: { [key: number]: string };
|
||||
}
|
||||
|
||||
export const calculateWHO5Results = ({ answers }: WHO5CalculatorProps) => {
|
||||
const itemScores = Array.from({ length: 5 }, (_, index) => {
|
||||
const questionId = index + 1;
|
||||
return Number.parseInt(answers[questionId] ?? "0", 10);
|
||||
});
|
||||
|
||||
const rawScore = itemScores.reduce((sum, score) => sum + score, 0);
|
||||
const percentageScore = rawScore * 4;
|
||||
const hasVeryLowItem = itemScores.some((score) => score <= 1);
|
||||
|
||||
let level: "high" | "moderate" | "low" | "very_low" = "high";
|
||||
if (rawScore <= 7) {
|
||||
level = "very_low";
|
||||
} else if (rawScore <= 12) {
|
||||
level = "low";
|
||||
} else if (rawScore <= 17) {
|
||||
level = "moderate";
|
||||
}
|
||||
|
||||
return {
|
||||
rawScore,
|
||||
percentageScore,
|
||||
itemScores,
|
||||
hasVeryLowItem,
|
||||
level,
|
||||
needsAttention: rawScore <= 12 || hasVeryLowItem,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,77 @@
|
||||
import { QuestionType } from "@/types";
|
||||
|
||||
/**
|
||||
* Yale-Brown Obsessive Compulsive Scale calculator parameters interface
|
||||
*/
|
||||
interface YBOCSCalculatorProps {
|
||||
/** Answer data, key is question ID, value is selected score */
|
||||
answers: { [key: number]: string };
|
||||
/** Questions list */
|
||||
questions: QuestionType[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate Yale-Brown Obsessive Compulsive Scale (Y-BOCS) results
|
||||
*
|
||||
* @param answers - User answer data, containing question ID and selected score
|
||||
* @returns Calculation results, including total score, factor scores, severity level and other information
|
||||
*/
|
||||
export const calculateYBOCSResults = ({ answers }: YBOCSCalculatorProps): any => {
|
||||
// Y-BOCS Obsessive Compulsive Scale calculation logic
|
||||
let totalScore = 0;
|
||||
|
||||
// Subscale score initialization: obsessions (questions 1-5) and compulsions (questions 6-10)
|
||||
let obsessionScore = 0; // Obsessions subscale score
|
||||
let compulsionScore = 0; // Compulsions subscale score
|
||||
|
||||
// Calculate total score and subscale scores
|
||||
Object.entries(answers).forEach(([questionId, score]) => {
|
||||
const questionNum = parseInt(questionId); // Question number
|
||||
const scoreValue = parseInt(score); // Selected score value
|
||||
|
||||
totalScore += scoreValue; // Accumulate total score
|
||||
|
||||
// Subscale score calculation: distinguish between obsessions and compulsions
|
||||
if (questionNum >= 1 && questionNum <= 5) {
|
||||
// Obsessions subscale (questions 1-5)
|
||||
obsessionScore += scoreValue;
|
||||
} else if (questionNum >= 6 && questionNum <= 10) {
|
||||
// Compulsions subscale (questions 6-10)
|
||||
compulsionScore += scoreValue;
|
||||
}
|
||||
});
|
||||
|
||||
// Determine severity level of obsessive-compulsive symptoms based on total score
|
||||
// 0-7: Normal
|
||||
// 8-15: Mild obsessive-compulsive symptoms
|
||||
// 16-23: Moderate obsessive-compulsive symptoms
|
||||
// 24-31: Severe obsessive-compulsive symptoms
|
||||
// 32-40: Extremely severe obsessive-compulsive symptoms
|
||||
let severity = "normal"; // Default to normal
|
||||
if (totalScore >= 8 && totalScore <= 15) {
|
||||
severity = "mild"; // Mild obsessive-compulsive symptoms
|
||||
} else if (totalScore >= 16 && totalScore <= 23) {
|
||||
severity = "moderate"; // Moderate obsessive-compulsive symptoms
|
||||
} else if (totalScore >= 24 && totalScore <= 31) {
|
||||
severity = "severe"; // Severe obsessive-compulsive symptoms
|
||||
} else if (totalScore >= 32) {
|
||||
severity = "extreme"; // Extremely severe obsessive-compulsive symptoms
|
||||
}
|
||||
|
||||
// Calculate factor scores (obsessions subscale and compulsions subscale)
|
||||
// These factor scores can be used to analyze symptom characteristics and intervention directions
|
||||
const factorScores: { [key: string]: number } = {
|
||||
"obsession": obsessionScore, // Obsessions score
|
||||
"compulsion": compulsionScore // Compulsions score
|
||||
};
|
||||
|
||||
// Return complete calculation results
|
||||
return {
|
||||
totalScore, // Total score
|
||||
factorScores, // Factor scores (including obsessions and compulsions)
|
||||
positiveItemCount: Object.values(answers).filter(score => parseInt(score) >= 1).length, // Positive item count (number of items with score ≥1)
|
||||
positiveItemAverage: totalScore / Object.keys(answers).length, // Positive item average score
|
||||
isSevere: severity === "severe" || severity === "extreme", // Whether it's severe or extremely severe symptoms
|
||||
severity // Severity level classification
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user