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[]) { // 先按起始年龄排序 const sortedRanges = [...ranges].sort( (a, b) => (a.start || 0) - (b.start || 0), ); 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} 不连续`); } } } 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')}`; } }