This commit is contained in:
@@ -111,6 +111,22 @@ export class MatchesController {
|
||||
return this.matchesService.getActiveLeagues(sport || Sport.FOOTBALL);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /matches/:id/odds-movement
|
||||
* Opening→closing odds movement per market/selection (from live_odds_history)
|
||||
*/
|
||||
@Public()
|
||||
@Get(":id/odds-movement")
|
||||
@ApiOperation({ summary: "Opening→closing odds movement for a match" })
|
||||
@ApiParam({ name: "id", description: "Match ID" })
|
||||
@ApiResponse({ status: 200, description: "{ market: { selection: { open, close } } }" })
|
||||
async getOddsMovement(@Param("id") id: string) {
|
||||
if (!id) {
|
||||
throw new BadRequestException("Match ID is required");
|
||||
}
|
||||
return this.matchesService.getOddsMovement(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /matches/:id
|
||||
* Get full match details
|
||||
|
||||
@@ -28,6 +28,51 @@ export class MatchesService {
|
||||
this.loadTopLeagues();
|
||||
}
|
||||
|
||||
/**
|
||||
* Per-match odds movement (opening→closing) from live_odds_history.
|
||||
* Returns { [market]: { [selection]: { open, close } } } with the same
|
||||
* Turkish market/selection labels used in match.odds, so the UI can line
|
||||
* them up directly. Returns {} if there is no data or the table is absent.
|
||||
*/
|
||||
async getOddsMovement(
|
||||
matchId: string,
|
||||
): Promise<Record<string, Record<string, { open: number; close: number }>>> {
|
||||
try {
|
||||
const rows = await this.prisma.$queryRawUnsafe<
|
||||
Array<{
|
||||
market: string;
|
||||
selection: string;
|
||||
open: number | null;
|
||||
close: number | null;
|
||||
}>
|
||||
>(
|
||||
`SELECT market, selection,
|
||||
(array_agg(new_value ORDER BY change_time ASC))[1] AS open,
|
||||
(array_agg(new_value ORDER BY change_time DESC))[1] AS close
|
||||
FROM live_odds_history
|
||||
WHERE match_id = $1
|
||||
GROUP BY market, selection`,
|
||||
matchId,
|
||||
);
|
||||
const out: Record<
|
||||
string,
|
||||
Record<string, { open: number; close: number }>
|
||||
> = {};
|
||||
for (const r of rows) {
|
||||
if (r.open == null || r.close == null) continue;
|
||||
(out[r.market] ??= {})[r.selection] = {
|
||||
open: Number(r.open),
|
||||
close: Number(r.close),
|
||||
};
|
||||
}
|
||||
return out;
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
this.logger.warn(`getOddsMovement failed for ${matchId}: ${msg}`);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
private loadTopLeagues() {
|
||||
try {
|
||||
const filePath = path.join(process.cwd(), "top_leagues.json");
|
||||
|
||||
Reference in New Issue
Block a user