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"; interface AgeRange { start: number | null; end: number | null; label: string; } interface Record { [key: number]: (number | string)[]; } interface ScoreStandard { ageRanges: AgeRange[]; scoreTable: Record; } 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() }); @Injectable() export class SportStandardService extends BaseService { 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, include?: Prisma.SportStandardInclude ) { 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[]) { // 检查年龄段是否按顺序排列且无重叠 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('分数表的每行数据长度必须与年龄段数量匹配'); } }); } 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('未找到匹配的年龄段'); } // 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; } } else { if (performance >= standard) { return score; } } } return 0; } async getScore(data: z.infer) { 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('未找到对应的评分标准'); } 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')}`; } }