M3: console.log -> Logger + UI redesign (QuestionBank) + S7/A9/A10/A11/U11 bug fixes + #1/#2/#3/#4 enhancements + i18n for QuestionBank pages
This commit is contained in:
@@ -561,7 +561,9 @@ private async getModel(tenantId: string): Promise<ChatOpenAI> {
|
||||
`[startSession] Session ${savedSession.id} created and saved`,
|
||||
);
|
||||
|
||||
this.cleanupOldSessions(userId);
|
||||
// cleanupOldSessions permanently destroys data - disabled to preserve history.
|
||||
// Admins can use batch-delete endpoint for manual cleanup.
|
||||
// this.cleanupOldSessions(userId);
|
||||
|
||||
return savedSession;
|
||||
}
|
||||
@@ -777,6 +779,33 @@ const initialState: Partial<EvaluationState> = {
|
||||
});
|
||||
if (!session) throw new NotFoundException('Session not found');
|
||||
|
||||
if (session.status === AssessmentStatus.IN_PROGRESS) {
|
||||
const now = new Date();
|
||||
const startTime = session.startedAt ? new Date(session.startedAt) : now;
|
||||
const questionStartTime = session.currentQuestionStartedAt ? new Date(session.currentQuestionStartedAt) : now;
|
||||
const totalElapsed = Math.floor((now.getTime() - startTime.getTime()) / 1000);
|
||||
const questionElapsed = Math.floor((now.getTime() - questionStartTime.getTime()) / 1000);
|
||||
|
||||
if (totalElapsed >= session.totalTimeLimit || questionElapsed >= session.perQuestionTimeLimit) {
|
||||
session.status = AssessmentStatus.COMPLETED;
|
||||
session.finalReport = totalElapsed >= session.totalTimeLimit
|
||||
? '评测总时间已用尽,评估已自动结束'
|
||||
: '单题答题时间已用尽,评估已自动结束';
|
||||
if (session.finalScore === null || session.finalScore === undefined) {
|
||||
session.finalScore = 0;
|
||||
}
|
||||
await this.sessionRepository.save(session);
|
||||
this.logger.log(`[submitAnswer] Session ${sessionId} auto-ended due to timeout`);
|
||||
return {
|
||||
assessmentSessionId: sessionId,
|
||||
status: 'COMPLETED',
|
||||
timeout: true,
|
||||
finalScore: session.finalScore,
|
||||
finalReport: session.finalReport,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const model = await this.getModel(session.tenantId);
|
||||
await this.ensureGraphState(sessionId, session);
|
||||
const content = await this.getSessionContent(session);
|
||||
@@ -844,18 +873,18 @@ 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;
|
||||
const passingScore = (session.templateJson?.passingScore ?? 90) / 10;
|
||||
|
||||
if (questions.length > 0 && Object.keys(scores).length > 0) {
|
||||
const { finalScore, dimensionScores, radarData } = this.calculateScores(
|
||||
questions,
|
||||
scores,
|
||||
weightConfig,
|
||||
);
|
||||
session.finalScore = finalScore;
|
||||
(session as any).dimensionScores = dimensionScores;
|
||||
(session as any).radarData = radarData;
|
||||
(session as any).passed = finalScore >= passingScore;
|
||||
if (questions.length > 0 && Object.keys(scores).length > 0) {
|
||||
const { finalScore, dimensionScores, radarData } = this.calculateScores(
|
||||
questions,
|
||||
scores,
|
||||
weightConfig,
|
||||
);
|
||||
session.finalScore = finalScore;
|
||||
(session as any).dimensionScores = dimensionScores;
|
||||
(session as any).radarData = radarData;
|
||||
(session as any).passed = finalScore >= passingScore;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -918,6 +947,35 @@ const initialState: Partial<EvaluationState> = {
|
||||
return;
|
||||
}
|
||||
|
||||
if (session.status === AssessmentStatus.IN_PROGRESS) {
|
||||
const now = new Date();
|
||||
const startTime = session.startedAt ? new Date(session.startedAt) : now;
|
||||
const questionStartTime = session.currentQuestionStartedAt ? new Date(session.currentQuestionStartedAt) : now;
|
||||
const totalElapsed = Math.floor((now.getTime() - startTime.getTime()) / 1000);
|
||||
const questionElapsed = Math.floor((now.getTime() - questionStartTime.getTime()) / 1000);
|
||||
|
||||
if (totalElapsed >= session.totalTimeLimit || questionElapsed >= session.perQuestionTimeLimit) {
|
||||
session.status = AssessmentStatus.COMPLETED;
|
||||
session.finalReport = totalElapsed >= session.totalTimeLimit
|
||||
? '评测总时间已用尽,评估已自动结束'
|
||||
: '单题答题时间已用尽,评估已自动结束';
|
||||
if (session.finalScore === null || session.finalScore === undefined) {
|
||||
session.finalScore = 0;
|
||||
}
|
||||
await this.sessionRepository.save(session);
|
||||
this.logger.log(`[submitAnswerStream] Session ${sessionId} auto-ended due to timeout`);
|
||||
observer.next({
|
||||
assessmentSessionId: sessionId,
|
||||
status: 'COMPLETED',
|
||||
timeout: true,
|
||||
finalScore: session.finalScore,
|
||||
finalReport: session.finalReport,
|
||||
});
|
||||
observer.complete();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const model = await this.getModel(session.tenantId);
|
||||
const content = await this.getSessionContent(session);
|
||||
await this.ensureGraphState(sessionId, session);
|
||||
@@ -1037,7 +1095,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;
|
||||
const passingScore = (session.templateJson?.passingScore ?? 90) / 10;
|
||||
|
||||
if (questions.length > 0 && Object.keys(scores).length > 0) {
|
||||
const { finalScore, dimensionScores, radarData } = this.calculateScores(
|
||||
@@ -1049,6 +1107,7 @@ const initialState: Partial<EvaluationState> = {
|
||||
(session as any).dimensionScores = dimensionScores;
|
||||
(session as any).radarData = radarData;
|
||||
(session as any).passed = finalScore >= passingScore;
|
||||
|
||||
this.logger.log(
|
||||
`[DimensionScoring] Session ${sessionId} Final Score: ${finalScore}, Passed: ${finalScore >= passingScore}`,
|
||||
);
|
||||
@@ -1188,55 +1247,46 @@ const initialState: Partial<EvaluationState> = {
|
||||
const historicalMessages = this.hydrateMessages(session.messages);
|
||||
const existingQuestions = session.questions_json || [];
|
||||
const hasQuestionsFromBank = existingQuestions.length > 0;
|
||||
const scoresRecord: Record<string, number> = {};
|
||||
if (session.feedbackHistory) {
|
||||
for (const fh of session.feedbackHistory) {
|
||||
if (fh.score && fh.questionId) scoresRecord[fh.questionId] = fh.score;
|
||||
}
|
||||
}
|
||||
|
||||
const recoveredState: any = {
|
||||
assessmentSessionId: sessionId,
|
||||
knowledgeBaseId:
|
||||
session.knowledgeBaseId || session.knowledgeGroupId || '',
|
||||
messages: historicalMessages,
|
||||
feedbackHistory: this.hydrateMessages(
|
||||
session.feedbackHistory || [],
|
||||
),
|
||||
questions: existingQuestions,
|
||||
currentQuestionIndex: session.currentQuestionIndex || 0,
|
||||
followUpCount: session.followUpCount || 0,
|
||||
shouldFollowUp: false,
|
||||
scores: scoresRecord,
|
||||
questionCount: session.templateJson?.questionCount || 5,
|
||||
difficultyDistribution:
|
||||
session.templateJson?.difficultyDistribution,
|
||||
style: session.templateJson?.style,
|
||||
keywords: session.templateJson?.keywords,
|
||||
language: session.language || 'zh',
|
||||
report: session.finalReport || undefined,
|
||||
};
|
||||
|
||||
if (hasQuestionsFromBank) {
|
||||
this.logger.log(
|
||||
`[ensureGraphState] Using ${existingQuestions.length} questions from question bank`,
|
||||
);
|
||||
await this.graph.updateState(
|
||||
{ configurable: { thread_id: sessionId } },
|
||||
{
|
||||
assessmentSessionId: sessionId,
|
||||
knowledgeBaseId:
|
||||
session.knowledgeBaseId || session.knowledgeGroupId || '',
|
||||
messages: historicalMessages,
|
||||
feedbackHistory: this.hydrateMessages(
|
||||
session.feedbackHistory || [],
|
||||
),
|
||||
questions: existingQuestions,
|
||||
currentQuestionIndex: session.currentQuestionIndex || 0,
|
||||
followUpCount: session.followUpCount || 0,
|
||||
questionCount: session.templateJson?.questionCount || 5,
|
||||
difficultyDistribution:
|
||||
session.templateJson?.difficultyDistribution,
|
||||
style: session.templateJson?.style,
|
||||
keywords: session.templateJson?.keywords,
|
||||
},
|
||||
'grader',
|
||||
);
|
||||
} else {
|
||||
await this.graph.updateState(
|
||||
{ configurable: { thread_id: sessionId } },
|
||||
{
|
||||
assessmentSessionId: sessionId,
|
||||
knowledgeBaseId:
|
||||
session.knowledgeBaseId || session.knowledgeGroupId || '',
|
||||
messages: historicalMessages,
|
||||
feedbackHistory: this.hydrateMessages(
|
||||
session.feedbackHistory || [],
|
||||
),
|
||||
questions: session.questions_json || [],
|
||||
currentQuestionIndex: session.currentQuestionIndex || 0,
|
||||
followUpCount: session.followUpCount || 0,
|
||||
questionCount: session.templateJson?.questionCount || 5,
|
||||
difficultyDistribution:
|
||||
session.templateJson?.difficultyDistribution,
|
||||
style: session.templateJson?.style,
|
||||
keywords: session.templateJson?.keywords,
|
||||
},
|
||||
'grader',
|
||||
);
|
||||
}
|
||||
|
||||
await this.graph.updateState(
|
||||
{ configurable: { thread_id: sessionId } },
|
||||
recoveredState,
|
||||
'interviewer',
|
||||
);
|
||||
} else {
|
||||
this.logger.log(`Initializing new state for session ${sessionId}`);
|
||||
const content = await this.getSessionContent(session);
|
||||
@@ -1350,7 +1400,7 @@ const initialState: Partial<EvaluationState> = {
|
||||
}
|
||||
|
||||
if (session.status !== AssessmentStatus.COMPLETED) {
|
||||
throw new Error('Session not completed');
|
||||
throw new BadRequestException('Session not completed yet');
|
||||
}
|
||||
|
||||
const existing = await this.certificateRepository.findOne({
|
||||
@@ -1474,19 +1524,15 @@ const initialState: Partial<EvaluationState> = {
|
||||
|
||||
const sessions = await qb.take(100).getMany();
|
||||
|
||||
const dimensionScores: Record<string, number[]> = {
|
||||
PROMPT: [],
|
||||
LLM: [],
|
||||
IDE: [],
|
||||
DEV_PATTERN: [],
|
||||
WORK_CAPABILITY: [],
|
||||
};
|
||||
const dimensionScores: Record<string, number[]> = {};
|
||||
|
||||
for (const session of sessions) {
|
||||
const messages = session.messages || [];
|
||||
for (const msg of messages) {
|
||||
if (msg.dimension && msg.score !== undefined) {
|
||||
dimensionScores[msg.dimension]?.push(msg.score);
|
||||
const scores = (session as any).dimensionScores || {};
|
||||
for (const [dim, score] of Object.entries(scores)) {
|
||||
if (dimensionScores[dim]) {
|
||||
dimensionScores[dim].push(score as number);
|
||||
} else {
|
||||
dimensionScores[dim] = [score as number];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1570,7 +1616,7 @@ const initialState: Partial<EvaluationState> = {
|
||||
}
|
||||
|
||||
session.finalScore = newScore;
|
||||
const passingScore = session.templateJson?.passingScore || 90;
|
||||
const passingScore = (session.templateJson?.passingScore ?? 90) / 10;
|
||||
(session as any).passed = newScore >= passingScore;
|
||||
session.reviewedBy = reviewerId;
|
||||
session.reviewedAt = new Date();
|
||||
|
||||
@@ -97,4 +97,16 @@ export class CreateTemplateDto {
|
||||
@Max(100)
|
||||
@IsOptional()
|
||||
passingScore?: number;
|
||||
|
||||
@IsInt()
|
||||
@Min(60)
|
||||
@Max(7200)
|
||||
@IsOptional()
|
||||
totalTimeLimit?: number;
|
||||
|
||||
@IsInt()
|
||||
@Min(30)
|
||||
@Max(1800)
|
||||
@IsOptional()
|
||||
perQuestionTimeLimit?: number;
|
||||
}
|
||||
|
||||
@@ -122,7 +122,7 @@ export class QuestionBankService {
|
||||
page?: number,
|
||||
limit?: number,
|
||||
): Promise<{ data: QuestionBank[]; total: number } | QuestionBank[]> {
|
||||
console.log('[QuestionBank findAll] userId:', userId, 'tenantId:', tenantId);
|
||||
this.logger.log('[QuestionBank findAll] userId: ' + userId + ', tenantId: ' + tenantId);
|
||||
const queryBuilder = this.bankRepository
|
||||
.createQueryBuilder('bank')
|
||||
.leftJoinAndSelect('bank.template', 'template');
|
||||
|
||||
Reference in New Issue
Block a user