Files
iddaai-be/src/modules/predictions/services/ai-feature-store.service.ts
T
fahricansecer 182f4aae16
Deploy Iddaai Backend / build-and-deploy (push) Successful in 33s
first (part 3: src directory)
2026-04-16 15:12:27 +03:00

115 lines
3.7 KiB
TypeScript
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
}
}