first (part 3: src directory)
Deploy Iddaai Backend / build-and-deploy (push) Successful in 33s

This commit is contained in:
2026-04-16 15:12:27 +03:00
parent 2f0b85a0c7
commit 182f4aae16
125 changed files with 22552 additions and 0 deletions
@@ -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;
}
}