training_data/apps/server/src/models/sport-standard/sportStandard.service.ts

231 lines
6.7 KiB
TypeScript
Raw Normal View History

2025-03-31 20:44: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';
import { z } from 'zod';
2025-03-25 07:52:33 +08:00
interface AgeRange {
2025-03-31 20:44:33 +08:00
start: number | null;
end: number | null;
label: string;
2025-03-25 08:22:17 +08:00
}
interface Record {
2025-03-31 20:44:33 +08:00
[key: number]: (number | string)[];
2025-03-25 08:22:17 +08:00
}
2025-03-25 07:52:33 +08:00
interface ScoreStandard {
2025-03-31 20:44:33 +08:00
ageRanges: AgeRange[];
scoreTable: Record;
2025-03-25 08:22:17 +08:00
}
2025-03-25 07:52:33 +08:00
2025-03-27 08:50:22 +08:00
const GetScoreArgsSchema = z.object({
2025-03-31 20:44:33 +08:00
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-27 08:50:22 +08:00
});
2025-03-25 07:52:33 +08:00
@Injectable()
export class SportStandardService extends BaseService<Prisma.SportStandardDelegate> {
2025-03-31 20:44:33 +08:00
constructor() {
super(db, ObjectType.SPORT_STANDARD, false);
}
async create(args: Prisma.SportStandardCreateArgs) {
console.log(args);
const result = await super.create(args);
this.emitDataChanged(CrudOperation.CREATED, result);
return result;
}
async update(args: Prisma.SportStandardUpdateArgs) {
const result = await super.update(args);
this.emitDataChanged(CrudOperation.UPDATED, result);
return result;
}
async findMany(args: Prisma.SportStandardFindManyArgs) {
const result = await super.findMany(args);
return result;
}
async findUnique(args: Prisma.SportStandardFindUniqueArgs) {
const result = await super.findUnique(args);
return result;
}
private emitDataChanged(operation: CrudOperation, data: any) {
EventBus.emit('dataChanged', {
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>,
) {
console.log(data);
this.validateAgeRanges(data.ageRanges);
this.validateScoreTable(data.scoreTable, data.ageRanges.length);
const result = await super.create({
data: {
projectId: data.projectId,
gender: data.gender,
personType: data.personType,
ageRanges: JSON.stringify(data.ageRanges),
scoreTable: JSON.stringify(data.scoreTable),
},
select,
include,
});
this.emitDataChanged(CrudOperation.CREATED, result);
return result;
}
private validateAgeRanges(ranges: AgeRange[]) {
2025-04-08 09:18:42 +08:00
// 先按起始年龄排序
const sortedRanges = [...ranges].sort(
(a, b) => (a.start || 0) - (b.start || 0),
);
2025-03-31 20:44:33 +08:00
2025-04-08 09:18:42 +08:00
for (let i = 0; i < sortedRanges.length - 1; i++) {
const current = sortedRanges[i];
const next = sortedRanges[i + 1];
// 检查重叠
if ((current.end || Infinity) >= next.start) {
throw new Error(`年龄范围 ${current.label}${next.label} 重叠`);
}
// 检查连续性(允许有间隔)
if ((current.end || Infinity) + 1 > next.start) {
throw new Error(`年龄范围 ${current.label}${next.label} 不连续`);
2025-03-31 20:44:33 +08:00
}
2025-03-25 08:22:17 +08:00
}
2025-03-31 20:44:33 +08:00
}
private validateScoreTable(scoreTable: Record, expectedLength: number) {
Object.values(scoreTable).forEach((standards) => {
if (standards.length !== expectedLength) {
throw new Error('分数表的每行数据长度必须与年龄段数量匹配');
}
});
}
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;
}
public SportScoreCalculator(
performance: number | string,
age: number,
scoreStandard: ScoreStandard,
projectUnit: string,
): number {
// 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;
});
if (ageRangeIndex === -1) {
throw new Error('未找到匹配的年龄段');
2025-03-25 07:52:33 +08:00
}
2025-03-31 20:44:33 +08:00
// 2. 查找对应分数
const scores = Object.keys(scoreStandard.scoreTable)
.map(Number)
.sort((a, b) => b - a);
const isTimeUnit = projectUnit.includes('time'); // 假设时间单位包含 '时间单位' 字符串
for (const score of scores) {
const standard =
scoreStandard.scoreTable[score.toString()][ageRangeIndex];
if (isTimeUnit) {
// 此时的performance和standard是时间字符串
// 需要将时间字符串转换为秒
if (
this.timeStringToSeconds(performance) <=
this.timeStringToSeconds(standard)
) {
return score;
2025-03-25 07:52:33 +08:00
}
2025-03-31 20:44:33 +08:00
} else {
if (performance >= standard) {
return score;
2025-03-27 08:50:22 +08:00
}
2025-03-31 20:44:33 +08:00
}
2025-03-27 08:50:22 +08:00
}
2025-03-31 20:44:33 +08:00
return 0;
}
async getScore(data: z.infer<typeof GetScoreArgsSchema>) {
console.log('传入的参数', data);
const standard = await this.findUnique({
where: {
projectId_gender_personType: {
// 使用复合唯一索引
projectId: data.projectId,
gender: data.gender,
personType: data.personType,
},
},
});
console.log('找到的评分标准', standard);
if (!standard) {
throw new Error('未找到对应的评分标准');
2025-03-27 08:50:22 +08:00
}
2025-03-31 20:44:33 +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);
return this.SportScoreCalculator(
data.performance,
data.age,
scoreStandard,
data.projectUnit,
);
}
// 将 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')}`;
}
}