153 lines
4.0 KiB
TypeScript
Executable File
153 lines
4.0 KiB
TypeScript
Executable File
import { Injectable, Logger } from "@nestjs/common";
|
|
import { PrismaService } from "../../database/prisma.service";
|
|
import {
|
|
MatchAnalysisService,
|
|
AnalysisResult,
|
|
} from "../../services/match-analysis.service";
|
|
|
|
@Injectable()
|
|
export class AnalysisService {
|
|
private readonly logger = new Logger(AnalysisService.name);
|
|
|
|
constructor(
|
|
private readonly prisma: PrismaService,
|
|
private readonly matchAnalysisService: MatchAnalysisService,
|
|
) {}
|
|
|
|
/**
|
|
* Analyze multiple matches (coupon)
|
|
*/
|
|
async analyzeCoupon(matchIds: string[], userId: string): Promise<any> {
|
|
this.logger.log(`Analyzing ${matchIds.length} matches for coupon`);
|
|
|
|
const results: AnalysisResult[] = [];
|
|
|
|
for (const matchId of matchIds) {
|
|
try {
|
|
// Get match from DB
|
|
const match = await this.prisma.match.findFirst({
|
|
where: {
|
|
OR: [{ id: matchId }],
|
|
},
|
|
include: {
|
|
league: true,
|
|
homeTeam: true,
|
|
awayTeam: true,
|
|
},
|
|
});
|
|
|
|
// Try live match if not found
|
|
const liveMatch = !match
|
|
? await this.prisma.liveMatch.findUnique({
|
|
where: { id: matchId },
|
|
})
|
|
: null;
|
|
|
|
const targetMatch = match || liveMatch;
|
|
if (!targetMatch) {
|
|
this.logger.warn(`Match not found: ${matchId}`);
|
|
continue;
|
|
}
|
|
|
|
// Build URL for analysis
|
|
const sport = (targetMatch as any).sport || "football";
|
|
const slug = (targetMatch as any).matchSlug || matchId;
|
|
const url = `https://www.mackolik.com/${sport === "basketball" ? "basketbol/mac" : "mac"}/${slug}/${matchId}`;
|
|
|
|
// Run analysis
|
|
const result = await this.matchAnalysisService.analyzeMatch(
|
|
url,
|
|
userId,
|
|
);
|
|
results.push(result);
|
|
} catch (err: any) {
|
|
this.logger.warn(`Analysis failed for ${matchId}: ${err.message}`);
|
|
}
|
|
}
|
|
|
|
if (results.length === 0) {
|
|
return null;
|
|
}
|
|
|
|
// Combine results into coupon format
|
|
return {
|
|
totalMatches: matchIds.length,
|
|
analyzedMatches: results.length,
|
|
matches: results.map((r) => ({
|
|
matchDetails: r.matchDetails,
|
|
predictions: r.aiAnalysis?.predictions || [],
|
|
recommendedBets: r.aiAnalysis?.recommendedBets || [],
|
|
confidence: r.aiAnalysis?.confidenceScore || 0,
|
|
})),
|
|
generatedAt: new Date().toISOString(),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Check user usage limit
|
|
*/
|
|
async checkUsageLimit(
|
|
userId: string,
|
|
isCoupon: boolean,
|
|
matchCount: number,
|
|
): Promise<boolean> {
|
|
const usageLimit = await this.prisma.usageLimit.findUnique({
|
|
where: { userId },
|
|
});
|
|
|
|
if (!usageLimit) {
|
|
// Create default limit
|
|
await this.prisma.usageLimit.create({
|
|
data: {
|
|
userId,
|
|
analysisCount: 0,
|
|
couponCount: 0,
|
|
lastResetDate: new Date(),
|
|
},
|
|
});
|
|
return true;
|
|
}
|
|
|
|
// Check limits (default: 10 analyses, 3 coupons per day)
|
|
const user = await this.prisma.user.findUnique({ where: { id: userId } });
|
|
const isPremium = user?.subscriptionStatus === "active";
|
|
|
|
const maxAnalyses = isPremium ? 50 : 10;
|
|
const maxCoupons = isPremium ? 10 : 3;
|
|
|
|
if (isCoupon) {
|
|
return usageLimit.couponCount < maxCoupons;
|
|
}
|
|
|
|
return usageLimit.analysisCount + matchCount <= maxAnalyses;
|
|
}
|
|
|
|
/**
|
|
* Record usage
|
|
*/
|
|
async recordUsage(userId: string, isCoupon: boolean): Promise<void> {
|
|
if (isCoupon) {
|
|
await this.prisma.usageLimit.update({
|
|
where: { userId },
|
|
data: { couponCount: { increment: 1 } },
|
|
});
|
|
} else {
|
|
await this.prisma.usageLimit.update({
|
|
where: { userId },
|
|
data: { analysisCount: { increment: 1 } },
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get user analysis history
|
|
*/
|
|
async getAnalysisHistory(userId: string, limit: number = 20) {
|
|
return this.prisma.analysis.findMany({
|
|
where: { userId },
|
|
orderBy: { createdAt: "desc" },
|
|
take: limit,
|
|
});
|
|
}
|
|
}
|