2025-03-25 07:52:33 +08:00
|
|
|
import { Injectable } from "@nestjs/common";
|
|
|
|
|
import { BaseService } from "../base/base.service";
|
|
|
|
|
import { db, ObjectType, Prisma, UserProfile } from "@nice/common";
|
|
|
|
|
import EventBus, { CrudOperation } from "@server/utils/event-bus";
|
|
|
|
|
import { DefaultArgs } from "@prisma/client/runtime/library";
|
2025-03-27 08:50:22 +08:00
|
|
|
import { z } from "zod";
|
2025-03-25 07:52:33 +08:00
|
|
|
|
|
|
|
|
interface AgeRange {
|
|
|
|
|
start: number | null;
|
|
|
|
|
end: number | null;
|
|
|
|
|
label: string;
|
2025-03-25 08:22:17 +08:00
|
|
|
}
|
|
|
|
|
interface Record {
|
2025-03-27 08:50:22 +08:00
|
|
|
[key: number]: (number | string)[];
|
2025-03-25 08:22:17 +08:00
|
|
|
}
|
2025-03-25 07:52:33 +08:00
|
|
|
interface ScoreStandard {
|
|
|
|
|
ageRanges: AgeRange[];
|
2025-03-25 08:22:17 +08:00
|
|
|
scoreTable: Record;
|
|
|
|
|
}
|
2025-03-25 07:52:33 +08:00
|
|
|
|
2025-03-27 08:50:22 +08:00
|
|
|
const GetScoreArgsSchema = z.object({
|
|
|
|
|
projectId: z.string().nonempty(),
|
|
|
|
|
gender: z.boolean(),
|
|
|
|
|
age: z.number().min(0),
|
|
|
|
|
performance: z.number().min(0).or(z.string()),
|
|
|
|
|
personType: z.string().nonempty(),
|
|
|
|
|
projectUnit: z.string().nonempty()
|
|
|
|
|
});
|
|
|
|
|
|
2025-03-25 07:52:33 +08:00
|
|
|
@Injectable()
|
|
|
|
|
export class SportStandardService extends BaseService<Prisma.SportStandardDelegate> {
|
|
|
|
|
constructor() {
|
2025-03-25 10:46:28 +08:00
|
|
|
super(db, ObjectType.SPORT_STANDARD, false);
|
2025-03-25 07:52:33 +08:00
|
|
|
}
|
|
|
|
|
async create(args: Prisma.SportStandardCreateArgs) {
|
|
|
|
|
console.log(args)
|
|
|
|
|
const result = await super.create(args)
|
2025-03-25 08:22:17 +08:00
|
|
|
this.emitDataChanged(CrudOperation.CREATED, result)
|
|
|
|
|
return result
|
2025-03-25 07:52:33 +08:00
|
|
|
}
|
|
|
|
|
|
2025-03-25 08:22:17 +08:00
|
|
|
async update(args: Prisma.SportStandardUpdateArgs) {
|
2025-03-25 07:52:33 +08:00
|
|
|
const result = await super.update(args)
|
2025-03-25 08:22:17 +08:00
|
|
|
this.emitDataChanged(CrudOperation.UPDATED, result)
|
2025-03-25 07:52:33 +08:00
|
|
|
return result
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async findMany(args: Prisma.SportStandardFindManyArgs) {
|
|
|
|
|
const result = await super.findMany(args);
|
|
|
|
|
return result;
|
|
|
|
|
}
|
2025-03-25 20:17:05 +08:00
|
|
|
async findUnique(args: Prisma.SportStandardFindUniqueArgs) {
|
2025-03-25 07:52:33 +08:00
|
|
|
const result = await super.findUnique(args)
|
|
|
|
|
return result
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private emitDataChanged(operation: CrudOperation, data: any) {
|
|
|
|
|
EventBus.emit('dataChanged', {
|
2025-03-25 08:22:17 +08:00
|
|
|
type: ObjectType.SPORT_STANDARD,
|
|
|
|
|
operation,
|
|
|
|
|
data,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async createStandard(
|
|
|
|
|
data: {
|
|
|
|
|
projectId: string;
|
|
|
|
|
gender: boolean;
|
|
|
|
|
personType: string;
|
|
|
|
|
ageRanges: AgeRange[];
|
|
|
|
|
scoreTable: Record;
|
|
|
|
|
},
|
|
|
|
|
select?: Prisma.SportStandardSelect<DefaultArgs>,
|
|
|
|
|
include?: Prisma.SportStandardInclude<DefaultArgs>
|
|
|
|
|
) {
|
2025-03-25 10:46:28 +08:00
|
|
|
console.log(data)
|
2025-03-25 08:22:17 +08:00
|
|
|
this.validateAgeRanges(data.ageRanges);
|
|
|
|
|
this.validateScoreTable(data.scoreTable, data.ageRanges.length);
|
2025-03-25 10:46:28 +08:00
|
|
|
const result = await super.create({
|
2025-03-25 08:22:17 +08:00
|
|
|
data: {
|
|
|
|
|
projectId: data.projectId,
|
|
|
|
|
gender: data.gender,
|
|
|
|
|
personType: data.personType,
|
|
|
|
|
ageRanges: JSON.stringify(data.ageRanges),
|
|
|
|
|
scoreTable: JSON.stringify(data.scoreTable)
|
|
|
|
|
},
|
|
|
|
|
select,
|
|
|
|
|
include
|
|
|
|
|
})
|
2025-03-25 10:46:28 +08:00
|
|
|
this.emitDataChanged(CrudOperation.CREATED, result)
|
|
|
|
|
return result
|
2025-03-25 08:22:17 +08:00
|
|
|
}
|
|
|
|
|
private validateAgeRanges(ranges: AgeRange[]) {
|
|
|
|
|
// 检查年龄段是否按顺序排列且无重叠
|
|
|
|
|
for (let i = 0; i < ranges.length - 1; i++) {
|
|
|
|
|
const current = ranges[i];
|
|
|
|
|
const next = ranges[i + 1];
|
|
|
|
|
|
|
|
|
|
if (current.end !== next.start) {
|
|
|
|
|
throw new Error('年龄段必须连续且不重叠');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private validateScoreTable(
|
|
|
|
|
scoreTable: Record,
|
|
|
|
|
expectedLength: number
|
|
|
|
|
) {
|
|
|
|
|
Object.values(scoreTable).forEach(standards => {
|
|
|
|
|
if (standards.length !== expectedLength) {
|
|
|
|
|
throw new Error('分数表的每行数据长度必须与年龄段数量匹配');
|
|
|
|
|
}
|
2025-03-25 07:52:33 +08:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-25 20:17:05 +08:00
|
|
|
async updateStandard(
|
|
|
|
|
data: {
|
|
|
|
|
id: string;
|
|
|
|
|
ageRanges: AgeRange[];
|
|
|
|
|
scoreTable: Record;
|
|
|
|
|
}
|
|
|
|
|
) {
|
|
|
|
|
this.validateAgeRanges(data.ageRanges);
|
|
|
|
|
this.validateScoreTable(data.scoreTable, data.ageRanges.length);
|
|
|
|
|
const result = await super.update({
|
|
|
|
|
where: {
|
|
|
|
|
id: data.id
|
|
|
|
|
},
|
|
|
|
|
data: {
|
|
|
|
|
ageRanges: JSON.stringify(data.ageRanges),
|
|
|
|
|
scoreTable: JSON.stringify(data.scoreTable)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
this.emitDataChanged(CrudOperation.UPDATED, result)
|
|
|
|
|
return result
|
|
|
|
|
}
|
2025-03-27 08:50:22 +08:00
|
|
|
|
|
|
|
|
public SportScoreCalculator(performance: number | string, age: number, scoreStandard: ScoreStandard, projectUnit: string, ): number {
|
2025-03-25 07:52:33 +08:00
|
|
|
// 1. 找到对应的年龄段索引
|
|
|
|
|
const ageRangeIndex = scoreStandard.ageRanges.findIndex(range => {
|
|
|
|
|
const isAboveStart = range.start === null || age > range.start;
|
|
|
|
|
const isBelowEnd = range.end === null || age <= range.end;
|
|
|
|
|
return isAboveStart && isBelowEnd;
|
|
|
|
|
});
|
2025-03-25 08:22:17 +08:00
|
|
|
|
2025-03-25 07:52:33 +08:00
|
|
|
if (ageRangeIndex === -1) {
|
|
|
|
|
throw new Error('未找到匹配的年龄段');
|
|
|
|
|
}
|
2025-03-25 08:22:17 +08:00
|
|
|
|
2025-03-25 07:52:33 +08:00
|
|
|
// 2. 查找对应分数
|
|
|
|
|
const scores = Object.keys(scoreStandard.scoreTable)
|
|
|
|
|
.map(Number)
|
|
|
|
|
.sort((a, b) => b - a);
|
2025-03-25 08:22:17 +08:00
|
|
|
|
2025-03-27 08:50:22 +08:00
|
|
|
const isTimeUnit = projectUnit.includes('time'); // 假设时间单位包含 '时间单位' 字符串
|
|
|
|
|
|
2025-03-25 07:52:33 +08:00
|
|
|
for (const score of scores) {
|
2025-03-27 08:50:22 +08:00
|
|
|
const standard = scoreStandard.scoreTable[score.toString()][ageRangeIndex];
|
|
|
|
|
if (isTimeUnit) {
|
|
|
|
|
// 此时的performance和standard是时间字符串
|
|
|
|
|
// 需要将时间字符串转换为秒
|
|
|
|
|
if (this.timeStringToSeconds(performance) <= this.timeStringToSeconds(standard)) {
|
|
|
|
|
return score;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (performance >= standard) {
|
|
|
|
|
return score;
|
|
|
|
|
}
|
2025-03-25 07:52:33 +08:00
|
|
|
}
|
|
|
|
|
}
|
2025-03-25 08:22:17 +08:00
|
|
|
|
2025-03-25 07:52:33 +08:00
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-27 08:50:22 +08:00
|
|
|
async getScore(data: z.infer<typeof GetScoreArgsSchema>) {
|
|
|
|
|
console.log("传入的参数",data)
|
2025-03-25 07:52:33 +08:00
|
|
|
const standard = await this.findUnique({
|
2025-03-25 08:22:17 +08:00
|
|
|
where: {
|
2025-03-27 08:50:22 +08:00
|
|
|
projectId_gender_personType: { // 使用复合唯一索引
|
|
|
|
|
projectId: data.projectId,
|
|
|
|
|
gender: data.gender,
|
|
|
|
|
personType: data.personType
|
|
|
|
|
}
|
2025-03-25 07:52:33 +08:00
|
|
|
}
|
|
|
|
|
})
|
2025-03-27 08:50:22 +08:00
|
|
|
console.log("找到的评分标准",standard)
|
2025-03-25 07:52:33 +08:00
|
|
|
if (!standard) {
|
|
|
|
|
throw new Error('未找到对应的评分标准');
|
|
|
|
|
}
|
2025-03-27 08:50:22 +08:00
|
|
|
const scoreTable:Record = JSON.parse(String(standard.scoreTable))
|
|
|
|
|
const ageRanges:AgeRange[] = JSON.parse(String(standard.ageRanges))
|
|
|
|
|
const scoreStandard:ScoreStandard = {
|
|
|
|
|
ageRanges,
|
|
|
|
|
scoreTable
|
|
|
|
|
}
|
|
|
|
|
console.log("评分标准",scoreStandard)
|
2025-03-25 07:52:33 +08:00
|
|
|
return this.SportScoreCalculator(
|
|
|
|
|
data.performance,
|
|
|
|
|
data.age,
|
2025-03-27 08:50:22 +08:00
|
|
|
scoreStandard,
|
|
|
|
|
data.projectUnit,
|
2025-03-25 07:52:33 +08:00
|
|
|
)
|
2025-03-25 08:22:17 +08:00
|
|
|
}
|
2025-03-27 08:50:22 +08:00
|
|
|
// 将 13:45 格式的字符串转换为秒数
|
|
|
|
|
private timeStringToSeconds(timeStr) {
|
|
|
|
|
const [minutes, seconds] = timeStr.split(':').map(Number);
|
|
|
|
|
return minutes * 60 + seconds;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 将秒数转换为 13:45 格式的字符串
|
|
|
|
|
private secondsToTimeString(seconds: number) {
|
|
|
|
|
const minutesPart = Math.floor(seconds / 60);
|
|
|
|
|
const secondsPart = seconds % 60;
|
|
|
|
|
return `${String(minutesPart).padStart(2, '0')}:${String(secondsPart).padStart(2, '0')}`;
|
|
|
|
|
}
|
2025-03-25 07:52:33 +08:00
|
|
|
}
|