77 lines
3.1 KiB
TypeScript
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
|
|
}
|