Skip to content

Commit

Permalink
fix: add numerical ranking endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
Fllorent0D committed Sep 20, 2023
1 parent 184e4b8 commit 60dbe15
Show file tree
Hide file tree
Showing 10 changed files with 268 additions and 110 deletions.
3 changes: 3 additions & 0 deletions prisma/migrations/20230920070531_add_float/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-- AlterTable
ALTER TABLE "IndividualResult" ALTER COLUMN "diffPoints" SET DATA TYPE DOUBLE PRECISION,
ALTER COLUMN "pointsToAdd" SET DATA TYPE DOUBLE PRECISION;
4 changes: 2 additions & 2 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ model IndividualResult {
competitionCoef Int
competitionType CompetitionType
competitionName String
diffPoints Int
pointsToAdd Int
diffPoints Float
pointsToAdd Float
looseFactor Float
definitivePointsToAdd Int
member Member @relation("member", fields: [memberId, memberLicence], references: [id, licence])
Expand Down
24 changes: 23 additions & 1 deletion src/api/member/controllers/member.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,16 @@ import {
WeeklyNumericRanking,
WeeklyNumericRankingInput,
WeeklyNumericRankingInputV2,
WeeklyNumericRankingV2, WeeklyNumericRankingV3,
WeeklyNumericRankingV2,
WeeklyNumericRankingV3,
} from '../dto/member.dto';
import { PlayerCategory } from '../../../entity/tabt-input.interface';
import { EloMemberService } from '../../../services/members/elo-member.service';
import { SeasonService } from '../../../services/seasons/season.service';
import { MembersSearchIndexService } from '../../../services/members/members-search-index.service';
import { MemberCategoryService } from '../../../services/members/member-category.service';
import { getSimplifiedPlayerCategory } from '../helpers/player-category-helpers';
import { NumericRankingService } from 'src/common/data-aftt/services/numeric-ranking.service';

@ApiTags('Members')
@Controller({
Expand All @@ -43,6 +45,7 @@ export class MemberController {
private readonly eloMemberService: EloMemberService,
private readonly seasonService: SeasonService,
private readonly membersSearchIndexService: MembersSearchIndexService,
private readonly numericRankingService: NumericRankingService
) {
}

Expand Down Expand Up @@ -218,6 +221,25 @@ export class MemberController {
return numericRankingV3;
}

@Get(':uniqueIndex/numeric-rankings')
@ApiOkResponse({
type: WeeklyNumericRankingV3,
description: 'The list of ELO points for a player in a season',
})
@ApiOperation({
operationId: 'findMemberNumericRankingsHistoryV3',
})
@ApiNotFoundResponse({
description: 'No points found for given player',
})
@Version('4')
async findNumericRankingV4(
@Param('uniqueIndex', ParseIntPipe) id: number,
@Query() params: WeeklyNumericRankingInputV2,
) {
const simplifiedCategory = getSimplifiedPlayerCategory(params.category);
return await this.numericRankingService.getWeeklyRanking(id, simplifiedCategory);
}


}
2 changes: 2 additions & 0 deletions src/api/member/dto/member.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,8 @@ export class WeeklyNumericRankingV3 {
perDateHistory: NumericRankingDetailsV3[];
}

export type WeeklyNumericRankingV4 = WeeklyNumericRankingV3;

export enum PLAYER_CATEGORY {
MEN = 'MEN',
WOMEN = 'WOMEN',
Expand Down
7 changes: 5 additions & 2 deletions src/common/common.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { PrismaService } from './prisma.service';
import { DataAFTTMemberNumericRankingModel } from './data-aftt/model/member-numeric-ranking.model';
import { DataAFTTMemberProcessingService } from './data-aftt/services/member-processing.service';
import { DataAFTTResultsProcessingService } from './data-aftt/services/results-processing.service';
import { NumericRankingService } from './data-aftt/services/numeric-ranking.service';


const asyncProviders: Provider[] = [
Expand Down Expand Up @@ -90,7 +91,8 @@ const asyncProviders: Provider[] = [
DataAFTTMemberNumericRankingModel,
PrismaService,
DataAFTTMemberProcessingService,
DataAFTTResultsProcessingService
DataAFTTResultsProcessingService,
NumericRankingService
],
exports: [
...asyncProviders,
Expand All @@ -105,7 +107,8 @@ const asyncProviders: Provider[] = [
DataAFTTMemberModel,
DataAFTTMemberNumericRankingModel,
DataAFTTMemberProcessingService,
DataAFTTResultsProcessingService
DataAFTTResultsProcessingService,
NumericRankingService
],
})
export class CommonModule {
Expand Down
42 changes: 40 additions & 2 deletions src/common/data-aftt/model/individual-results.model.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { Injectable } from "@nestjs/common";
import { PrismaService } from "../../prisma.service";
import { Gender, IndividualResult, Member } from "@prisma/client";
import { Gender, IndividualResult, Member, Prisma } from "@prisma/client";

export type IndividualResultWithOpponent = Prisma.IndividualResultGetPayload<{
include: {
memberOpponent: true
}
}>;

@Injectable()
export class DataAFTTIndividualResultModel {
Expand All @@ -9,6 +15,23 @@ export class DataAFTTIndividualResultModel {
) {
}

async getResults(
licence: number,
gender: Gender
): Promise<IndividualResultWithOpponent[]> {
return this.prismaService.individualResult.findMany({
where: {
memberLicence: licence,
member: {
gender
}
},
include: {
memberOpponent: true
}
});
}

async upsert(
result: Omit<Omit<IndividualResult, 'memberId'>, 'opponentId'>,
gender: Gender
Expand All @@ -29,11 +52,26 @@ export class DataAFTTIndividualResultModel {
})
]);


return this.prismaService.individualResult.upsert({
where: {
id: result.id
},
update: {
date: result.date,
competitionType: result.competitionType,
score: result.score,
memberRanking: result.memberRanking,
memberPoints: result.memberPoints,
opponentRanking: result.opponentRanking,
opponentPoints: result.opponentPoints,
competitionCoef: result.competitionCoef,
competitionName: result.competitionName,
result: result.result,
definitivePointsToAdd: result.definitivePointsToAdd,
diffPoints: result.diffPoints,
looseFactor: result.looseFactor,
pointsToAdd: result.pointsToAdd,
member: {
connect: {
id_licence: {
Expand Down Expand Up @@ -88,4 +126,4 @@ export class DataAFTTIndividualResultModel {
}


}
}
18 changes: 17 additions & 1 deletion src/common/data-aftt/model/member-numeric-ranking.model.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Injectable } from "@nestjs/common";
import { PrismaService } from "../../prisma.service";
import { NumericPoints } from "@prisma/client";
import { Gender, NumericPoints } from "@prisma/client";

@Injectable()
export class DataAFTTMemberNumericRankingModel {
Expand All @@ -10,6 +10,22 @@ export class DataAFTTMemberNumericRankingModel {
}


getLatestPoints(licence: number, gender: Gender): Promise<NumericPoints[]> {
const points = this.prismaService.numericPoints.findMany({
where: {
memberLicence: licence,
member: {
gender
}
},
orderBy: {
date: 'desc'
}
});
return points;
}


async insertInHistory(points: NumericPoints): Promise<NumericPoints> {
// get latest point for member
// if latest point is different from current point, insert new point. I want to keen track of the evolution of the points
Expand Down
176 changes: 86 additions & 90 deletions src/common/data-aftt/services/member-processing.service.ts
Original file line number Diff line number Diff line change
@@ -1,100 +1,96 @@
import { HttpService } from "@nestjs/axios";
import { ConfigService } from "@nestjs/config";
import { DataAFTTMemberModel } from "../model/member.model";
import { DataAFTTMemberNumericRankingModel } from "../model/member-numeric-ranking.model";
import { genderMapping } from "../constants";
import { firstValueFrom } from "rxjs";
import { Injectable, Logger } from "@nestjs/common";
import { Gender } from "@prisma/client";
import * as pqueue from 'p-queue';
import * as os from 'os';
import { HttpService } from '@nestjs/axios';
import { ConfigService } from '@nestjs/config';
import { DataAFTTMemberModel } from '../model/member.model';
import { DataAFTTMemberNumericRankingModel } from '../model/member-numeric-ranking.model';
import { genderMapping } from '../constants';
import { firstValueFrom } from 'rxjs';
import { Injectable, Logger } from '@nestjs/common';
import { Gender } from '@prisma/client';

@Injectable()
export class DataAFTTMemberProcessingService {

private readonly logger = new Logger(DataAFTTMemberProcessingService.name);
private readonly logger = new Logger(DataAFTTMemberProcessingService.name);

constructor(
private readonly httpService: HttpService,
private readonly configService: ConfigService,
private readonly memberServiceModel: DataAFTTMemberModel,
private readonly numericRankingModel: DataAFTTMemberNumericRankingModel
) {
}
constructor(
private readonly httpService: HttpService,
private readonly configService: ConfigService,
private readonly memberServiceModel: DataAFTTMemberModel,
private readonly numericRankingModel: DataAFTTMemberNumericRankingModel,
) {
}


async process(): Promise<void> {
const queue = new pqueue.default({concurrency: (os.cpus().length * 2) + 1});
for (const [gender, mapping] of genderMapping) {
const file = await this.downloadFile(gender, mapping);

// split lines and remove last line
const lines = file.data.split('\n').slice(0, -1);
//console.log(cols);
this.logger.log(`File downloaded, start processing with concurrency ${queue.concurrency}...`);
queue.addAll(lines.map(line => async () => {
const cols = line.split(';');
return this.updateDB(cols, gender);
}));
console.log(`Processing ${queue.size} lines...`)
await queue.onIdle();
this.logger.log(`Processing done. (${lines.length} lines)`);
}
}
async process(): Promise<void> {
for (const [gender, mapping] of genderMapping) {
const file = await this.downloadFile(gender, mapping);

private async updateDB(cols: string[], gender: Gender) {
try {
await this.memberServiceModel.upsert({
id: parseInt(cols[0], 10),
licence: parseInt(cols[1], 10),
gender,
lastname: cols[2],
firstname: cols[3],
ranking: cols[4],
club: cols[5],
category: cols[7],
worldRanking: parseInt(cols[8], 10),
nationality: cols[9],
});
/*
17;
519190;
GERTENBACH;
ANGELIQUE;
B2;
5 - A062;
6 - 0;
7 - SEN;
8 - 999;
9 - NL;
10 - 2205
Ranking_Pos 11 - ;
Ranking_Pos_WI - 12 - ;
RankingAn - 13 - 153;
*/
await this.numericRankingModel.insertInHistory({
memberId: parseInt(cols[0], 10),
memberLicence: parseInt(cols[1], 10),
date: new Date(),
points: parseInt(cols[10], 10),
ranking: cols[11].length ? parseInt(cols[11]) : null,
rankingWI: cols[12].length ? parseInt(cols[12]) : null,
rankingLetterEstimation: null
});
} catch (e) {
this.logger.error(e.message);
}
// split lines and remove last line
const lines = file.data.split('\n').slice(0, -1);
//console.log(cols);
this.logger.log(`File downloaded, start processing ${lines.length} lines...`);
for (const line of lines) {
const cols = line.split(';');
return this.updateDB(cols, gender);
}
this.logger.log(`Processing done. (${lines.length} lines)`);
}
}

private async downloadFile(gender: string, mapping: string) {
this.logger.log(`Downloading ${gender} file from data.aftt.be`);
const url = `https://data.aftt.be/export/liste_joueurs_${mapping}.txt`;
const file = await firstValueFrom(this.httpService.get<string>(url, {
auth: {
username: this.configService.get('AFTT_DATA_USERNAME'),
password: this.configService.get('AFTT_DATA_PASSWORD')
},
responseType: 'text'
}));
return file;
private async updateDB(cols: string[], gender: Gender) {
try {
await this.memberServiceModel.upsert({
id: parseInt(cols[0], 10),
licence: parseInt(cols[1], 10),
gender,
lastname: cols[2],
firstname: cols[3],
ranking: cols[4],
club: cols[5],
category: cols[7],
worldRanking: parseInt(cols[8], 10),
nationality: cols[9],
});
/*
17;
519190;
GERTENBACH;
ANGELIQUE;
B2;
5 - A062;
6 - 0;
7 - SEN;
8 - 999;
9 - NL;
10 - 2205
Ranking_Pos 11 - ;
Ranking_Pos_WI - 12 - ;
RankingAn - 13 - 153;
*/
await this.numericRankingModel.insertInHistory({
memberId: parseInt(cols[0], 10),
memberLicence: parseInt(cols[1], 10),
date: new Date(),
points: parseInt(cols[10], 10),
ranking: cols[11].length ? parseInt(cols[11]) : null,
rankingWI: cols[12].length ? parseInt(cols[12]) : null,
rankingLetterEstimation: null,
});
} catch (e) {
this.logger.error(e.message);
}
}
}

private async downloadFile(gender: string, mapping: string) {
this.logger.log(`Downloading ${gender} file from data.aftt.be`);
const url = `https://data.aftt.be/export/liste_joueurs_${mapping}.txt`;
const file = await firstValueFrom(this.httpService.get<string>(url, {
auth: {
username: this.configService.get('AFTT_DATA_USERNAME'),
password: this.configService.get('AFTT_DATA_PASSWORD'),
},
responseType: 'text',
}));
return file;
}
}
Loading

0 comments on commit 60dbe15

Please sign in to comment.