cr
This commit is contained in:
@@ -9,31 +9,31 @@ import {
|
||||
UseGuards,
|
||||
Req,
|
||||
Logger,
|
||||
} from '@nestjs/common';
|
||||
} from "@nestjs/common";
|
||||
import {
|
||||
ApiTags,
|
||||
ApiBearerAuth,
|
||||
ApiOperation,
|
||||
ApiResponse,
|
||||
} from '@nestjs/swagger';
|
||||
import { CouponsService } from './coupons.service';
|
||||
import { MatchesService } from '../matches/matches.service';
|
||||
import { SmartCouponService } from './services/smart-coupon.service';
|
||||
} from "@nestjs/swagger";
|
||||
import { CouponsService } from "./coupons.service";
|
||||
import { MatchesService } from "../matches/matches.service";
|
||||
import { SmartCouponService } from "./services/smart-coupon.service";
|
||||
import {
|
||||
UserCouponService,
|
||||
CreateCouponDto,
|
||||
} from './services/user-coupon.service';
|
||||
} from "./services/user-coupon.service";
|
||||
import {
|
||||
AnalyzeMatchDto,
|
||||
DailyBankoDto,
|
||||
SuggestCouponDto,
|
||||
} from './dto/coupons-request.dto';
|
||||
import { Public } from '../../common/decorators';
|
||||
import { JwtAuthGuard } from '../auth/guards/auth.guards'; // Assuming standard guard
|
||||
import { Sport } from '../matches/dto';
|
||||
} from "./dto/coupons-request.dto";
|
||||
import { Public } from "../../common/decorators";
|
||||
import { JwtAuthGuard } from "../auth/guards/auth.guards"; // Assuming standard guard
|
||||
import { Sport } from "../matches/dto";
|
||||
|
||||
@ApiTags('Coupon')
|
||||
@Controller('coupon')
|
||||
@ApiTags("Coupon")
|
||||
@Controller("coupon")
|
||||
export class CouponsController {
|
||||
private readonly logger = new Logger(CouponsController.name);
|
||||
|
||||
@@ -48,15 +48,15 @@ export class CouponsController {
|
||||
* POST /coupon/analyze-match
|
||||
* Analyze a single match with V20+ single-match package
|
||||
*/
|
||||
@Post('analyze-match')
|
||||
@Post("analyze-match")
|
||||
@Public()
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@ApiOperation({ summary: 'Analyze single match with V20 model' })
|
||||
@ApiResponse({ status: 200, description: 'Match analysis' })
|
||||
@ApiOperation({ summary: "Analyze single match with V20 model" })
|
||||
@ApiResponse({ status: 200, description: "Match analysis" })
|
||||
async analyzeMatch(@Body() dto: AnalyzeMatchDto) {
|
||||
const analysis = await this.smartCouponService.analyzeMatch(dto.matchId);
|
||||
if (!analysis) {
|
||||
return { success: false, message: 'Analiz yapılamadı.' };
|
||||
return { success: false, message: "Analiz yapılamadı." };
|
||||
}
|
||||
return { success: true, data: analysis };
|
||||
}
|
||||
@@ -64,11 +64,11 @@ export class CouponsController {
|
||||
/**
|
||||
* POST /coupon/analyze (alias for /analyze-match - frontend compatibility)
|
||||
*/
|
||||
@Post('analyze')
|
||||
@Post("analyze")
|
||||
@Public()
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@ApiOperation({
|
||||
summary: 'Analyze single match with V20 model (alias)',
|
||||
summary: "Analyze single match with V20 model (alias)",
|
||||
deprecated: true,
|
||||
})
|
||||
async analyzeMatchAlias(@Body() dto: AnalyzeMatchDto) {
|
||||
@@ -83,7 +83,7 @@ export class CouponsController {
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@HttpCode(HttpStatus.CREATED)
|
||||
@ApiOperation({ summary: 'Create and save a user coupon (alias)' })
|
||||
@ApiOperation({ summary: "Create and save a user coupon (alias)" })
|
||||
async createCouponAlias(@Body() dto: CreateCouponDto, @Req() req: any) {
|
||||
return this.createCoupon(dto, req);
|
||||
}
|
||||
@@ -92,11 +92,11 @@ export class CouponsController {
|
||||
* POST /coupon/daily-banko
|
||||
* Generate a high-confidence banko combo (2 matches)
|
||||
*/
|
||||
@Post('daily-banko')
|
||||
@Post("daily-banko")
|
||||
@Public()
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@ApiOperation({
|
||||
summary: 'Generate a high-confidence banko combo (2 matches)',
|
||||
summary: "Generate a high-confidence banko combo (2 matches)",
|
||||
})
|
||||
async getDailyBanko(@Body() dto: DailyBankoDto) {
|
||||
// If no match IDs provided, fetch from system (top 50 upcoming)
|
||||
@@ -122,7 +122,7 @@ export class CouponsController {
|
||||
if (candidateMatches.length === 0) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Kupon için uygun, henüz başlamamış maç bulunamadı.',
|
||||
message: "Kupon için uygun, henüz başlamamış maç bulunamadı.",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ export class CouponsController {
|
||||
if (!coupon) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Kriterlere uygun (80%+ güvenli) yeterli maç bulunamadı.',
|
||||
message: "Kriterlere uygun (80%+ güvenli) yeterli maç bulunamadı.",
|
||||
};
|
||||
}
|
||||
return { success: true, data: coupon };
|
||||
@@ -141,11 +141,11 @@ export class CouponsController {
|
||||
* POST /coupon/suggest
|
||||
* Generate Smart Coupon
|
||||
*/
|
||||
@Post('suggest')
|
||||
@Post("suggest")
|
||||
@Public()
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@ApiOperation({ summary: 'Suggest Smart Coupon' })
|
||||
@ApiResponse({ status: 200, description: 'Smart Coupon generated' })
|
||||
@ApiOperation({ summary: "Suggest Smart Coupon" })
|
||||
@ApiResponse({ status: 200, description: "Smart Coupon generated" })
|
||||
async suggestCoupon(@Body() dto: SuggestCouponDto) {
|
||||
// If no match IDs provided, fetch from system (top 50 upcoming)
|
||||
let candidateMatches = dto.matchIds || [];
|
||||
@@ -170,7 +170,7 @@ export class CouponsController {
|
||||
if (candidateMatches.length === 0) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Tahmin için uygun, henüz başlamamış maç bulunamadı.',
|
||||
message: "Tahmin için uygun, henüz başlamamış maç bulunamadı.",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -183,7 +183,7 @@ export class CouponsController {
|
||||
},
|
||||
);
|
||||
if (!coupon) {
|
||||
return { success: false, message: 'Kupon oluşturulamadı.' };
|
||||
return { success: false, message: "Kupon oluşturulamadı." };
|
||||
}
|
||||
return { success: true, data: coupon };
|
||||
}
|
||||
@@ -196,11 +196,11 @@ export class CouponsController {
|
||||
* POST /coupon/create
|
||||
* Save a user generated coupon
|
||||
*/
|
||||
@Post('create')
|
||||
@Post("create")
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@HttpCode(HttpStatus.CREATED)
|
||||
@ApiOperation({ summary: 'Create and save a user coupon' })
|
||||
@ApiOperation({ summary: "Create and save a user coupon" })
|
||||
async createCoupon(@Body() dto: CreateCouponDto, @Req() req: any) {
|
||||
// req.user is populated by JwtAuthGuard
|
||||
const coupon = await this.userCouponService.createCoupon(req.user, dto);
|
||||
@@ -211,10 +211,10 @@ export class CouponsController {
|
||||
* GET /coupon/my-stats
|
||||
* Get user betting statistics (ROI, Win Rate)
|
||||
*/
|
||||
@Get('my-stats')
|
||||
@Get("my-stats")
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@ApiOperation({ summary: 'Get user betting statistics' })
|
||||
@ApiOperation({ summary: "Get user betting statistics" })
|
||||
async getUserStats(@Req() req: any) {
|
||||
const stats = await this.userCouponService.getUserStatistics(req.user.id);
|
||||
return { success: true, data: stats };
|
||||
@@ -224,11 +224,11 @@ export class CouponsController {
|
||||
* GET /coupon/history
|
||||
* Get coupon history (Public/System coupons)
|
||||
*/
|
||||
@Get('history')
|
||||
@Get("history")
|
||||
@ApiBearerAuth()
|
||||
@ApiOperation({ summary: 'Get coupon history' })
|
||||
@ApiResponse({ status: 200, description: 'History retrieved' })
|
||||
async getHistory(@Query('limit') limit?: string) {
|
||||
@ApiOperation({ summary: "Get coupon history" })
|
||||
@ApiResponse({ status: 200, description: "History retrieved" })
|
||||
async getHistory(@Query("limit") limit?: string) {
|
||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
||||
const results = await this.couponsService.getCouponHistory(
|
||||
Number(limit) || 10,
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { CouponsController } from './coupons.controller';
|
||||
import { SmartCouponService } from './services/smart-coupon.service';
|
||||
import { UserCouponService } from './services/user-coupon.service';
|
||||
import { CouponsService } from './coupons.service';
|
||||
import { DatabaseModule } from '../../database/database.module';
|
||||
import { ServicesModule } from '../../services/services.module';
|
||||
import { MatchesModule } from '../matches/matches.module';
|
||||
import { Module } from "@nestjs/common";
|
||||
import { CouponsController } from "./coupons.controller";
|
||||
import { SmartCouponService } from "./services/smart-coupon.service";
|
||||
import { UserCouponService } from "./services/user-coupon.service";
|
||||
import { CouponsService } from "./coupons.service";
|
||||
import { DatabaseModule } from "../../database/database.module";
|
||||
import { ServicesModule } from "../../services/services.module";
|
||||
import { MatchesModule } from "../matches/matches.module";
|
||||
|
||||
@Module({
|
||||
imports: [DatabaseModule, ServicesModule, MatchesModule],
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { PrismaService } from '../../database/prisma.service';
|
||||
import { AiService } from '../../services/ai.service';
|
||||
import { Injectable, Logger } from "@nestjs/common";
|
||||
import { PrismaService } from "../../database/prisma.service";
|
||||
import { AiService } from "../../services/ai.service";
|
||||
// [REMOVED V16 IMPORTS]
|
||||
|
||||
export type RiskLevel = 'banko' | 'safe' | 'value';
|
||||
export type RiskLevel = "banko" | "safe" | "value";
|
||||
|
||||
export interface CouponMatch {
|
||||
matchId: string;
|
||||
|
||||
@@ -8,19 +8,19 @@ import {
|
||||
ArrayMaxSize,
|
||||
Min,
|
||||
Max,
|
||||
} from 'class-validator';
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
} from "class-validator";
|
||||
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
|
||||
|
||||
export enum CouponStrategyEnum {
|
||||
SAFE = 'SAFE',
|
||||
BALANCED = 'BALANCED',
|
||||
AGGRESSIVE = 'AGGRESSIVE',
|
||||
VALUE = 'VALUE',
|
||||
MIRACLE = 'MIRACLE',
|
||||
SAFE = "SAFE",
|
||||
BALANCED = "BALANCED",
|
||||
AGGRESSIVE = "AGGRESSIVE",
|
||||
VALUE = "VALUE",
|
||||
MIRACLE = "MIRACLE",
|
||||
}
|
||||
|
||||
export class AnalyzeMatchDto {
|
||||
@ApiProperty({ description: 'Match ID to analyze' })
|
||||
@ApiProperty({ description: "Match ID to analyze" })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
matchId: string;
|
||||
@@ -28,8 +28,8 @@ export class AnalyzeMatchDto {
|
||||
|
||||
export class DailyBankoDto {
|
||||
@ApiPropertyOptional({
|
||||
description: 'Optional match IDs — system fetches if empty',
|
||||
example: ['match-1', 'match-2'],
|
||||
description: "Optional match IDs — system fetches if empty",
|
||||
example: ["match-1", "match-2"],
|
||||
})
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@@ -40,8 +40,8 @@ export class DailyBankoDto {
|
||||
|
||||
export class SuggestCouponDto {
|
||||
@ApiPropertyOptional({
|
||||
description: 'Match IDs — system fetches if empty',
|
||||
example: ['match-1', 'match-2'],
|
||||
description: "Match IDs — system fetches if empty",
|
||||
example: ["match-1", "match-2"],
|
||||
})
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@@ -57,7 +57,7 @@ export class SuggestCouponDto {
|
||||
@IsEnum(CouponStrategyEnum)
|
||||
strategy?: CouponStrategyEnum;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Maximum matches in coupon', example: 5 })
|
||||
@ApiPropertyOptional({ description: "Maximum matches in coupon", example: 5 })
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Min(1)
|
||||
@@ -65,7 +65,7 @@ export class SuggestCouponDto {
|
||||
maxMatches?: number;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: 'Minimum confidence threshold (0-100)',
|
||||
description: "Minimum confidence threshold (0-100)",
|
||||
example: 60,
|
||||
})
|
||||
@IsOptional()
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common';
|
||||
import axios from 'axios';
|
||||
import { GeminiService } from '../../gemini/gemini.service';
|
||||
import { HttpException, HttpStatus, Injectable, Logger } from "@nestjs/common";
|
||||
import axios from "axios";
|
||||
import { GeminiService } from "../../gemini/gemini.service";
|
||||
|
||||
export type PredictionRiskLevel = 'LOW' | 'MEDIUM' | 'HIGH' | 'EXTREME';
|
||||
export type PredictionDataQuality = 'HIGH' | 'MEDIUM' | 'LOW';
|
||||
export type BetGrade = 'A' | 'B' | 'C' | 'PASS';
|
||||
export type PredictionRiskLevel = "LOW" | "MEDIUM" | "HIGH" | "EXTREME";
|
||||
export type PredictionDataQuality = "HIGH" | "MEDIUM" | "LOW";
|
||||
export type BetGrade = "A" | "B" | "C" | "PASS";
|
||||
|
||||
export interface PredictionPickRow {
|
||||
market: string;
|
||||
@@ -128,7 +128,7 @@ export class SmartCouponService {
|
||||
private readonly aiEngineUrl: string;
|
||||
|
||||
constructor(private readonly geminiService: GeminiService) {
|
||||
this.aiEngineUrl = process.env.AI_ENGINE_URL || 'http://ai-engine:8000';
|
||||
this.aiEngineUrl = process.env.AI_ENGINE_URL || "http://ai-engine:8000";
|
||||
}
|
||||
|
||||
async analyzeMatch(matchId: string): Promise<SingleMatchPredictionPackage> {
|
||||
@@ -147,7 +147,7 @@ export class SmartCouponService {
|
||||
);
|
||||
}
|
||||
throw new HttpException(
|
||||
'AI analyze failed',
|
||||
"AI analyze failed",
|
||||
HttpStatus.SERVICE_UNAVAILABLE,
|
||||
);
|
||||
}
|
||||
@@ -168,7 +168,7 @@ export class SmartCouponService {
|
||||
const result = await this.geminiService.generateText(
|
||||
JSON.stringify(prediction, null, 2),
|
||||
{
|
||||
model: 'gemini-2.0-flash',
|
||||
model: "gemini-2.0-flash",
|
||||
temperature: 0.7,
|
||||
maxTokens: 600,
|
||||
systemPrompt: MATCH_COMMENTARY_SYSTEM_PROMPT,
|
||||
@@ -176,7 +176,7 @@ export class SmartCouponService {
|
||||
);
|
||||
return result.text || null;
|
||||
} catch (error) {
|
||||
this.logger.warn('AI commentary generation failed, skipping', error);
|
||||
this.logger.warn("AI commentary generation failed, skipping", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -188,7 +188,7 @@ export class SmartCouponService {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.getSmartCoupon(matchIds, 'SAFE', {
|
||||
return this.getSmartCoupon(matchIds, "SAFE", {
|
||||
maxMatches: 2,
|
||||
minConfidence: 78,
|
||||
});
|
||||
@@ -197,11 +197,11 @@ export class SmartCouponService {
|
||||
async getSmartCoupon(
|
||||
matchIds: string[],
|
||||
strategy:
|
||||
| 'SAFE'
|
||||
| 'BALANCED'
|
||||
| 'AGGRESSIVE'
|
||||
| 'VALUE'
|
||||
| 'MIRACLE' = 'BALANCED',
|
||||
| "SAFE"
|
||||
| "BALANCED"
|
||||
| "AGGRESSIVE"
|
||||
| "VALUE"
|
||||
| "MIRACLE" = "BALANCED",
|
||||
options: { maxMatches?: number; minConfidence?: number } = {},
|
||||
): Promise<SmartCouponResult> {
|
||||
try {
|
||||
@@ -216,7 +216,7 @@ export class SmartCouponService {
|
||||
);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to generate smart coupon', error);
|
||||
this.logger.error("Failed to generate smart coupon", error);
|
||||
if (axios.isAxiosError(error)) {
|
||||
const detail = error.response?.data?.detail || error.message;
|
||||
throw new HttpException(
|
||||
@@ -225,7 +225,7 @@ export class SmartCouponService {
|
||||
);
|
||||
}
|
||||
throw new HttpException(
|
||||
'Coupon generation failed',
|
||||
"Coupon generation failed",
|
||||
HttpStatus.SERVICE_UNAVAILABLE,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { PrismaService } from '../../../database/prisma.service';
|
||||
import { User, UserCoupon, Match } from '@prisma/client';
|
||||
import { Injectable, Logger } from "@nestjs/common";
|
||||
import { PrismaService } from "../../../database/prisma.service";
|
||||
import { User, UserCoupon, Match } from "@prisma/client";
|
||||
|
||||
export class CreateCouponDto {
|
||||
strategy: string; // 'SAFE', 'VALUE', 'CUSTOM'
|
||||
@@ -39,7 +39,7 @@ export class UserCouponService {
|
||||
strategy: dto.strategy,
|
||||
totalOdds: parseFloat(totalOdds.toFixed(2)),
|
||||
isPublic: dto.isPublic || false,
|
||||
status: 'PENDING',
|
||||
status: "PENDING",
|
||||
couponItems: {
|
||||
create: dto.items.map((item) => ({
|
||||
matchId: item.matchId,
|
||||
@@ -66,7 +66,7 @@ export class UserCouponService {
|
||||
async updatePendingCoupons(): Promise<void> {
|
||||
// Sadece bitmiş (FT) maçları içeren PENDING kuponları çek
|
||||
const pendingCoupons = await this.prisma.userCoupon.findMany({
|
||||
where: { status: 'PENDING' },
|
||||
where: { status: "PENDING" },
|
||||
include: {
|
||||
couponItems: {
|
||||
include: { match: true },
|
||||
@@ -80,7 +80,7 @@ export class UserCouponService {
|
||||
let allMatchesFinished = true;
|
||||
|
||||
for (const item of coupon.couponItems) {
|
||||
if (item.match.status !== 'FT') {
|
||||
if (item.match.status !== "FT") {
|
||||
allMatchesFinished = false;
|
||||
break; // Henüz bitmemiş maç var, kuponu güncelleme
|
||||
}
|
||||
@@ -104,12 +104,12 @@ export class UserCouponService {
|
||||
if (isCouponLost) {
|
||||
await this.prisma.userCoupon.update({
|
||||
where: { id: coupon.id },
|
||||
data: { status: 'LOST' },
|
||||
data: { status: "LOST" },
|
||||
});
|
||||
} else if (allMatchesFinished && isCouponWon) {
|
||||
await this.prisma.userCoupon.update({
|
||||
where: { id: coupon.id },
|
||||
data: { status: 'WON' },
|
||||
data: { status: "WON" },
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -125,23 +125,23 @@ export class UserCouponService {
|
||||
const total = home + away;
|
||||
|
||||
switch (selection) {
|
||||
case 'MS 1':
|
||||
case "MS 1":
|
||||
return home > away;
|
||||
case 'MS X':
|
||||
case "MS X":
|
||||
return home === away;
|
||||
case 'MS 2':
|
||||
case "MS 2":
|
||||
return away > home;
|
||||
case '1.5 UST':
|
||||
case "1.5 UST":
|
||||
return total > 1.5;
|
||||
case '2.5 UST':
|
||||
case "2.5 UST":
|
||||
return total > 2.5;
|
||||
case '3.5 UST':
|
||||
case "3.5 UST":
|
||||
return total > 3.5;
|
||||
case '2.5 ALT':
|
||||
case "2.5 ALT":
|
||||
return total < 2.5;
|
||||
case 'KG VAR':
|
||||
case "KG VAR":
|
||||
return home > 0 && away > 0;
|
||||
case 'KG YOK':
|
||||
case "KG YOK":
|
||||
return home === 0 || away === 0;
|
||||
default:
|
||||
return false; // Bilinmeyen market
|
||||
@@ -155,7 +155,7 @@ export class UserCouponService {
|
||||
const coupons = await this.prisma.userCoupon.findMany({
|
||||
where: {
|
||||
userId,
|
||||
status: { in: ['WON', 'LOST'] },
|
||||
status: { in: ["WON", "LOST"] },
|
||||
},
|
||||
});
|
||||
|
||||
@@ -171,7 +171,7 @@ export class UserCouponService {
|
||||
};
|
||||
}
|
||||
|
||||
const wonCoupons = coupons.filter((c) => c.status === 'WON');
|
||||
const wonCoupons = coupons.filter((c) => c.status === "WON");
|
||||
const totalInvested = totalCoupons; // Her kupona 1 birim yatırıldığını varsayıyoruz
|
||||
const totalReturn = wonCoupons.reduce((acc, c) => acc + c.totalOdds, 0);
|
||||
const winRate = (wonCoupons.length / totalCoupons) * 100;
|
||||
|
||||
Reference in New Issue
Block a user