fix: minor issues from code review

(M1) DTO: @IsObject({ each: true }) on dimensions array
(M2) audit log: add missing tenantId in submitAnswer
(M3) console.log -> this.logger in controller + service
This commit is contained in:
Developer
2026-05-19 10:22:18 +08:00
parent 82a9e75842
commit 5b5f14674d
3 changed files with 39 additions and 34 deletions
+21 -18
View File
@@ -13,6 +13,7 @@ import {
Delete, Delete,
Put, Put,
ForbiddenException, ForbiddenException,
Logger,
} from '@nestjs/common'; } from '@nestjs/common';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import { AssessmentService } from './assessment.service'; import { AssessmentService } from './assessment.service';
@@ -26,6 +27,8 @@ import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
@Controller('assessment') @Controller('assessment')
@UseGuards(CombinedAuthGuard) @UseGuards(CombinedAuthGuard)
export class AssessmentController { export class AssessmentController {
private readonly logger = new Logger(AssessmentController.name);
constructor( constructor(
private readonly assessmentService: AssessmentService, private readonly assessmentService: AssessmentService,
private readonly exportService: ExportService, private readonly exportService: ExportService,
@@ -40,8 +43,8 @@ export class AssessmentController {
body: { knowledgeBaseId?: string; language?: string; templateId?: string }, body: { knowledgeBaseId?: string; language?: string; templateId?: string },
) { ) {
const { id: userId, tenantId } = req.user; const { id: userId, tenantId } = req.user;
console.log( this.logger.log(
`[AssessmentController] startSession: user=${userId}, tenant=${tenantId}, templateId=${body.templateId}, kbId=${body.knowledgeBaseId}`, `startSession: user=${userId}, tenant=${tenantId}, templateId=${body.templateId}, kbId=${body.knowledgeBaseId}`,
); );
const session = await this.assessmentService.startSession( const session = await this.assessmentService.startSession(
userId, userId,
@@ -61,9 +64,9 @@ export class AssessmentController {
@Param('id') sessionId: string, @Param('id') sessionId: string,
@Body() body: { answer: string; language?: string }, @Body() body: { answer: string; language?: string },
) { ) {
const { id: userId } = req.user; const { id: userId, tenantId } = req.user;
console.log( this.logger.log(
`[AssessmentController] >>> submitAnswer CALLED: user=${userId}, session=${sessionId}, answerLen=${body.answer?.length}`, `submitAnswer: user=${userId}, session=${sessionId}, answerLen=${body.answer?.length}`,
); );
const result = await this.assessmentService.submitAnswer( const result = await this.assessmentService.submitAnswer(
sessionId, sessionId,
@@ -71,7 +74,7 @@ export class AssessmentController {
body.answer, body.answer,
body.language, body.language,
); );
this.auditLog.log({ userId, action: 'session.answer', resourceType: 'assessment_session', resourceId: sessionId, details: { answerLength: body.answer?.length } }); this.auditLog.log({ userId, tenantId, action: 'session.answer', resourceType: 'assessment_session', resourceId: sessionId, details: { answerLength: body.answer?.length } });
return result; return result;
} }
@@ -79,8 +82,8 @@ export class AssessmentController {
@ApiOperation({ summary: 'Stream initial session generation' }) @ApiOperation({ summary: 'Stream initial session generation' })
startSessionStream(@Request() req: any, @Param('id') sessionId: string) { startSessionStream(@Request() req: any, @Param('id') sessionId: string) {
const { id: userId } = req.user; const { id: userId } = req.user;
console.log( this.logger.log(
`[AssessmentController] startSessionStream: user=${userId}, session=${sessionId}`, `startSessionStream: user=${userId}, session=${sessionId}`,
); );
return this.assessmentService return this.assessmentService
.startSessionStream(sessionId, userId) .startSessionStream(sessionId, userId)
@@ -98,8 +101,8 @@ export class AssessmentController {
@Query('language') language?: string, @Query('language') language?: string,
) { ) {
const { id: userId } = req.user; const { id: userId } = req.user;
console.log( this.logger.log(
`[AssessmentController] >>> submitAnswerStream CALLED: user=${userId}, session=${sessionId}, answerLen=${answer?.length}, lang=${language}`, `submitAnswerStream: user=${userId}, session=${sessionId}, answerLen=${answer?.length}, lang=${language}`,
); );
return this.assessmentService return this.assessmentService
.submitAnswerStream(sessionId, userId, answer, language) .submitAnswerStream(sessionId, userId, answer, language)
@@ -110,8 +113,8 @@ export class AssessmentController {
@ApiOperation({ summary: 'Get the current state of an assessment session' }) @ApiOperation({ summary: 'Get the current state of an assessment session' })
async getSessionState(@Request() req: any, @Param('id') sessionId: string) { async getSessionState(@Request() req: any, @Param('id') sessionId: string) {
const { id: userId } = req.user; const { id: userId } = req.user;
console.log( this.logger.log(
`[AssessmentController] getSessionState: user=${userId}, session=${sessionId}`, `getSessionState: user=${userId}, session=${sessionId}`,
); );
return this.assessmentService.getSessionState(sessionId, userId); return this.assessmentService.getSessionState(sessionId, userId);
} }
@@ -120,8 +123,8 @@ export class AssessmentController {
@ApiOperation({ summary: 'Delete an assessment session' }) @ApiOperation({ summary: 'Delete an assessment session' })
async deleteSession(@Request() req: any, @Param('id') sessionId: string) { async deleteSession(@Request() req: any, @Param('id') sessionId: string) {
const user = req.user; const user = req.user;
console.log( this.logger.log(
`[AssessmentController] deleteSession: user=${user.id}, role=${user.role}, session=${sessionId}`, `deleteSession: user=${user.id}, role=${user.role}, session=${sessionId}`,
); );
await this.assessmentService.deleteSession(sessionId, user); await this.assessmentService.deleteSession(sessionId, user);
this.auditLog.log({ userId: user.id, tenantId: user.tenantId, action: 'session.delete', resourceType: 'assessment_session', resourceId: sessionId }); this.auditLog.log({ userId: user.id, tenantId: user.tenantId, action: 'session.delete', resourceType: 'assessment_session', resourceId: sessionId });
@@ -135,8 +138,8 @@ export class AssessmentController {
@Param('id') sessionId: string, @Param('id') sessionId: string,
) { ) {
const { id: userId, tenantId } = req.user; const { id: userId, tenantId } = req.user;
console.log( this.logger.log(
`[AssessmentController] getCertificate: user=${userId}, session=${sessionId}`, `getCertificate: user=${userId}, session=${sessionId}`,
); );
return this.assessmentService.generateCertificate(sessionId, userId, tenantId); return this.assessmentService.generateCertificate(sessionId, userId, tenantId);
} }
@@ -178,8 +181,8 @@ export class AssessmentController {
@Query('knowledgeGroupId') knowledgeGroupId?: string, @Query('knowledgeGroupId') knowledgeGroupId?: string,
) { ) {
const { id: userId, tenantId, role } = req.user; const { id: userId, tenantId, role } = req.user;
console.log( this.logger.log(
`[AssessmentController] getStats: user=${userId}, role=${role}, tenant=${tenantId}`, `getStats: user=${userId}, role=${role}, tenant=${tenantId}`,
); );
return this.assessmentService.getStats( return this.assessmentService.getStats(
userId, userId,
+16 -16
View File
@@ -142,7 +142,7 @@ private async getModel(tenantId: string): Promise<ChatOpenAI> {
scores: Record<string, number>, scores: Record<string, number>,
weightConfig: { prompt: number; other: number }, weightConfig: { prompt: number; other: number },
): { finalScore: number; dimensionScores: Record<string, number>; radarData: Record<string, number> } { ): { finalScore: number; dimensionScores: Record<string, number>; radarData: Record<string, number> } {
console.log('[calculateScores] Input:', { this.logger.debug('[calculateScores] Input:', {
questionsCount: questions.length, questionsCount: questions.length,
scores, scores,
weightConfig, weightConfig,
@@ -180,7 +180,7 @@ private async getModel(tenantId: string): Promise<ChatOpenAI> {
? otherDimsWithScores.reduce((sum, dim) => sum + (dimensionAverages[dim] || 0), 0) / otherDimsWithScores.length ? otherDimsWithScores.reduce((sum, dim) => sum + (dimensionAverages[dim] || 0), 0) / otherDimsWithScores.length
: 0; : 0;
console.log('[calculateScores] Scoring debug:', { promptAvg, otherDimsWithScores, otherAvg, workCapability: dimensionAverages.workCapability }); this.logger.debug('[calculateScores] Scoring debug:', { promptAvg, otherDimsWithScores, otherAvg, workCapability: dimensionAverages.workCapability });
const finalScore = promptAvg * (weightConfig.prompt / 100) + otherAvg * (weightConfig.other / 100); const finalScore = promptAvg * (weightConfig.prompt / 100) + otherAvg * (weightConfig.other / 100);
@@ -189,7 +189,7 @@ private async getModel(tenantId: string): Promise<ChatOpenAI> {
radarData[dim] = Math.round(dimensionAverages[dim] * 10) / 10; radarData[dim] = Math.round(dimensionAverages[dim] * 10) / 10;
}); });
console.log('[calculateScores] Result:', { this.logger.debug('[calculateScores] Result:', {
finalScore: Math.round(finalScore * 10) / 10, finalScore: Math.round(finalScore * 10) / 10,
dimensionScores: dimensionAverages, dimensionScores: dimensionAverages,
promptAvg, promptAvg,
@@ -709,7 +709,7 @@ const initialState: Partial<EvaluationState> = {
const finalData = fullState.values as EvaluationState; const finalData = fullState.values as EvaluationState;
if (finalData && finalData.messages) { if (finalData && finalData.messages) {
console.log( this.logger.debug(
`[AssessmentService] startSessionStream Final Authoritative State messages:`, `[AssessmentService] startSessionStream Final Authoritative State messages:`,
finalData.messages.length, finalData.messages.length,
); );
@@ -903,13 +903,13 @@ const initialState: Partial<EvaluationState> = {
answer: string, answer: string,
language: string = 'en', language: string = 'en',
): Observable<any> { ): Observable<any> {
console.log('[submitAnswerStream] START - sessionId:', sessionId, 'answer length:', answer?.length); this.logger.debug('[submitAnswerStream] START - sessionId:', sessionId, 'answer length:', answer?.length);
let emittedNextQuestion = false; let emittedNextQuestion = false;
let hasEmittedNodes = false; let hasEmittedNodes = false;
return new Observable((observer) => { return new Observable((observer) => {
(async () => { (async () => {
try { try {
console.log('[submitAnswerStream] After Observable - sessionId:', sessionId); this.logger.debug('[submitAnswerStream] After Observable - sessionId:', sessionId);
const session = await this.sessionRepository.findOne({ const session = await this.sessionRepository.findOne({
where: { id: sessionId, userId }, where: { id: sessionId, userId },
}); });
@@ -928,7 +928,7 @@ const initialState: Partial<EvaluationState> = {
graphState && graphState &&
graphState.values && graphState.values &&
Object.keys(graphState.values).length > 0; Object.keys(graphState.values).length > 0;
console.log( this.logger.debug(
`[AssessmentService] submitAnswerStream: sessionId=${sessionId}, hasState=${hasState}, nextNodes=[${graphState.next || ''}]`, `[AssessmentService] submitAnswerStream: sessionId=${sessionId}, hasState=${hasState}, nextNodes=[${graphState.next || ''}]`,
); );
@@ -954,8 +954,8 @@ const initialState: Partial<EvaluationState> = {
let hasEmittedNodes = false; let hasEmittedNodes = false;
for await (const [mode, data] of stream) { for await (const [mode, data] of stream) {
streamCount++; streamCount++;
console.log('[submitAnswerStream] Stream event:', streamCount, mode, Object.keys(data || {})); this.logger.debug('[submitAnswerStream] Stream event:', streamCount, mode, Object.keys(data || {}));
console.log('[submitAnswerStream] Data detail:', JSON.stringify(data).substring(0, 500)); this.logger.debug('[submitAnswerStream] Data detail:', JSON.stringify(data).substring(0, 500));
if (mode === 'updates') { if (mode === 'updates') {
hasEmittedNodes = true; hasEmittedNodes = true;
const node = Object.keys(data)[0]; const node = Object.keys(data)[0];
@@ -963,17 +963,17 @@ const initialState: Partial<EvaluationState> = {
// Skip interrupt nodes - they have no useful data // Skip interrupt nodes - they have no useful data
if (node === '__interrupt__' || !updateData || Object.keys(updateData).length === 0) { if (node === '__interrupt__' || !updateData || Object.keys(updateData).length === 0) {
console.log('[submitAnswerStream] Skipping empty interrupt node'); this.logger.debug('[submitAnswerStream] Skipping empty interrupt node');
continue; continue;
} }
console.log('[submitAnswerStream] Node update:', node, { this.logger.debug('[submitAnswerStream] Node update:', node, {
hasMessages: !!updateData.messages, hasMessages: !!updateData.messages,
messageCount: updateData.messages?.length, messageCount: updateData.messages?.length,
currentIndex: updateData.currentQuestionIndex, currentIndex: updateData.currentQuestionIndex,
dataKeys: Object.keys(updateData).join(',') dataKeys: Object.keys(updateData).join(',')
}); });
console.log('[submitAnswerStream] Sending to frontend:', JSON.stringify(updateData).substring(0, 500)); this.logger.debug('[submitAnswerStream] Sending to frontend:', JSON.stringify(updateData).substring(0, 500));
if (updateData.messages) { if (updateData.messages) {
updateData.messages = this.mapMessages(updateData.messages); updateData.messages = this.mapMessages(updateData.messages);
} }
@@ -984,7 +984,7 @@ const initialState: Partial<EvaluationState> = {
} }
observer.next({ type: 'node', node, data: updateData }); observer.next({ type: 'node', node, data: updateData });
} else if (mode === 'values') { } else if (mode === 'values') {
console.log('[submitAnswerStream] Values update - keys:', Object.keys(data || {})); this.logger.debug('[submitAnswerStream] Values update - keys:', Object.keys(data || {}));
} }
} }
@@ -995,13 +995,13 @@ const initialState: Partial<EvaluationState> = {
const finalData = fullState.values as EvaluationState; const finalData = fullState.values as EvaluationState;
// Force emit the next question if stream didn't emit updates (hasEmittedNodes is false) // Force emit the next question if stream didn't emit updates (hasEmittedNodes is false)
console.log('[submitAnswerStream] Force check:', { hasEmittedNodes, hasFinalData: !!finalData, hasQuestions: !!finalData?.questions, qLen: finalData?.questions?.length, emittedNextQuestion }); this.logger.debug('[submitAnswerStream] Force check:', { hasEmittedNodes, hasFinalData: !!finalData, hasQuestions: !!finalData?.questions, qLen: finalData?.questions?.length, emittedNextQuestion });
if (!hasEmittedNodes && finalData && finalData.questions && finalData.questions.length > 0 && !emittedNextQuestion) { if (!hasEmittedNodes && finalData && finalData.questions && finalData.questions.length > 0 && !emittedNextQuestion) {
const currentIndex = finalData.currentQuestionIndex || 0; const currentIndex = finalData.currentQuestionIndex || 0;
const nextQuestion = finalData.questions[currentIndex]; const nextQuestion = finalData.questions[currentIndex];
if (nextQuestion) { if (nextQuestion) {
const questionText = nextQuestion.questionText || ''; const questionText = nextQuestion.questionText || '';
console.log('[submitAnswerStream] Forcing emit next question:', { this.logger.debug('[submitAnswerStream] Forcing emit next question:', {
currentIndex, currentIndex,
questionPreview: questionText.substring(0, 50) questionPreview: questionText.substring(0, 50)
}); });
@@ -1021,7 +1021,7 @@ const initialState: Partial<EvaluationState> = {
} }
if (finalData && finalData.messages) { if (finalData && finalData.messages) {
console.log( this.logger.debug(
`[AssessmentService] submitAnswerStream Final Authoritative State messages:`, `[AssessmentService] submitAnswerStream Final Authoritative State messages:`,
finalData.messages.length, finalData.messages.length,
); );
@@ -8,6 +8,7 @@ import {
Max, Max,
IsObject, IsObject,
IsBoolean, IsBoolean,
IsNumber,
} from 'class-validator'; } from 'class-validator';
export class CreateTemplateDto { export class CreateTemplateDto {
@@ -60,6 +61,7 @@ export class CreateTemplateDto {
linkedGroupIds?: string[]; linkedGroupIds?: string[];
@IsArray() @IsArray()
@IsObject({ each: true })
@IsOptional() @IsOptional()
dimensions?: Array<{ name: string; label: string; weight: number }>; dimensions?: Array<{ name: string; label: string; weight: number }>;