main
Deploy Iddaai Backend / build-and-deploy (push) Successful in 37s

This commit is contained in:
2026-05-17 02:17:22 +03:00
parent 17ace9bd12
commit 94c7a4481a
53 changed files with 29602 additions and 7832 deletions
@@ -134,6 +134,7 @@ export class FeederPersistenceService {
categoryId: category.dbId,
name: sName,
oddValue: sValue,
openingValue: sValue,
position: sPos,
},
});
+112
View File
@@ -99,9 +99,121 @@ export class PredictionSettlementTask {
this.logger.log(
`Settlement finished: scanned=${scanned} updated=${updated}`,
);
const settled = await this.computeOddsMovementAndCleanup();
this.logger.log(
`Odds movement: computed=${settled.computed} cleaned=${settled.cleaned}`,
);
return { scanned, updated };
}
private async computeOddsMovementAndCleanup(): Promise<{
computed: number;
cleaned: number;
}> {
let computed = 0;
let cleaned = 0;
const finishedMatchIds = await this.prisma.$queryRaw<{ id: string }[]>(
Prisma.sql`
SELECT DISTINCT oh.match_id AS id
FROM odds_history oh
JOIN matches m ON m.id = oh.match_id
WHERE m.status = 'FT'
LIMIT 500
`,
);
if (finishedMatchIds.length === 0) return { computed, cleaned };
const matchIds = finishedMatchIds.map((r) => r.id);
for (const matchId of matchIds) {
try {
await this.computeMovementForMatch(matchId);
computed++;
} catch (e) {
this.logger.warn(`Movement calc failed for ${matchId}: ${e}`);
}
await this.prisma.oddsHistory.deleteMany({ where: { matchId } });
cleaned++;
}
return { computed, cleaned };
}
private async computeMovementForMatch(matchId: string): Promise<void> {
const selections = await this.prisma.$queryRaw<
{
name: string;
category_name: string;
opening_value: string | null;
odd_value: string | null;
}[]
>(Prisma.sql`
SELECT os.name, oc.name AS category_name,
os.opening_value, os.odd_value
FROM odd_selections os
JOIN odd_categories oc ON oc.db_id = os.odd_category_db_id
WHERE oc.match_id = ${matchId}
AND os.opening_value IS NOT NULL
AND os.odd_value IS NOT NULL
`);
const movements: Record<string, number> = {};
for (const sel of selections) {
const opening = parseFloat(sel.opening_value!);
const closing = parseFloat(sel.odd_value!);
if (!Number.isFinite(opening) || !Number.isFinite(closing) || opening <= 0)
continue;
const movement = ((closing - opening) / opening) * 100;
const cat = (sel.category_name ?? "").toLowerCase();
const name = (sel.name ?? "").toLowerCase();
if (cat.includes("maç sonucu") || cat.includes("mac sonucu") || cat === "ms") {
if (name === "1") movements.home = movement;
else if (name === "x" || name === "0") movements.draw = movement;
else if (name === "2") movements.away = movement;
} else if (
(cat.includes("2,5") || cat.includes("2.5")) &&
(cat.includes("alt/üst") || cat.includes("alt/ust"))
) {
if (name.includes("üst") || name.includes("ust") || name.includes("over"))
movements.o25 = movement;
} else if (
cat.includes("karşılıklı gol") ||
cat.includes("karsilikli gol") ||
cat === "kg"
) {
if (name === "var" || name.includes("yes")) movements.btts = movement;
}
}
if (Object.keys(movements).length === 0) return;
const vals = Object.values(movements);
const sharpness =
vals.length > 0
? vals.reduce((sum, v) => sum + Math.abs(v), 0) / vals.length
: 0;
await this.prisma.footballAiFeature.updateMany({
where: { matchId },
data: {
oddsMovementHome: movements.home ?? null,
oddsMovementDraw: movements.draw ?? null,
oddsMovementAway: movements.away ?? null,
oddsMovementO25: movements.o25 ?? null,
oddsMovementBtts: movements.btts ?? null,
oddsSharpness: sharpness || null,
},
});
}
private settleRow(
row: UnresolvedRow,
): { outcome: string; unitProfit: number } | null {