fix: fix bugs and structure

This commit is contained in:
2026-08-13 09:40:21 +03:30
parent c368948c4b
commit f6a57454f4
18 changed files with 630 additions and 590 deletions
@@ -0,0 +1,222 @@
import { Injectable } from '@nestjs/common';
import {
EntityManager,
In,
LessThanOrEqual,
MoreThanOrEqual,
Repository,
} from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { SubmissionResult } from '../../entities/submission-result.entity';
import { Submission } from '../../entities/submission.entity';
import { QuestionType } from '@/modules/questions/enums/question-type.enum';
import { AppTimeoutException } from '@/common/exceptions/app-timeout.exception';
import { SubmissionAnswer } from '../../entities/submission-answer.entity';
import { Question } from '@/modules/questions/entities/question.entity';
import { ResultRange } from '@/modules/scoring/entities/result-range.entity';
import { GuestSubmissionSession } from '../../interfaces/guest-submission-session.interface';
@Injectable()
export class CalculateResultProvider {
constructor(
/**
* Inject Question Repository
*/
@InjectRepository(Question)
private readonly questionRepository: Repository<Question>,
/**
* Inject Result Range Repository
*/
@InjectRepository(ResultRange)
private readonly resultRangeRepository: Repository<ResultRange>,
) {}
public async calculateAuthenticated(
submission: Submission,
manager: EntityManager,
) {
let submissionAnswers: SubmissionAnswer[] = [];
try {
submissionAnswers = await manager.find(SubmissionAnswer, {
where: { submission: { id: submission.id } },
relations: {
question: {
rangeRules: true,
},
choice: true,
},
});
} catch (err) {
throw new AppTimeoutException(err);
}
const result = await this.calculateFromAnswers(
submissionAnswers.map((answer) => ({
questionId: answer.question.id,
question: answer.question,
choiceId: answer.choice?.id,
choice: answer.choice,
numericValue: answer.numericValue,
})),
async (questionIds) =>
manager.find(Question, {
where: { id: In(questionIds) },
relations: {
choices: true,
rangeRules: true,
},
}),
async (score) =>
manager.find(ResultRange, {
where: {
test: { id: submission.test.id },
minScore: LessThanOrEqual(score),
maxScore: MoreThanOrEqual(score),
},
}),
);
const submissionResult = manager.create(SubmissionResult, {
submission,
score: result.score,
probability: result.probability,
resultRange: result.resultRange,
});
try {
await manager.save(submissionResult);
} catch (err) {
throw new AppTimeoutException(err);
}
return {
score: result.score,
probability: result.probability,
title: result.resultRange?.title,
description: result.resultRange?.description,
};
}
public async calculateGuest(submission: GuestSubmissionSession) {
const result = await this.calculateFromAnswers(
submission.answers.map((answer) => ({
questionId: answer.questionId,
choiceId: answer.choiceId,
numericValue: answer.numericValue,
})),
async (questionIds) =>
this.questionRepository.find({
where: { id: In(questionIds) },
relations: {
choices: true,
rangeRules: true,
},
}),
async (score) =>
this.resultRangeRepository.find({
where: {
test: { id: submission.testId },
minScore: LessThanOrEqual(score),
maxScore: MoreThanOrEqual(score),
},
}),
);
submission.result = {
score: result.score,
probability: result.probability,
title: result.resultRange?.title || '',
description: result.resultRange?.description || '',
};
return submission.result;
}
private async calculateFromAnswers(
answers: Array<{
questionId: number;
question?: Question;
choiceId?: number;
choice?: { id: number; score: number };
numericValue?: number;
}>,
loadQuestions: (questionIds: number[]) => Promise<Question[]>,
loadResultRanges: (score: number) => Promise<ResultRange[]>,
) {
let score = 0;
const questionIds = answers.map((a) => a.questionId);
const questions = await loadQuestions(questionIds);
const questionsMap = new Map<number, Question>();
questions.forEach((q) => questionsMap.set(q.id, q));
for (const answer of answers) {
const question = answer.question ?? questionsMap.get(answer.questionId);
if (!question) continue;
if (
question.type === QuestionType.MULTI_CHOICE &&
answer.choiceId !== undefined
) {
const choice =
answer.choice ??
question.choices.find((c) => c.id === answer.choiceId);
if (choice) {
score += question.weight * choice.score;
}
} else if (
question.type === QuestionType.NUMERIC &&
answer.numericValue !== undefined
) {
question.rangeRules?.forEach((rule) => {
if (
answer.numericValue! >= rule.min &&
answer.numericValue! <= rule.max
) {
score += question.weight * rule.score;
}
});
}
}
let totalScores = 0;
questions.forEach((question) => {
const answerScores: number[] = [];
if (question.type === QuestionType.MULTI_CHOICE) {
question.choices.forEach((choice) => {
answerScores.push(choice.score);
});
} else if (
question.type === QuestionType.NUMERIC &&
question.rangeRules &&
question.rangeRules.length > 0
) {
question.rangeRules.forEach((rule) => {
answerScores.push(rule.score);
});
}
const maxScore = answerScores.length > 0 ? Math.max(...answerScores) : 0;
totalScores += question.weight * maxScore;
});
const resultRanges = await loadResultRanges(score);
return {
score,
probability: totalScores > 0 ? Math.ceil((score / totalScores) * 100) : 0,
resultRange: resultRanges.length > 0 ? resultRanges[0] : undefined,
};
}
}