Initial commit - MIDAS App educação financeira para apostadores (FastAPI + Next.js)
This commit is contained in:
0
backend/app/services/__init__.py
Normal file
0
backend/app/services/__init__.py
Normal file
40
backend/app/services/gamification.py
Normal file
40
backend/app/services/gamification.py
Normal file
@@ -0,0 +1,40 @@
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select, func
|
||||
from app.models.achievement import Achievement, UserAchievement
|
||||
from app.models.bet import Bet
|
||||
from app.models.lesson import UserLesson
|
||||
|
||||
async def check_achievements(user_id, db: AsyncSession):
|
||||
"""Check and award new achievements"""
|
||||
achievements = (await db.execute(select(Achievement))).scalars().all()
|
||||
existing = (await db.execute(
|
||||
select(UserAchievement.achievement_id).where(UserAchievement.user_id == user_id)
|
||||
)).scalars().all()
|
||||
existing_ids = set(existing)
|
||||
|
||||
bet_count = (await db.execute(
|
||||
select(func.count()).select_from(Bet).where(Bet.user_id == user_id)
|
||||
)).scalar() or 0
|
||||
|
||||
lesson_count = (await db.execute(
|
||||
select(func.count()).select_from(UserLesson).where(UserLesson.user_id == user_id)
|
||||
)).scalar() or 0
|
||||
|
||||
new_badges = []
|
||||
for a in achievements:
|
||||
if a.id in existing_ids:
|
||||
continue
|
||||
awarded = False
|
||||
if a.requirement_type == "bets" and bet_count >= (a.requirement_value or 1):
|
||||
awarded = True
|
||||
elif a.requirement_type == "lessons" and lesson_count >= (a.requirement_value or 1):
|
||||
awarded = True
|
||||
|
||||
if awarded:
|
||||
ua = UserAchievement(user_id=user_id, achievement_id=a.id)
|
||||
db.add(ua)
|
||||
new_badges.append(a.name)
|
||||
|
||||
if new_badges:
|
||||
await db.commit()
|
||||
return new_badges
|
||||
79
backend/app/services/risk_engine.py
Normal file
79
backend/app/services/risk_engine.py
Normal file
@@ -0,0 +1,79 @@
|
||||
from datetime import datetime, timedelta
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select, func
|
||||
from app.models.bet import Bet
|
||||
from app.models.bankroll import Bankroll
|
||||
|
||||
async def calculate_risk_score(user_id, db: AsyncSession) -> dict:
|
||||
score = 0
|
||||
factors = []
|
||||
now = datetime.utcnow()
|
||||
|
||||
# 1. Horário noturno (22h-5h) +15
|
||||
hour = now.hour
|
||||
if hour >= 22 or hour < 5:
|
||||
score += 15
|
||||
factors.append("Apostando em horário noturno")
|
||||
|
||||
# 2. Valor crescente +25
|
||||
recent = await db.execute(
|
||||
select(Bet).where(Bet.user_id == user_id).order_by(Bet.created_at.desc()).limit(5)
|
||||
)
|
||||
recent_bets = recent.scalars().all()
|
||||
if len(recent_bets) >= 3:
|
||||
amounts = [float(b.amount) for b in recent_bets[:3]]
|
||||
if amounts[0] > amounts[1] > amounts[2]:
|
||||
score += 25
|
||||
factors.append("Valores de aposta crescentes")
|
||||
|
||||
# 3. Recuperação (aposta logo após loss) +30
|
||||
if len(recent_bets) >= 2:
|
||||
last = recent_bets[0]
|
||||
prev = recent_bets[1]
|
||||
if prev.result == "loss" and last.created_at and prev.created_at:
|
||||
diff = (last.created_at - prev.created_at).total_seconds()
|
||||
if diff < 1800: # 30 min
|
||||
score += 30
|
||||
factors.append("Tentativa de recuperação após perda")
|
||||
|
||||
# 4. Frequência alta (>5 apostas em 24h) +20
|
||||
day_ago = now - timedelta(hours=24)
|
||||
count_result = await db.execute(
|
||||
select(func.count()).select_from(Bet).where(Bet.user_id == user_id, Bet.created_at >= day_ago)
|
||||
)
|
||||
bet_count = count_result.scalar() or 0
|
||||
if bet_count > 5:
|
||||
score += 20
|
||||
factors.append(f"Alta frequência: {bet_count} apostas em 24h")
|
||||
|
||||
# 5. Limite estourado +25
|
||||
br_result = await db.execute(select(Bankroll).where(Bankroll.user_id == user_id))
|
||||
bankroll = br_result.scalar_one_or_none()
|
||||
if bankroll and float(bankroll.month_spent) > float(bankroll.monthly_budget):
|
||||
score += 25
|
||||
factors.append("Limite mensal ultrapassado")
|
||||
|
||||
# 6. Emoção negativa +15
|
||||
if recent_bets and recent_bets[0].emotion in ["😤", "😰", "tilt", "ansioso", "frustrado"]:
|
||||
score += 15
|
||||
factors.append("Emoção negativa detectada")
|
||||
|
||||
# 7. Sequência de perdas +20
|
||||
loss_streak = 0
|
||||
for b in recent_bets:
|
||||
if b.result == "loss":
|
||||
loss_streak += 1
|
||||
else:
|
||||
break
|
||||
if loss_streak >= 3:
|
||||
score += 20
|
||||
factors.append(f"Sequência de {loss_streak} perdas consecutivas")
|
||||
|
||||
if score <= 30:
|
||||
level = "green"
|
||||
elif score <= 60:
|
||||
level = "yellow"
|
||||
else:
|
||||
level = "red"
|
||||
|
||||
return {"score": score, "level": level, "factors": factors}
|
||||
Reference in New Issue
Block a user