Event layout page update.

This commit is contained in:
2026-01-11 15:21:17 -06:00
parent e0af183086
commit e50b2f31d3
13 changed files with 1460 additions and 183 deletions

436
backend/simulate_activity.py Executable file
View File

@ -0,0 +1,436 @@
#!/usr/bin/env python3
"""
Activity Simulation Script
Simulates random user activity including:
- New user registrations
- Creating spread bets
- Taking/matching bets
- Adding comments to events and matches
- Cancelling bets
Usage:
python simulate_activity.py [--iterations N] [--delay SECONDS] [--continuous]
"""
import asyncio
import argparse
import random
from decimal import Decimal
from datetime import datetime
from sqlalchemy import select, and_
from sqlalchemy.orm import selectinload
from app.database import async_session, init_db
from app.models import (
User, Wallet, SportEvent, SpreadBet, EventComment, MatchComment,
EventStatus, SpreadBetStatus, TeamSide, Transaction, TransactionType, TransactionStatus
)
from app.utils.security import get_password_hash
# Sample data for generating random users
FIRST_NAMES = [
"James", "Emma", "Liam", "Olivia", "Noah", "Ava", "Oliver", "Sophia",
"Elijah", "Isabella", "Lucas", "Mia", "Mason", "Charlotte", "Ethan",
"Amelia", "Alexander", "Harper", "Henry", "Evelyn", "Sebastian", "Luna",
"Jack", "Camila", "Aiden", "Gianna", "Owen", "Abigail", "Samuel", "Ella",
"Ryan", "Scarlett", "Nathan", "Emily", "Caleb", "Elizabeth", "Hunter",
"Sofia", "Christian", "Avery", "Landon", "Chloe", "Jonathan", "Victoria"
]
LAST_NAMES = [
"Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller",
"Davis", "Rodriguez", "Martinez", "Hernandez", "Lopez", "Gonzalez",
"Wilson", "Anderson", "Thomas", "Taylor", "Moore", "Jackson", "Martin",
"Lee", "Perez", "Thompson", "White", "Harris", "Sanchez", "Clark",
"Ramirez", "Lewis", "Robinson", "Walker", "Young", "Allen", "King"
]
# Sample comments for events
EVENT_COMMENTS = [
"This is going to be a great game!",
"Home team looking strong this season",
"I'm betting on the underdog here",
"What do you all think about the spread?",
"Last time these teams played it was close",
"Weather might be a factor today",
"Key player is out, that changes everything",
"The odds seem off to me",
"Anyone else feeling bullish on the away team?",
"This matchup is always entertaining",
"Line moved a lot overnight",
"Sharp money coming in on the home side",
"Public is heavy on the favorite",
"Value play on the underdog here",
"Injury report looking concerning",
"Home field advantage is huge here",
"Expecting a low-scoring affair",
"Over/under seems too high",
"Rivalry game, throw out the records!",
"Coach has a great record against this opponent"
]
# Sample comments for matches (between two bettors)
MATCH_COMMENTS = [
"Good luck!",
"May the best bettor win",
"This should be interesting",
"I'm feeling confident about this one",
"Let's see how this plays out",
"Nice bet, looking forward to the game",
"GL HF",
"First time betting against you",
"Rematch from last week!",
"I've been waiting for this matchup",
"Your team doesn't stand a chance!",
"We'll see about that...",
"Close game incoming",
"I'll be watching every play",
"Don't count your winnings yet!"
]
async def create_random_user(db) -> User | None:
"""Create a new random user with a wallet."""
first = random.choice(FIRST_NAMES)
last = random.choice(LAST_NAMES)
# Generate unique username
suffix = random.randint(100, 9999)
username = f"{first.lower()}{last.lower()}{suffix}"
email = f"{username}@example.com"
# Check if user already exists
existing = await db.execute(
select(User).where(User.username == username)
)
if existing.scalar_one_or_none():
return None
user = User(
email=email,
username=username,
password_hash=get_password_hash("password123"),
display_name=f"{first} {last}"
)
db.add(user)
await db.flush()
# Create wallet with random starting balance
starting_balance = Decimal(str(random.randint(500, 5000)))
wallet = Wallet(
user_id=user.id,
balance=starting_balance,
escrow=Decimal("0.00")
)
db.add(wallet)
await db.commit()
print(f" [USER] Created user: {user.username} with ${starting_balance} balance")
return user
async def create_random_bet(db, users: list[User], events: list[SportEvent]) -> SpreadBet | None:
"""Create a random spread bet on an event."""
if not users or not events:
return None
# Filter for users with sufficient balance
users_with_balance = []
for user in users:
wallet_result = await db.execute(
select(Wallet).where(Wallet.user_id == user.id)
)
wallet = wallet_result.scalar_one_or_none()
if wallet and wallet.balance >= Decimal("10"):
users_with_balance.append((user, wallet))
if not users_with_balance:
return None
user, wallet = random.choice(users_with_balance)
event = random.choice(events)
# Random spread within event range
spread = round(random.uniform(event.min_spread, event.max_spread) * 2) / 2 # Round to 0.5
# Random stake (10-50% of balance, max $500)
max_stake = min(float(wallet.balance) * 0.5, 500)
stake = Decimal(str(round(random.uniform(10, max(10, max_stake)), 2)))
# Random team selection
team = random.choice([TeamSide.HOME, TeamSide.AWAY])
bet = SpreadBet(
event_id=event.id,
spread=spread,
team=team,
creator_id=user.id,
stake_amount=stake,
house_commission_percent=Decimal("10.00"),
status=SpreadBetStatus.OPEN
)
db.add(bet)
await db.commit()
team_name = event.home_team if team == TeamSide.HOME else event.away_team
print(f" [BET] {user.username} created ${stake} bet on {team_name} {'+' if spread > 0 else ''}{spread}")
return bet
async def take_random_bet(db, users: list[User]) -> SpreadBet | None:
"""Have a random user take an open bet."""
# Get open bets
result = await db.execute(
select(SpreadBet)
.options(selectinload(SpreadBet.event), selectinload(SpreadBet.creator))
.where(SpreadBet.status == SpreadBetStatus.OPEN)
)
open_bets = result.scalars().all()
if not open_bets:
return None
bet = random.choice(open_bets)
# Find eligible takers (not creator, has balance)
eligible_takers = []
for user in users:
if user.id == bet.creator_id:
continue
wallet_result = await db.execute(
select(Wallet).where(Wallet.user_id == user.id)
)
wallet = wallet_result.scalar_one_or_none()
if wallet and wallet.balance >= bet.stake_amount:
eligible_takers.append((user, wallet))
if not eligible_takers:
return None
taker, taker_wallet = random.choice(eligible_takers)
# Get creator's wallet
creator_wallet_result = await db.execute(
select(Wallet).where(Wallet.user_id == bet.creator_id)
)
creator_wallet = creator_wallet_result.scalar_one_or_none()
if not creator_wallet or creator_wallet.balance < bet.stake_amount:
return None
# Lock funds for both parties
creator_wallet.balance -= bet.stake_amount
creator_wallet.escrow += bet.stake_amount
taker_wallet.balance -= bet.stake_amount
taker_wallet.escrow += bet.stake_amount
# Create transactions
creator_tx = Transaction(
user_id=bet.creator_id,
wallet_id=creator_wallet.id,
type=TransactionType.ESCROW_LOCK,
amount=-bet.stake_amount,
balance_after=creator_wallet.balance,
reference_id=bet.id,
description=f"Escrow lock for spread bet #{bet.id}",
status=TransactionStatus.COMPLETED
)
taker_tx = Transaction(
user_id=taker.id,
wallet_id=taker_wallet.id,
type=TransactionType.ESCROW_LOCK,
amount=-bet.stake_amount,
balance_after=taker_wallet.balance,
reference_id=bet.id,
description=f"Escrow lock for spread bet #{bet.id}",
status=TransactionStatus.COMPLETED
)
db.add(creator_tx)
db.add(taker_tx)
# Update bet
bet.taker_id = taker.id
bet.status = SpreadBetStatus.MATCHED
bet.matched_at = datetime.utcnow()
await db.commit()
print(f" [MATCH] {taker.username} took {bet.creator.username}'s ${bet.stake_amount} bet")
return bet
async def cancel_random_bet(db) -> SpreadBet | None:
"""Cancel a random open bet."""
result = await db.execute(
select(SpreadBet)
.options(selectinload(SpreadBet.creator))
.where(SpreadBet.status == SpreadBetStatus.OPEN)
)
open_bets = result.scalars().all()
if not open_bets:
return None
# 20% chance to cancel
if random.random() > 0.2:
return None
bet = random.choice(open_bets)
bet.status = SpreadBetStatus.CANCELLED
await db.commit()
print(f" [CANCEL] {bet.creator.username} cancelled their ${bet.stake_amount} bet")
return bet
async def add_event_comment(db, users: list[User], events: list[SportEvent]) -> EventComment | None:
"""Add a random comment to an event."""
if not users or not events:
return None
user = random.choice(users)
event = random.choice(events)
content = random.choice(EVENT_COMMENTS)
comment = EventComment(
event_id=event.id,
user_id=user.id,
content=content
)
db.add(comment)
await db.commit()
print(f" [COMMENT] {user.username} on {event.home_team} vs {event.away_team}: \"{content[:40]}...\"")
return comment
async def add_match_comment(db, users: list[User]) -> MatchComment | None:
"""Add a random comment to a matched bet."""
# Get matched bets
result = await db.execute(
select(SpreadBet)
.options(selectinload(SpreadBet.creator), selectinload(SpreadBet.taker))
.where(SpreadBet.status == SpreadBetStatus.MATCHED)
)
matched_bets = result.scalars().all()
if not matched_bets:
return None
bet = random.choice(matched_bets)
# Comment from either creator or taker
user = random.choice([bet.creator, bet.taker])
content = random.choice(MATCH_COMMENTS)
comment = MatchComment(
spread_bet_id=bet.id,
user_id=user.id,
content=content
)
db.add(comment)
await db.commit()
print(f" [CHAT] {user.username} in match #{bet.id}: \"{content}\"")
return comment
async def run_simulation(iterations: int = 10, delay: float = 2.0, continuous: bool = False):
"""Run the activity simulation."""
print("=" * 60)
print("H2H Activity Simulator")
print("=" * 60)
await init_db()
iteration = 0
while continuous or iteration < iterations:
iteration += 1
print(f"\n--- Iteration {iteration} ---")
async with async_session() as db:
# Get existing users and events
users_result = await db.execute(select(User))
users = list(users_result.scalars().all())
events_result = await db.execute(
select(SportEvent).where(SportEvent.status == EventStatus.UPCOMING)
)
events = list(events_result.scalars().all())
if not events:
print(" No upcoming events found. Run manage_events.py first.")
if not continuous:
break
await asyncio.sleep(delay)
continue
# Random actions with weighted probabilities
actions = [
(create_random_user, 0.15), # 15% - Create user
(lambda db: create_random_bet(db, users, events), 0.30), # 30% - Create bet
(lambda db: take_random_bet(db, users), 0.20), # 20% - Take bet
(lambda db: cancel_random_bet(db), 0.05), # 5% - Cancel bet
(lambda db: add_event_comment(db, users, events), 0.15), # 15% - Event comment
(lambda db: add_match_comment(db, users), 0.15), # 15% - Match comment
]
# Perform 1-3 random actions per iteration
num_actions = random.randint(1, 3)
for _ in range(num_actions):
# Weighted random selection
rand = random.random()
cumulative = 0
for action_fn, probability in actions:
cumulative += probability
if rand <= cumulative:
try:
if action_fn == create_random_user:
await action_fn(db)
else:
await action_fn(db)
except Exception as e:
print(f" [ERROR] {e}")
break
await asyncio.sleep(delay)
print("\n" + "=" * 60)
print("Simulation complete!")
print("=" * 60)
def main():
parser = argparse.ArgumentParser(description="Simulate H2H platform activity")
parser.add_argument(
"--iterations", "-n",
type=int,
default=10,
help="Number of simulation iterations (default: 10)"
)
parser.add_argument(
"--delay", "-d",
type=float,
default=2.0,
help="Delay between iterations in seconds (default: 2.0)"
)
parser.add_argument(
"--continuous", "-c",
action="store_true",
help="Run continuously until interrupted"
)
args = parser.parse_args()
try:
asyncio.run(run_simulation(
iterations=args.iterations,
delay=args.delay,
continuous=args.continuous
))
except KeyboardInterrupt:
print("\n\nSimulation stopped by user.")
if __name__ == "__main__":
main()