feat: change minmax probability to minmax score
This commit is contained in:
@@ -4,6 +4,6 @@
|
|||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"resultRangeOverlappingConstraint": "The specified score range has some overlaps with other records.",
|
"resultRangeOverlappingConstraint": "The specified score range has some overlaps with other records.",
|
||||||
"resultRangeMinMaxConstraint": "minProbability value should be less than maxProbability."
|
"resultRangeMinMaxConstraint": "minScore value should be less than maxScore."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,6 @@
|
|||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"resultRangeOverlappingConstraint": "بازه امتیاز مشخص شده با بازه های دیگر تداخل دارد.",
|
"resultRangeOverlappingConstraint": "بازه امتیاز مشخص شده با بازه های دیگر تداخل دارد.",
|
||||||
"resultRangeMinMaxConstraint": "minProbability باید کمتر از maxProbability باشد."
|
"resultRangeMinMaxConstraint": "minScore باید کمتر از maxScore باشد."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,35 @@
|
|||||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||||
|
|
||||||
export class ChangeResultRangeFields1788950646073 implements MigrationInterface {
|
export class ChangeResultRangeFields1788950646073 implements MigrationInterface {
|
||||||
name = 'ChangeResultRangeFields1788950646073'
|
name = 'ChangeResultRangeFields1788950646073';
|
||||||
|
|
||||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
await queryRunner.query(`ALTER TABLE "result_range" DROP COLUMN "minScore"`);
|
await queryRunner.query(
|
||||||
await queryRunner.query(`ALTER TABLE "result_range" DROP COLUMN "maxScore"`);
|
`ALTER TABLE "result_range" DROP COLUMN "minScore"`,
|
||||||
await queryRunner.query(`ALTER TABLE "result_range" ADD "minProbability" integer NOT NULL`);
|
);
|
||||||
await queryRunner.query(`ALTER TABLE "result_range" ADD "maxProbability" integer NOT NULL`);
|
await queryRunner.query(
|
||||||
}
|
`ALTER TABLE "result_range" DROP COLUMN "maxScore"`,
|
||||||
|
);
|
||||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
await queryRunner.query(
|
||||||
await queryRunner.query(`ALTER TABLE "result_range" DROP COLUMN "maxProbability"`);
|
`ALTER TABLE "result_range" ADD "minProbability" integer NOT NULL`,
|
||||||
await queryRunner.query(`ALTER TABLE "result_range" DROP COLUMN "minProbability"`);
|
);
|
||||||
await queryRunner.query(`ALTER TABLE "result_range" ADD "maxScore" integer NOT NULL`);
|
await queryRunner.query(
|
||||||
await queryRunner.query(`ALTER TABLE "result_range" ADD "minScore" integer NOT NULL`);
|
`ALTER TABLE "result_range" ADD "maxProbability" integer NOT NULL`,
|
||||||
}
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "result_range" DROP COLUMN "maxProbability"`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "result_range" DROP COLUMN "minProbability"`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "result_range" ADD "maxScore" integer NOT NULL`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "result_range" ADD "minScore" integer NOT NULL`,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||||
|
|
||||||
|
export class CalculateByScore1789906979798 implements MigrationInterface {
|
||||||
|
name = 'CalculateByScore1789906979798';
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "result_range" DROP COLUMN "minProbability"`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "result_range" DROP COLUMN "maxProbability"`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "result_range" ADD "minScore" integer NOT NULL`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "result_range" ADD "maxScore" integer NOT NULL`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "result_range" DROP COLUMN "maxScore"`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "result_range" DROP COLUMN "minScore"`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "result_range" ADD "maxProbability" integer NOT NULL`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "result_range" ADD "minProbability" integer NOT NULL`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,6 @@ Content-Type: application/json
|
|||||||
"test": 4,
|
"test": 4,
|
||||||
"title": "احتمال خیلی بالا",
|
"title": "احتمال خیلی بالا",
|
||||||
"description": "احتمال ابتلا خیلی بالا است.",
|
"description": "احتمال ابتلا خیلی بالا است.",
|
||||||
"minProbability": 21,
|
"minScore": 21,
|
||||||
"maxProbability": 40
|
"maxScore": 40
|
||||||
}
|
}
|
||||||
@@ -8,11 +8,11 @@ export class CreateResultRangeDTO {
|
|||||||
|
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
minProbability!: number;
|
minScore!: number;
|
||||||
|
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
maxProbability!: number;
|
maxScore!: number;
|
||||||
|
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
|
|||||||
@@ -14,11 +14,11 @@ export class FindResultRangeDTO extends PartialType(PaginationQueryDTO) {
|
|||||||
|
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
minProbability?: number;
|
minScore?: number;
|
||||||
|
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
maxProbability?: number;
|
maxScore?: number;
|
||||||
|
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
|
|||||||
@@ -15,13 +15,13 @@ export class ResultRange {
|
|||||||
type: 'int',
|
type: 'int',
|
||||||
nullable: false,
|
nullable: false,
|
||||||
})
|
})
|
||||||
minProbability!: number;
|
minScore!: number;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
type: 'int',
|
type: 'int',
|
||||||
nullable: false,
|
nullable: false,
|
||||||
})
|
})
|
||||||
maxProbability!: number;
|
maxScore!: number;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
type: 'varchar',
|
type: 'varchar',
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
export enum ResultRangeSortField {
|
export enum ResultRangeSortField {
|
||||||
ID = 'id',
|
ID = 'id',
|
||||||
TEST = 'test',
|
TEST = 'test',
|
||||||
MIN_PROBABILITY = 'minProbability',
|
MIN_SCORE = 'minScore',
|
||||||
MAX_PROBABILITY = 'maxProbability',
|
MAX_SCORE = 'maxScore',
|
||||||
TITLE = 'title',
|
TITLE = 'title',
|
||||||
DESCRIPTION = 'description',
|
DESCRIPTION = 'description',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,12 +17,12 @@ export const filterResultRanges = (query: FindResultRangeDTO) => {
|
|||||||
where.title = ILike(`%${query.title}%`);
|
where.title = ILike(`%${query.title}%`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.maxProbability !== undefined) {
|
if (query.maxScore !== undefined) {
|
||||||
where.maxProbability = query.maxProbability;
|
where.maxScore = query.maxScore;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.minProbability !== undefined) {
|
if (query.minScore !== undefined) {
|
||||||
where.minProbability = query.minProbability;
|
where.minScore = query.minScore;
|
||||||
}
|
}
|
||||||
|
|
||||||
return where;
|
return where;
|
||||||
|
|||||||
@@ -32,16 +32,16 @@ export class ResultRangeCommandProvider {
|
|||||||
|
|
||||||
private async checkRangeOverlap(
|
private async checkRangeOverlap(
|
||||||
test: number,
|
test: number,
|
||||||
minProbability: number,
|
minScore: number,
|
||||||
maxProbability: number,
|
maxScore: number,
|
||||||
excludeSelf: boolean = false,
|
excludeSelf: boolean = false,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const queryBuilder = this.resultRangeRepository
|
const queryBuilder = this.resultRangeRepository
|
||||||
.createQueryBuilder('resultRange')
|
.createQueryBuilder('resultRange')
|
||||||
.where('resultRange.testId = :test', { test })
|
.where('resultRange.testId = :test', { test })
|
||||||
.andWhere(
|
.andWhere(
|
||||||
'(resultRange.minProbability BETWEEN :minProbability AND :maxProbability) OR (resultRange.maxProbability BETWEEN :minProbability AND :maxProbability) OR (:minProbability BETWEEN resultRange.minProbability AND resultRange.maxProbability) OR (:maxProbability BETWEEN resultRange.minProbability AND resultRange.maxProbability)',
|
'(resultRange.minScore BETWEEN :minScore AND :maxScore) OR (resultRange.maxScore BETWEEN :minScore AND :maxScore) OR (:minScore BETWEEN resultRange.minScore AND resultRange.maxScore) OR (:maxScore BETWEEN resultRange.minScore AND resultRange.maxScore)',
|
||||||
{ minProbability, maxProbability },
|
{ minScore, maxScore },
|
||||||
);
|
);
|
||||||
|
|
||||||
let overlappingRanges: Array<ResultRange>;
|
let overlappingRanges: Array<ResultRange>;
|
||||||
@@ -92,8 +92,8 @@ export class ResultRangeCommandProvider {
|
|||||||
|
|
||||||
await this.checkRangeOverlap(
|
await this.checkRangeOverlap(
|
||||||
test.id,
|
test.id,
|
||||||
createResultRangeDto.minProbability,
|
createResultRangeDto.minScore,
|
||||||
createResultRangeDto.maxProbability,
|
createResultRangeDto.maxScore,
|
||||||
);
|
);
|
||||||
|
|
||||||
const newResultRange = this.resultRangeRepository.create({
|
const newResultRange = this.resultRangeRepository.create({
|
||||||
@@ -129,23 +129,23 @@ export class ResultRangeCommandProvider {
|
|||||||
? updateResultRangeDto.test
|
? updateResultRangeDto.test
|
||||||
: resultRange.test.id;
|
: resultRange.test.id;
|
||||||
|
|
||||||
const minProbability =
|
const minScore =
|
||||||
updateResultRangeDto.minProbability !== undefined
|
updateResultRangeDto.minScore !== undefined
|
||||||
? updateResultRangeDto.minProbability
|
? updateResultRangeDto.minScore
|
||||||
: resultRange.minProbability;
|
: resultRange.minScore;
|
||||||
|
|
||||||
const maxProbability =
|
const maxScore =
|
||||||
updateResultRangeDto.maxProbability !== undefined
|
updateResultRangeDto.maxScore !== undefined
|
||||||
? updateResultRangeDto.maxProbability
|
? updateResultRangeDto.maxScore
|
||||||
: resultRange.maxProbability;
|
: resultRange.maxScore;
|
||||||
|
|
||||||
if (minProbability >= maxProbability) {
|
if (minScore >= maxScore) {
|
||||||
throw new BadRequestException(
|
throw new BadRequestException(
|
||||||
'scoring.errors.resultRangeMinMaxConstraint',
|
'scoring.errors.resultRangeMinMaxConstraint',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.checkRangeOverlap(testId, minProbability, maxProbability, true);
|
await this.checkRangeOverlap(testId, minScore, maxScore, true);
|
||||||
|
|
||||||
Object.assign(resultRange, updateResultRangeDto);
|
Object.assign(resultRange, updateResultRangeDto);
|
||||||
|
|
||||||
|
|||||||
@@ -14,15 +14,15 @@ export class ResultRangeValidator implements ValidatorConstraintInterface {
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
const obj = validationArguments?.object as any;
|
const obj = validationArguments?.object as any;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
const { minProbability, maxProbability } = obj;
|
const { minScore, maxScore } = obj;
|
||||||
return minProbability < maxProbability;
|
return minScore < maxScore;
|
||||||
}
|
}
|
||||||
defaultMessage?(): string {
|
defaultMessage?(): string {
|
||||||
const i18n = I18nContext.current();
|
const i18n = I18nContext.current();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
i18n?.t('scoring.errors.resultRangeMinMaxConstraint') ||
|
i18n?.t('scoring.errors.resultRangeMinMaxConstraint') ||
|
||||||
'minProbability value should be less than maxProbability.'
|
'minScore value should be less than maxScore.'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,12 +72,12 @@ export class CalculateResultProvider {
|
|||||||
rangeRules: true,
|
rangeRules: true,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
async (probability) =>
|
async (score) =>
|
||||||
manager.find(ResultRange, {
|
manager.find(ResultRange, {
|
||||||
where: {
|
where: {
|
||||||
test: { id: submission.test.id },
|
test: { id: submission.test.id },
|
||||||
minProbability: LessThanOrEqual(probability),
|
minScore: LessThanOrEqual(score),
|
||||||
maxProbability: MoreThanOrEqual(probability),
|
maxScore: MoreThanOrEqual(score),
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -119,12 +119,12 @@ export class CalculateResultProvider {
|
|||||||
rangeRules: true,
|
rangeRules: true,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
async (probability) =>
|
async (score) =>
|
||||||
this.resultRangeRepository.find({
|
this.resultRangeRepository.find({
|
||||||
where: {
|
where: {
|
||||||
test: { id: submission.testId },
|
test: { id: submission.testId },
|
||||||
minProbability: LessThanOrEqual(probability),
|
minScore: LessThanOrEqual(score),
|
||||||
maxProbability: MoreThanOrEqual(probability),
|
maxScore: MoreThanOrEqual(score),
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -150,7 +150,7 @@ export class CalculateResultProvider {
|
|||||||
numericValue?: number;
|
numericValue?: number;
|
||||||
}>,
|
}>,
|
||||||
loadQuestions: (questionIds: number[]) => Promise<Question[]>,
|
loadQuestions: (questionIds: number[]) => Promise<Question[]>,
|
||||||
loadResultRanges: (probability: number) => Promise<ResultRange[]>,
|
loadResultRanges: (score: number) => Promise<ResultRange[]>,
|
||||||
) {
|
) {
|
||||||
let score = 0;
|
let score = 0;
|
||||||
|
|
||||||
@@ -242,7 +242,7 @@ export class CalculateResultProvider {
|
|||||||
const probability =
|
const probability =
|
||||||
totalScores > 0 ? Math.ceil((score / totalScores) * 100) : 0;
|
totalScores > 0 ? Math.ceil((score / totalScores) * 100) : 0;
|
||||||
|
|
||||||
const resultRanges = await loadResultRanges(probability);
|
const resultRanges = await loadResultRanges(score);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
score,
|
score,
|
||||||
|
|||||||
Reference in New Issue
Block a user