gongxiangp2/lib/performance-survey-scoring.ts

77 lines
3.1 KiB
TypeScript

import type { PerformanceSurveyScoreBand, PerformanceSurveyTemplateShape } from './performance-survey-templates'
export type SurveyScoringAssignment = {
workflowStageKey?: string | null
reviewerSource?: string | null
evaluatorSnapshot?: unknown
}
/** The saved assessment workflow decides which reviewers contribute to the official result. */
export function isTemplateScoringAssignment(template: PerformanceSurveyTemplateShape, assignment?: SurveyScoringAssignment | null) {
const scoring = template.workflow?.scoring
if (!scoring) {
const visual = template.surveyType === 'visual_case' || template.code === 'visual_brand_tone'
const snapshot = assignment?.evaluatorSnapshot
const role = snapshot && typeof snapshot === 'object' && !Array.isArray(snapshot)
? String((snapshot as Record<string, unknown>).performanceSurveyWorkflowRole || '').trim() : ''
return !visual || role === 'visual_final' || role === 'self_visual_final'
}
if (scoring.mode === 'final_reviewer') {
return assignment?.workflowStageKey === scoring.stageKey && assignment?.reviewerSource === scoring.reviewerSource
}
return !scoring.stageKeys?.length || scoring.stageKeys.includes(String(assignment?.workflowStageKey || ''))
}
export function calculateAggregateScore(
responses: Array<{
id?: string
totalScore: number
createdAt: Date | string
scores?: Record<string, unknown> | null
respondentName?: string | null
}>,
_scoreBands: PerformanceSurveyScoreBand[],
_weight: number,
template?: Pick<PerformanceSurveyTemplateShape, 'code' | 'questions' | 'trimNote' | 'trimmedNote' | 'belowBand' | 'achievementLabel' | 'achievementSuffix' | 'maxTotal' | 'weightLabel' | 'aggregationStrategy'>,
) {
const directAverage = responses.length > 0
? responses.reduce((sum, response) => sum + Number(response.totalScore || 0), 0) / responses.length
: null
const dimensionAverages = Object.fromEntries(
(template?.questions || []).map((question) => {
const values = responses
.map((response) => Number(response.scores?.[question.id]))
.filter(Number.isFinite)
const average = values.length > 0
? values.reduce((sum, value) => sum + value, 0) / values.length
: null
return [question.id, average == null ? null : roundScore(average)]
}),
)
const directScore = directAverage == null ? null : roundScore(directAverage)
return {
responseCount: responses.length,
effectiveCount: responses.length,
rawAverage: directScore,
trimmedAverage: directScore,
droppedHighestLowest: false,
trimNote: responses.length > 0 ? '按全部评分直接计算平均分' : '暂无评分',
removedHigh: null,
removedLow: null,
dimensionAverages,
achievementLabel: template?.achievementLabel,
achievementSuffix: template?.achievementSuffix,
maxTotal: template?.maxTotal,
weightLabel: template?.weightLabel,
band: null,
achievementScore: directScore,
kpiScore: directScore,
weightedScore: directScore,
}
}
function roundScore(value: number) {
return Math.round(Number(value || 0) * 100) / 100
}