v28
Deploy Iddaai Backend / build-and-deploy (push) Successful in 3m21s

This commit is contained in:
2026-04-24 23:46:28 +03:00
parent 3875f2a512
commit 9027cc9900
17 changed files with 4315 additions and 122 deletions
+74 -5
View File
@@ -223,11 +223,13 @@ export class PredictionsService implements OnModuleInit, OnModuleDestroy {
`/v20plus/analyze/${matchId}`,
{ simulate: true, is_simulation: true, pre_match_only: true },
);
await this.recordPredictionRun(matchId, response.data);
return this.enrichPredictionResponse(
response.data as MatchPredictionDto,
const prediction = this.enrichPredictionResponse(
response.data,
matchContext,
);
await this.recordPredictionRun(matchId, response.data);
await this.cachePrediction(matchId, prediction);
return prediction;
} catch (e: unknown) {
const requestError =
e instanceof AiEngineRequestError
@@ -235,6 +237,20 @@ export class PredictionsService implements OnModuleInit, OnModuleDestroy {
: new AiEngineRequestError("AI Engine request failed");
const status = requestError.status;
const detail = requestError.detail || requestError.message;
if (
status === HttpStatus.SERVICE_UNAVAILABLE &&
this.hasCooldown(detail)
) {
const storedPrediction = await this.getStoredPrediction(matchId);
if (storedPrediction) {
this.logger.warn(
`AI Engine cooldown for ${matchId}; returning stored prediction`,
);
return this.enrichPredictionResponse(storedPrediction, matchContext);
}
}
this.logger.error(
`Direct AI Engine call failed for ${matchId}: status=${status}, detail=${JSON.stringify(detail)}`,
);
@@ -674,6 +690,11 @@ export class PredictionsService implements OnModuleInit, OnModuleDestroy {
odds: this.normalizeDisplayOdds(odds, impliedProb),
implied_prob: impliedProb,
ev_edge: evEdge,
playable: Boolean(record.playable) && interval.threshold_met,
stake_units:
Boolean(record.playable) && interval.threshold_met
? this.asNumber(record.stake_units)
: 0,
reasons: Array.isArray(record.reasons)
? record.reasons.map((reason) => this.translateReason(String(reason)))
: [],
@@ -919,15 +940,39 @@ export class PredictionsService implements OnModuleInit, OnModuleDestroy {
return 0;
}
const normalizedPick = pickName.toUpperCase();
const normalizedPick = this.normalizePickKey(pickName);
for (const [key, value] of Object.entries(probabilities)) {
if (key.toUpperCase() === normalizedPick) {
if (this.normalizePickKey(key) === normalizedPick) {
return this.asNumber(value);
}
}
return 0;
}
private normalizePickKey(value: string): string {
const normalized = value.trim().toUpperCase();
const aliases: Record<string, string> = {
ÜST: "OVER",
UST: "OVER",
OVER: "OVER",
ALT: "UNDER",
UNDER: "UNDER",
"KG VAR": "YES",
VAR: "YES",
YES: "YES",
"KG YOK": "NO",
YOK: "NO",
NO: "NO",
TEK: "ODD",
ODD: "ODD",
ÇİFT: "EVEN",
CIFT: "EVEN",
EVEN: "EVEN",
};
return aliases[normalized] ?? normalized;
}
private impliedProbabilityFromOdds(odds: number): number {
if (odds <= 1) {
return 0;
@@ -1132,6 +1177,30 @@ export class PredictionsService implements OnModuleInit, OnModuleDestroy {
return prediction.predictionJson as unknown as MatchPredictionDto;
}
private async getStoredPrediction(
matchId: string,
): Promise<MatchPredictionDto | null> {
const prediction = await this.prisma.prediction.findUnique({
where: { matchId },
});
return prediction
? (prediction.predictionJson as unknown as MatchPredictionDto)
: null;
}
private hasCooldown(detail: unknown): boolean {
if (typeof detail === "string") {
return detail.includes("cooldownRemainingMs");
}
if (detail && typeof detail === "object") {
return "cooldownRemainingMs" in detail;
}
return false;
}
private async ensureSmartCouponDataReady(matchIds: string[]): Promise<void> {
const uniqueMatchIds = [...new Set(matchIds.filter((id) => !!id))];
if (uniqueMatchIds.length === 0) {