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,190 @@
import { Injectable, Logger } from '@nestjs/common';
import { PrismaService } from '../../../database/prisma.service';
/**
* Spor Toto Analitik Servisi
* - Havuz dağılım hesabı (%25/%20/%20/%35)
* - Expected Value (EV) hesabı
* - Devir geçmişi ve trend analizi
*/
@Injectable()
export class TotoAnalyticsService {
private readonly logger = new Logger(TotoAnalyticsService.name);
constructor(private readonly prisma: PrismaService) {}
/**
* Havuz dağılımını hesapla
* Spor Toto havuz dağılımı:
* %35 → 15 bilen
* %20 → 14 bilen
* %20 → 13 bilen
* %25 → 12 bilen
*/
calculatePoolDistribution(totalPool: number): {
pool15: number;
pool14: number;
pool13: number;
pool12: number;
} {
return {
pool15: totalPool * 0.35,
pool14: totalPool * 0.2,
pool13: totalPool * 0.2,
pool12: totalPool * 0.25,
};
}
/**
* Expected Value hesaplama
* EV = (Kazanma Olasılığı × Ödül) - Maliyet
*
* 15 maçın tamamını bilme olasılığı (hepsi tek tahmin):
* P = (1/3)^15 ≈ 1/14,348,907
*/
calculateEV(
poolTotal: number,
rolloverAmount: number,
columnCost: number,
columnCount: number,
): {
totalPool: number;
pool15: number;
probWin15: number;
ev15: number;
totalCost: number;
netEV: number;
} {
const effectivePool = poolTotal + rolloverAmount;
const distribution = this.calculatePoolDistribution(effectivePool);
// Basit olasılık: 1/3^15 (her maç bağımsız, 3 sonuç)
const probSingleColumn = 1 / Math.pow(3, 15); // ~6.97e-8
const probWin15 = 1 - Math.pow(1 - probSingleColumn, columnCount);
const totalCost = columnCost * columnCount;
const ev15 = probWin15 * distribution.pool15 - totalCost;
return {
totalPool: effectivePool,
pool15: distribution.pool15,
probWin15,
ev15,
totalCost,
netEV: ev15,
};
}
/**
* Devir geçmişi ve trend analizi
*/
async getRolloverHistory(limit = 10): Promise<{
history: Array<{
gameCycleNo: number;
programName: string | null;
poolTotal: number | null;
rolloverAmount: number | null;
winners15: number;
prize15: number | null;
}>;
averageRollover: number;
consecutiveRollovers: number;
}> {
const bulletins = await this.prisma.totoBulletin.findMany({
where: { status: 'COMPLETED' },
orderBy: { gameCycleNo: 'desc' },
take: limit,
include: { result: true },
});
const history = bulletins.map((b) => ({
gameCycleNo: b.gameCycleNo,
programName: b.programName,
poolTotal: b.poolTotal,
rolloverAmount: b.rolloverAmount,
winners15: b.result?.winners15 ?? 0,
prize15: b.result?.prize15 ?? null,
}));
// Ortalama devir miktarı
const rollovers = history
.map((h) => h.rolloverAmount ?? 0)
.filter((r) => r > 0);
const averageRollover =
rollovers.length > 0
? rollovers.reduce((a, b) => a + b, 0) / rollovers.length
: 0;
// Ardışık devir sayısı (son kaç haftadır devir var)
let consecutiveRollovers = 0;
for (const h of history) {
if (h.winners15 === 0) {
consecutiveRollovers++;
} else {
break;
}
}
return { history, averageRollover, consecutiveRollovers };
}
/**
* Bülten istatistikleri
*/
async getBulletinStats(bulletinId: string): Promise<{
poolDistribution: {
pool15: number;
pool14: number;
pool13: number;
pool12: number;
} | null;
ev: {
totalPool: number;
pool15: number;
probWin15: number;
ev15: number;
totalCost: number;
netEV: number;
} | null;
rolloverInfo: { averageRollover: number; consecutiveRollovers: number };
}> {
const bulletin = await this.prisma.totoBulletin.findUnique({
where: { id: bulletinId },
});
if (!bulletin) {
return {
poolDistribution: null,
ev: null,
rolloverInfo: { averageRollover: 0, consecutiveRollovers: 0 },
};
}
const poolDistribution = bulletin.poolTotal
? this.calculatePoolDistribution(
bulletin.poolTotal + (bulletin.rolloverAmount ?? 0),
)
: null;
const ev =
bulletin.poolTotal != null
? this.calculateEV(
bulletin.poolTotal,
bulletin.rolloverAmount ?? 0,
1, // birim fiyat
1, // tek kolon bazında
)
: null;
const rolloverInfo = await this.getRolloverHistory(20);
return {
poolDistribution,
ev,
rolloverInfo: {
averageRollover: rolloverInfo.averageRollover,
consecutiveRollovers: rolloverInfo.consecutiveRollovers,
},
};
}
}