fix: MC options display, question selection, timeout handling, and grading prompts
This commit is contained in:
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user