This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { PrismaService } from '../../../database/prisma.service';
|
||||
|
||||
@Injectable()
|
||||
export class AiFeatureStoreService {
|
||||
private readonly logger = new Logger(AiFeatureStoreService.name);
|
||||
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
/**
|
||||
* Bir maç için AI özelliklerini hesaplar ve 'match_ai_features' tablosuna yazar.
|
||||
* Bu metod Feeder yeni veri çektiğinde tetiklenmelidir.
|
||||
*/
|
||||
async calculateAndSaveFeatures(matchId: string): Promise<void> {
|
||||
const match = await this.prisma.match.findUnique({
|
||||
where: { id: matchId },
|
||||
include: {
|
||||
homeTeam: {
|
||||
include: { homeMatches: { take: 5, orderBy: { mstUtc: 'desc' } } },
|
||||
},
|
||||
awayTeam: {
|
||||
include: { awayMatches: { take: 5, orderBy: { mstUtc: 'desc' } } },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!match || !match.homeTeam || !match.awayTeam) return;
|
||||
|
||||
// 1. Form Score Calculation (0-100)
|
||||
// Son 5 maçtaki galibiyet, beraberlik ve atılan gollerin ağırlıklı ortalaması
|
||||
const homeForm = this.calculateFormScore(match.homeTeam.homeMatches);
|
||||
const awayForm = this.calculateFormScore(match.awayTeam.awayMatches);
|
||||
|
||||
// 2. ELO — Read from team_elo_ratings table (populated by AI Engine compute_elo.py)
|
||||
const homeElo = match.homeTeamId
|
||||
? await this.getTeamElo(match.homeTeamId)
|
||||
: 1500.0;
|
||||
const awayElo = match.awayTeamId
|
||||
? await this.getTeamElo(match.awayTeamId)
|
||||
: 1500.0;
|
||||
|
||||
// 3. Missing Player Impact (Sakat/Cezalı etkisi)
|
||||
// Feeder'dan gelen lineups verisindeki eksik as oyuncuları analiz etmeliyiz.
|
||||
// Şimdilik 0.0 (Etkisiz) olarak set ediyoruz, ilerde Lineup analizi buraya eklenecek.
|
||||
const missingImpact = 0.0;
|
||||
|
||||
// 4. Save to Feature Store
|
||||
await this.prisma.footballAiFeature.upsert({
|
||||
where: { matchId },
|
||||
update: {
|
||||
homeElo,
|
||||
awayElo,
|
||||
homeFormScore: homeForm,
|
||||
awayFormScore: awayForm,
|
||||
missingPlayersImpact: missingImpact,
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
create: {
|
||||
matchId,
|
||||
homeElo,
|
||||
awayElo,
|
||||
homeFormScore: homeForm,
|
||||
awayFormScore: awayForm,
|
||||
missingPlayersImpact: missingImpact,
|
||||
},
|
||||
});
|
||||
|
||||
this.logger.debug(
|
||||
`Features calculated for match ${matchId} (Home Form: ${homeForm}, Away Form: ${awayForm})`,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Form Puanı Hesaplama Algoritması (V17 Simplified)
|
||||
* W=30, D=10, L=0 puan. + Gol başına 5 puan (max 15).
|
||||
* Toplam skor 0-100 arasına normalize edilir.
|
||||
*/
|
||||
private calculateFormScore(matches: any[]): number {
|
||||
if (!matches || matches.length === 0) return 50; // Nötr form
|
||||
|
||||
let totalPoints = 0;
|
||||
const maxPoints = matches.length * 45; // Max olası puan (30win + 15goal)
|
||||
|
||||
for (const m of matches) {
|
||||
// Skor kontrolü (bazı maçlar oynanmamış olabilir)
|
||||
if (m.scoreHome === null || m.scoreAway === null) continue;
|
||||
|
||||
const isWin = m.scoreHome > m.scoreAway; // Home team context
|
||||
const isDraw = m.scoreHome === m.scoreAway;
|
||||
|
||||
if (isWin) totalPoints += 30;
|
||||
else if (isDraw) totalPoints += 10;
|
||||
|
||||
const goals = Math.min(m.scoreHome, 3); // Max 3 gol katkısı
|
||||
totalPoints += goals * 5;
|
||||
}
|
||||
|
||||
// Normalize to 0-100
|
||||
// Eğer hiç maç oynanmadıysa yine 50 dön.
|
||||
return matches.length > 0 ? (totalPoints / maxPoints) * 100 : 50;
|
||||
}
|
||||
|
||||
/**
|
||||
* team_elo_ratings tablosundan takımın güncel ELO puanını okur.
|
||||
* Kayıt yoksa varsayılan 1500.0 döner.
|
||||
*/
|
||||
private async getTeamElo(teamId: string): Promise<number> {
|
||||
const row = await this.prisma.teamEloRating.findUnique({
|
||||
where: { teamId },
|
||||
select: { overallElo: true },
|
||||
});
|
||||
return row?.overallElo ?? 1500.0;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user