fix: MC options display, question selection, timeout handling, and grading prompts

This commit is contained in:
Developer
2026-06-03 20:58:19 +08:00
parent a71bde3452
commit 6d9acd7252
12 changed files with 408 additions and 157 deletions
+77 -43
View File
@@ -189,15 +189,16 @@ private async getModel(tenantId: string): Promise<ChatOpenAI> {
this.logger.debug('[calculateScores] Scoring debug:', { promptAvg, otherDimsWithScores, otherAvg, workCapability: dimensionAverages.workCapability });
const allScores: number[] = [];
questions.forEach((q: any) => {
const score = scores[q.id || questions.indexOf(q).toString()] || 0;
allScores.push(score);
});
const finalScore = allScores.length > 0
? allScores.reduce((a, b) => a + b, 0) / allScores.length
: 0;
// Weighted final score using weightConfig
let finalScore: number;
if (promptAvg > 0 && otherAvg > 0) {
const totalWeight = (weightConfig?.prompt ?? 50) + (weightConfig?.other ?? 50);
finalScore = totalWeight > 0
? (promptAvg * (weightConfig?.prompt ?? 50) + otherAvg * (weightConfig?.other ?? 50)) / totalWeight
: (promptAvg + otherAvg) / 2;
} else {
finalScore = promptAvg || otherAvg || 0;
}
const radarData: Record<string, number> = {};
Object.keys(dimensionAverages).forEach(dim => {
@@ -430,33 +431,54 @@ private async getModel(tenantId: string): Promise<ChatOpenAI> {
// Use kbId if provided, otherwise fall back to template's group ID
const activeKbId = kbId || template?.knowledgeGroupId;
this.logger.log(`[startSession] activeKbId resolved to: ${activeKbId}`);
if (!activeKbId) {
// If no knowledge source, check if template has a question bank first
let hasBankQuestions = false;
if (!activeKbId && templateId && template) {
try {
const targetCount = template.questionCount || 5;
const linkedBanks = await this.questionBankRepository.find({
where: { templateId },
});
if (linkedBanks.length > 0) {
const bankIds = linkedBanks.map(b => b.id);
const count = await this.questionBankItemRepository.count({
where: { bankId: In(bankIds), status: QuestionBankItemStatus.PUBLISHED },
});
if (count >= targetCount) {
hasBankQuestions = true;
this.logger.log(`[startSession] Template has ${count} published questions, skipping KB check`);
}
}
} catch (e) {
this.logger.warn(`[startSession] Bank pre-check failed: ${e.message}`);
}
}
if (!activeKbId && !hasBankQuestions) {
this.logger.error(`[startSession] No knowledge source resolved`);
throw new BadRequestException('Knowledge source (ID or Template) must be provided.');
}
// Try to determine if it's a KB or Group and check permissions
// Determine if it's a KB or Group (only when activeKbId exists)
let isKb = false;
try {
await this.kbService.findOne(activeKbId, userId, tenantId);
isKb = true;
} catch (kbError) {
if (kbError instanceof NotFoundException) {
// Try finding it as a Group
try {
await this.groupService.findOne(activeKbId, userId, tenantId);
} catch (groupError) {
this.logger.error(
`[startSession] Knowledge source ${activeKbId} not found as KB or Group`,
);
throw new NotFoundException(
this.i18nService.getMessage('knowledgeSourceNotFound') ||
'Knowledge source not found',
);
if (activeKbId) {
try {
await this.kbService.findOne(activeKbId, userId, tenantId);
isKb = true;
} catch (kbError) {
if (kbError instanceof NotFoundException) {
try {
await this.groupService.findOne(activeKbId, userId, tenantId);
} catch (groupError) {
this.logger.error(`[startSession] Knowledge source ${activeKbId} not found`);
throw new NotFoundException(
this.i18nService.getMessage('knowledgeSourceNotFound') || 'Knowledge source not found',
);
}
} else {
throw kbError;
}
} else {
throw kbError; // e.g. ForbiddenException
}
}
this.logger.debug(`[startSession] isKb: ${isKb}`);
@@ -503,6 +525,7 @@ private async getModel(tenantId: string): Promise<ChatOpenAI> {
const selectedItems = await this.questionBankService.selectQuestions(
bankId,
targetCount,
template?.dimensions,
);
questionsFromBank = selectedItems.map(item => {
@@ -586,9 +609,9 @@ private async getModel(tenantId: string): Promise<ChatOpenAI> {
};
// Skip content check if questions are loaded from the question bank
const hasBankQuestions = questionsFromBank.length > 0;
const hasBankContent = questionsFromBank.length > 0;
if (!hasBankQuestions) {
if (!hasBankContent) {
const content = await this.getSessionContent(sessionData);
if (!content || content.trim().length < 10) {
@@ -787,7 +810,7 @@ const initialState: Partial<EvaluationState> = {
const scores = finalData.scores;
const questions = finalData.questions || [];
const weightConfig = session.templateJson?.weightConfig || { prompt: 50, other: 50 };
const passingScore = (session.templateJson?.passingScore ?? 90) / 10;
const passingScore = (session.templateJson?.passingScore ?? 60) / 10;
if (questions.length > 0 && Object.keys(scores).length > 0) {
const { finalScore, dimensionScores, radarData } = this.calculateScores(
@@ -882,7 +905,7 @@ const initialState: Partial<EvaluationState> = {
let finalResult: any = null;
const weightConfig = session.templateJson?.weightConfig || { prompt: 50, other: 50 };
const passingScore = (session.templateJson?.passingScore ?? 90) / 10;
const passingScore = (session.templateJson?.passingScore ?? 60) / 10;
// Resume from the last interrupt (typically after interviewer)
const stream = await this.graph.stream(null, {
@@ -935,7 +958,7 @@ const initialState: Partial<EvaluationState> = {
const scores = finalResult.scores as Record<string, number>;
const questions = finalResult.questions || [];
const weightConfig = session.templateJson?.weightConfig || { prompt: 50, other: 50 };
const passingScore = (session.templateJson?.passingScore ?? 90) / 10;
const passingScore = (session.templateJson?.passingScore ?? 60) / 10;
if (questions.length > 0 && Object.keys(scores).length > 0) {
const { finalScore, dimensionScores, radarData } = this.calculateScores(
@@ -1158,7 +1181,7 @@ const initialState: Partial<EvaluationState> = {
const scores = finalData.scores;
const questions = finalData.questions || [];
const weightConfig = session.templateJson?.weightConfig || { prompt: 50, other: 50 };
const passingScore = (session.templateJson?.passingScore ?? 90) / 10;
const passingScore = (session.templateJson?.passingScore ?? 60) / 10;
if (questions.length > 0 && Object.keys(scores).length > 0) {
const { finalScore, dimensionScores, radarData } = this.calculateScores(
@@ -1364,17 +1387,27 @@ const initialState: Partial<EvaluationState> = {
const content = await this.getSessionContent(session);
const model = await this.getModel(session.tenantId);
const existingQuestions = session.questions_json || [];
const hasQuestionsFromBank = existingQuestions.length > 0;
const isZh = (session.language || 'en') === 'zh';
const isJa = session.language === 'ja';
const initialState: Partial<EvaluationState> = {
assessmentSessionId: sessionId,
knowledgeBaseId:
session.knowledgeBaseId || session.knowledgeGroupId || '',
messages: [],
messages: hasQuestionsFromBank
? [new HumanMessage(
isZh ? '我已准备好回答问题。' : isJa ? '質問への回答準備ができています。' : 'I am ready to answer the questions.',
)]
: [],
questionCount: session.templateJson?.questionCount,
difficultyDistribution: session.templateJson?.difficultyDistribution,
style: session.templateJson?.style,
keywords: session.templateJson?.keywords,
questionAnswerKey: session.templateJson?.questionAnswerKey,
language: session.language || 'en',
questions: hasQuestionsFromBank ? existingQuestions : undefined,
};
this.logger.log(
@@ -1504,7 +1537,8 @@ const initialState: Partial<EvaluationState> = {
return existing;
}
const level = this.determineLevel(session.finalScore || 0);
const passingThreshold = (session.templateJson?.passingScore ?? 60) / 10;
const level = this.determineLevel(session.finalScore || 0, !!(session as any).passed, passingThreshold);
const qrCode = `cert://${sessionId}-${Date.now()}`;
const questionDetails = (session.questions_json || []).map((q: any, i: number) => ({
@@ -1535,11 +1569,11 @@ const initialState: Partial<EvaluationState> = {
} as any;
}
private determineLevel(score: number): string {
private determineLevel(score: number, passed: boolean, passingThreshold: number): string {
if (!passed) return 'Novice';
if (score >= 9) return 'Expert';
if (score >= 7.5) return 'Advanced';
if (score >= 6) return 'Proficient';
return 'Novice';
if (score >= 7) return 'Advanced';
return 'Proficient';
}
async getStats(
@@ -1723,7 +1757,7 @@ const initialState: Partial<EvaluationState> = {
}
session.finalScore = newScore;
const passingScore = (session.templateJson?.passingScore ?? 90) / 10;
const passingScore = (session.templateJson?.passingScore ?? 60) / 10;
(session as any).passed = newScore >= passingScore;
session.reviewedBy = reviewerId;
session.reviewedAt = new Date();