399 lines
20 KiB
Python
399 lines
20 KiB
Python
"""
|
|
El Goose Activity Seeder (Enhanced)
|
|
Populates the demo environment with 36 distinct user personas and realistic activity.
|
|
Generates attendance, ratings, reviews (Shows, Venues, Tours), and comments.
|
|
Includes "Wiki-Style" linking logic [[Entity:ID|Label]].
|
|
"""
|
|
import random
|
|
import re
|
|
from datetime import datetime, timedelta
|
|
from sqlmodel import Session, select
|
|
from database import engine
|
|
from models import User, UserPreferences, Show, Song, Venue, Performance, Review, Attendance, Comment, Tour, Profile
|
|
from passlib.context import CryptContext
|
|
|
|
pwd_context = CryptContext(schemes=["argon2"], deprecated="auto")
|
|
|
|
# --- 1. Define 36 Distinct Personas ---
|
|
PERSONAS = [
|
|
# Original 12
|
|
{"username": "TheArchivist", "email": "archivist@demo.com", "role": "user", "style": "factual", "bio": "Tracking every tease since 2016."},
|
|
{"username": "StatNerd420", "email": "statnerd@demo.com", "role": "user", "style": "analytical", "bio": "It's all about the gap times."},
|
|
{"username": "CriticalListener", "email": "reviewer@demo.com", "role": "user", "style": "critical", "bio": "Honest takes only. 3.5 stars is a good rating."},
|
|
{"username": "CasualFan", "email": "casual@demo.com", "role": "user", "style": "casual", "bio": "Just here for a good time."},
|
|
{"username": "NortheastHonkers", "email": "groupleader@demo.com", "role": "user", "style": "community", "bio": "Rep the Northeast flock!"},
|
|
{"username": "ModGoose", "email": "mod@demo.com", "role": "moderator", "style": "official", "bio": "Play nice in the comments."},
|
|
{"username": "AdminBird", "email": "admin@demo.com", "role": "admin", "style": "official", "bio": "System Admin."},
|
|
{"username": "NewToGoose", "email": "newbie@demo.com", "role": "user", "style": "excited_noob", "bio": "Just discovered them last week!"},
|
|
{"username": "TaperTom", "email": "taper@demo.com", "role": "user", "style": "technical", "bio": "MK4 > V3 > 24/96"},
|
|
{"username": "RoadWarrior", "email": "tourfollower@demo.com", "role": "user", "style": "traveler", "bio": "100+ shows and counting."},
|
|
{"username": "SilentHonker", "email": "lurker@demo.com", "role": "user", "style": "quiet", "bio": "..."},
|
|
{"username": "HypeGoose", "email": "hype@demo.com", "role": "user", "style": "hype", "bio": "BEST BAND EVER LFG!!!"},
|
|
|
|
# New 24
|
|
{"username": "VinylJunkie", "email": "vinyl@demo.com", "role": "user", "style": "collector", "bio": "Spinning wax only. ISO Shenanigans variants."},
|
|
{"username": "CouchTourCapt", "email": "couch@demo.com", "role": "user", "style": "streamer", "bio": "Streaming from the living room every night."},
|
|
{"username": "RailRider", "email": "rail@demo.com", "role": "user", "style": "intense", "bio": "If you're not on the rail, were you even there?"},
|
|
{"username": "PosterNutbag", "email": "poster@demo.com", "role": "user", "style": "collector", "bio": "Here for the foil prints."},
|
|
{"username": "SetlistPredictor", "email": "predict@demo.com", "role": "user", "style": "analytical", "bio": "Called the opener 3 nights in a row."},
|
|
{"username": "JamFlowMan", "email": "jam@demo.com", "role": "user", "style": "vibey", "bio": "Surrender to the flow."},
|
|
{"username": "TedHead", "email": "ted@demo.com", "role": "user", "style": "insider", "bio": "Ted Tapes or bust."},
|
|
{"username": "HonkIfUrHorny", "email": "honk@demo.com", "role": "user", "style": "meme", "bio": "HONK HONK"},
|
|
{"username": "PeterSide", "email": "peter@demo.com", "role": "user", "style": "fanboy", "bio": "Mustache appreciation society."},
|
|
{"username": "RicksPick", "email": "rick@demo.com", "role": "user", "style": "fanboy", "bio": "That tone though."},
|
|
{"username": "TrevorBassFace", "email": "trevor@demo.com", "role": "user", "style": "fanboy", "bio": "T-Bone holds it down."},
|
|
{"username": "SpudsMcKenzie", "email": "spuds@demo.com", "role": "user", "style": "fanboy", "bio": "Drums > Space"},
|
|
{"username": "JeffsPercussion", "email": "jeff@demo.com", "role": "user", "style": "fanboy", "bio": "More gong please."},
|
|
{"username": "CotterPin", "email": "cotter@demo.com", "role": "user", "style": "fanboy", "bio": "New era is here."},
|
|
{"username": "OreboloFan", "email": "acoustic@demo.com", "role": "user", "style": "acoustic", "bio": "Acoustic sets are superior."},
|
|
{"username": "IndieGroove", "email": "indie@demo.com", "role": "user", "style": "hipster", "bio": "I liked them when they played bars."},
|
|
{"username": "PhishConvert", "email": "phish@demo.com", "role": "user", "style": "comparative", "bio": "3.0 vet giving this a shot."},
|
|
{"username": "DeadheadConvert", "email": "dead@demo.com", "role": "user", "style": "old_school", "bio": "Searching for the sound."},
|
|
{"username": "DiscoGoose", "email": "disco@demo.com", "role": "user", "style": "dancer", "bio": "Here to dance."},
|
|
{"username": "ProgRockFan", "email": "prog@demo.com", "role": "user", "style": "technical", "bio": "Time signatures are my love language."},
|
|
{"username": "FunkyUncle", "email": "funky@demo.com", "role": "user", "style": "vibey", "bio": "Keep it funky."},
|
|
{"username": "WysteriaLocal", "email": "local@demo.com", "role": "user", "style": "local", "bio": "CT born and raised."},
|
|
{"username": "FactoryFiction", "email": "factory@demo.com", "role": "user", "style": "intense", "bio": "Chasing that Factory Fiction."},
|
|
{"username": "Shenanigans", "email": "squad@demo.com", "role": "user", "style": "party", "bio": "Squad up!"},
|
|
]
|
|
|
|
# --- 2. Content Generation Templates ---
|
|
|
|
SHOW_REVIEWS = {
|
|
"hype": [
|
|
"ABSOLUTELY INSANE SHOW! That [[Song:{song_id}|{song}]] jam changed my life.",
|
|
"Can we talk about [[Song:{song_id}|{song}]]?! [[Venue:{venue_id}|{venue}]] was shaking!",
|
|
"Best show of the tour hands down. The energy during [[Song:{song_id}|{song}]] was unreal.",
|
|
"LFG!!! [[Song:{song_id}|{song}]] > [[Song:{song2_id}|{song2}]] was the highlight for me.",
|
|
"I have no words. [[Song:{song_id}|{song}]] was a heater."
|
|
],
|
|
"critical": [
|
|
"Solid show, but the mix was a bit muddy during [[Song:{song_id}|{song}]].",
|
|
"First set was slow, but they picked it up with [[Song:{song_id}|{song}]] in the second.",
|
|
"Standard version of [[Song:{song_id}|{song}]], nothing special. [[Song:{song2_id}|{song2}]] was the improvisational peak.",
|
|
"Good energy at [[Venue:{venue_id}|{venue}]], but I've heard better versions of [[Song:{song_id}|{song}]].",
|
|
"3.5/5. [[Song:{song_id}|{song}]] went deep but didn't quite stick the landing."
|
|
],
|
|
"analytical": [
|
|
"First time [[Song:{song_id}|{song}]] has opened a second set since 2019.",
|
|
"The jam in [[Song:{song_id}|{song}]] clocked in at 22 minutes. Major key modulation at the 12-minute mark.",
|
|
"Interesting placement for [[Song:{song_id}|{song}]]. [[Venue:{venue_id}|{venue}]] always gets unique setlists.",
|
|
"Gap bust! Haven't played [[Song:{song_id}|{song}]] in 45 shows.",
|
|
"Statistically an above-average show. [[Song:{song_id}|{song}]] jam density was high."
|
|
],
|
|
"casual": [
|
|
"Had a blast at [[Venue:{venue_id}|{venue}]]! [[Song:{song_id}|{song}]] was my favorite.",
|
|
"Great vibes tonight. Loved hearing [[Song:{song_id}|{song}]].",
|
|
"So much fun dancing to [[Song:{song_id}|{song}]]. Can't wait for the next one.",
|
|
"Took my friend to their first show and they loved [[Song:{song_id}|{song}]].",
|
|
"Good times. [[Song:{song_id}|{song}]] was cool."
|
|
],
|
|
"technical": [
|
|
"Rick's tone on [[Song:{song_id}|{song}]] was dialed in perfectly.",
|
|
"Peter's clav work on [[Song:{song_id}|{song}]] was funky as hell.",
|
|
"The lighting rig looked amazing during [[Song:{song_id}|{song}]].",
|
|
"Trevor was locked in for [[Song:{song_id}|{song}]]. Great low end.",
|
|
"Spuds and Jeff were driving the bus on [[Song:{song_id}|{song}]]."
|
|
],
|
|
"comparative": [
|
|
"Getting major 97 Phish vibes from that [[Song:{song_id}|{song}]] jam.",
|
|
"That [[Song:{song_id}|{song}]] transition reminded me of a Dead segue.",
|
|
"This version of [[Song:{song_id}|{song}]] rivals the one from last month.",
|
|
"They are really finding their own sound on [[Song:{song_id}|{song}]].",
|
|
"Better than the studio version of [[Song:{song_id}|{song}]] for sure."
|
|
]
|
|
}
|
|
|
|
VENUE_REVIEWS = {
|
|
"hype": [
|
|
"[[Venue:{venue_id}|{venue}]] IS THE CHURCH! Best place to see them.",
|
|
"The energy in this room is unmatched. I love [[Venue:{venue_id}|{venue}]]!",
|
|
"Always a party at [[Venue:{venue_id}|{venue}]]. Can't wait to go back."
|
|
],
|
|
"critical": [
|
|
"Sightlines at [[Venue:{venue_id}|{venue}]] are terrible if you're not on the floor.",
|
|
"Security at [[Venue:{venue_id}|{venue}]] was a nightmare. Great show otherwise.",
|
|
"Sound was bouncy in the balcony. [[Venue:{venue_id}|{venue}]] needs acoustic treatment."
|
|
],
|
|
"casual": [
|
|
"Nice venue, easy parking. [[Venue:{venue_id}|{venue}]] is a solid spot.",
|
|
"Had a good time at [[Venue:{venue_id}|{venue}]]. Beers were expensive though.",
|
|
"Beautiful theatre. [[Venue:{venue_id}|{venue}]] has great architecture."
|
|
],
|
|
"traveler": [
|
|
"Drove 6 hours to get to [[Venue:{venue_id}|{venue}]]. Worth it.",
|
|
"Checked [[Venue:{venue_id}|{venue}]] off my bucket list. 10/10.",
|
|
"One of my favorite stops on tour. [[Venue:{venue_id}|{venue}]] never disappoints."
|
|
]
|
|
}
|
|
|
|
TOUR_REVIEWS = {
|
|
"hype": [
|
|
"[[Tour:{tour_id}|{tour}]] was their best tour yet! Every night was fire.",
|
|
"They leveled up on [[Tour:{tour_id}|{tour}]]. New songs are sounding great.",
|
|
"Take me back to [[Tour:{tour_id}|{tour}]]! What a run."
|
|
],
|
|
"analytical": [
|
|
"[[Tour:{tour_id}|{tour}]] featured the most 20+ minute jams of any era.",
|
|
"Setlist variety on [[Tour:{tour_id}|{tour}]] was at an all-time high.",
|
|
"Interesting evolution of the sound during [[Tour:{tour_id}|{tour}]]."
|
|
],
|
|
"critical": [
|
|
"[[Tour:{tour_id}|{tour}]] started strong but fizzled out at the end.",
|
|
"Too many repeats on [[Tour:{tour_id}|{tour}]]. Need to dig deeper.",
|
|
"Not my favorite era. [[Tour:{tour_id}|{tour}]] felt a bit disjointed."
|
|
]
|
|
}
|
|
|
|
# --- 3. Helper Functions ---
|
|
|
|
def get_random_show_review(style, song1, song2, venue):
|
|
"""Generate a show review with wiki links"""
|
|
templates = SHOW_REVIEWS.get(style, SHOW_REVIEWS["casual"])
|
|
# Map specific styles to generic categories
|
|
if style in ["excited_noob", "intense", "party", "fanboy"]: templates = SHOW_REVIEWS["hype"]
|
|
elif style in ["factual", "technical", "collector"]: templates = SHOW_REVIEWS["technical"]
|
|
elif style in ["old_school", "hipster"]: templates = SHOW_REVIEWS["comparative"]
|
|
elif style in ["quiet", "streamer"]: templates = SHOW_REVIEWS["casual"]
|
|
elif style in ["analytical", "insider"]: templates = SHOW_REVIEWS["analytical"]
|
|
|
|
template = random.choice(templates)
|
|
return template.format(
|
|
song=song1.title, song_id=song1.id,
|
|
song2=song2.title, song2_id=song2.id,
|
|
venue=venue.name, venue_id=venue.id
|
|
)
|
|
|
|
def get_random_venue_review(style, venue):
|
|
"""Generate a venue review with wiki links"""
|
|
templates = VENUE_REVIEWS.get(style, VENUE_REVIEWS["casual"])
|
|
if style in ["excited_noob", "intense", "party"]: templates = VENUE_REVIEWS["hype"]
|
|
elif style in ["critical", "technical"]: templates = VENUE_REVIEWS["critical"]
|
|
elif style in ["traveler", "collector"]: templates = VENUE_REVIEWS["traveler"]
|
|
|
|
template = random.choice(templates)
|
|
return template.format(venue=venue.name, venue_id=venue.id)
|
|
|
|
def get_random_tour_review(style, tour):
|
|
"""Generate a tour review with wiki links"""
|
|
templates = TOUR_REVIEWS.get(style, TOUR_REVIEWS["analytical"])
|
|
if style in ["excited_noob", "intense", "party"]: templates = TOUR_REVIEWS["hype"]
|
|
elif style in ["critical", "old_school"]: templates = TOUR_REVIEWS["critical"]
|
|
|
|
template = random.choice(templates)
|
|
return template.format(tour=tour.name, tour_id=tour.id)
|
|
|
|
def seed_users(session):
|
|
"""Create all 36 users if they don't exist"""
|
|
print("👥 Seeding Users...")
|
|
users = {}
|
|
for p in PERSONAS:
|
|
user = session.exec(select(User).where(User.email == p["email"])).first()
|
|
if not user:
|
|
user = User(
|
|
email=p["email"],
|
|
hashed_password=pwd_context.hash("demo123"),
|
|
role=p["role"],
|
|
bio=p["bio"],
|
|
is_active=True
|
|
)
|
|
session.add(user)
|
|
session.commit()
|
|
session.refresh(user)
|
|
|
|
# Create Profile
|
|
profile = Profile(
|
|
user_id=user.id,
|
|
username=p["username"],
|
|
display_name=p["username"]
|
|
)
|
|
session.add(profile)
|
|
|
|
# Create preferences
|
|
prefs = UserPreferences(
|
|
user_id=user.id,
|
|
wiki_mode=(p["style"] == "factual" or p["style"] == "quiet"),
|
|
show_ratings=True,
|
|
show_comments=True
|
|
)
|
|
session.add(prefs)
|
|
session.commit()
|
|
print(f" + Created {p['username']}")
|
|
else:
|
|
# Update bio if missing
|
|
if not user.bio:
|
|
user.bio = p["bio"]
|
|
session.add(user)
|
|
session.commit()
|
|
|
|
# Ensure profile exists
|
|
profile = session.exec(select(Profile).where(Profile.user_id == user.id)).first()
|
|
if not profile:
|
|
profile = Profile(
|
|
user_id=user.id,
|
|
username=p["username"],
|
|
display_name=p["username"]
|
|
)
|
|
session.add(profile)
|
|
session.commit()
|
|
|
|
users[p["username"]] = user
|
|
return users
|
|
|
|
def seed_activity(session, users):
|
|
"""Generate attendance, reviews, and ratings"""
|
|
print("\n🎲 Generating Activity...")
|
|
|
|
# Fetch data
|
|
shows = session.exec(select(Show).order_by(Show.date.desc())).all()
|
|
venues = session.exec(select(Venue)).all()
|
|
tours = session.exec(select(Tour)).all()
|
|
|
|
if not shows:
|
|
print("❌ No shows found! Run import_elgoose.py first.")
|
|
return
|
|
|
|
print(f" Found {len(shows)} shows, {len(venues)} venues, {len(tours)} tours.")
|
|
|
|
count_attendance = 0
|
|
count_reviews = 0
|
|
|
|
# 1. Show Activity (Attendance + Reviews)
|
|
target_shows = shows[:50] + random.sample(shows[50:], min(len(shows)-50, 50))
|
|
|
|
for show in target_shows:
|
|
venue = session.get(Venue, show.venue_id)
|
|
if not venue: continue
|
|
|
|
performances = session.exec(select(Performance).where(Performance.show_id == show.id)).all()
|
|
if not performances: continue
|
|
|
|
songs = [session.get(Song, p.song_id) for p in performances]
|
|
if not songs: continue
|
|
|
|
attendees = random.sample(list(users.values()), k=random.randint(3, 15))
|
|
|
|
for user in attendees:
|
|
# Attendance
|
|
exists = session.exec(select(Attendance).where(
|
|
Attendance.user_id == user.id,
|
|
Attendance.show_id == show.id
|
|
)).first()
|
|
|
|
if not exists:
|
|
att = Attendance(
|
|
user_id=user.id,
|
|
show_id=show.id,
|
|
created_at=show.date + timedelta(days=random.randint(1, 5))
|
|
)
|
|
session.add(att)
|
|
count_attendance += 1
|
|
|
|
# Show Review (30% chance)
|
|
if random.random() < 0.3:
|
|
persona = next((p for p in PERSONAS if p["email"] == user.email), None)
|
|
style = persona["style"] if persona else "casual"
|
|
|
|
song1 = random.choice(songs)
|
|
song2 = random.choice(songs)
|
|
|
|
review_text = get_random_show_review(style, song1, song2, venue)
|
|
|
|
rating = random.randint(3, 5)
|
|
if style == "critical": rating = random.randint(2, 4)
|
|
if style == "hype": rating = 5
|
|
|
|
rev_exists = session.exec(select(Review).where(
|
|
Review.user_id == user.id,
|
|
Review.show_id == show.id
|
|
)).first()
|
|
|
|
if not rev_exists:
|
|
blurb = review_text.split('.')[0] + "."
|
|
if len(blurb) > 100: blurb = blurb[:97] + "..."
|
|
|
|
review = Review(
|
|
user_id=user.id,
|
|
show_id=show.id,
|
|
score=rating,
|
|
content=review_text,
|
|
blurb=blurb,
|
|
created_at=show.date + timedelta(days=random.randint(1, 10))
|
|
)
|
|
session.add(review)
|
|
count_reviews += 1
|
|
|
|
# 2. Venue Reviews
|
|
target_venues = random.sample(venues, min(len(venues), 30))
|
|
for venue in target_venues:
|
|
reviewers = random.sample(list(users.values()), k=random.randint(1, 5))
|
|
for user in reviewers:
|
|
persona = next((p for p in PERSONAS if p["email"] == user.email), None)
|
|
style = persona["style"] if persona else "casual"
|
|
|
|
review_text = get_random_venue_review(style, venue)
|
|
rating = random.randint(3, 5)
|
|
|
|
rev_exists = session.exec(select(Review).where(
|
|
Review.user_id == user.id,
|
|
Review.venue_id == venue.id
|
|
)).first()
|
|
|
|
if not rev_exists:
|
|
blurb = review_text.split('.')[0] + "."
|
|
if len(blurb) > 100: blurb = blurb[:97] + "..."
|
|
|
|
review = Review(
|
|
user_id=user.id,
|
|
venue_id=venue.id,
|
|
score=rating,
|
|
content=review_text,
|
|
blurb=blurb,
|
|
created_at=datetime.utcnow() - timedelta(days=random.randint(1, 365))
|
|
)
|
|
session.add(review)
|
|
count_reviews += 1
|
|
|
|
# 3. Tour Reviews
|
|
target_tours = random.sample(tours, min(len(tours), 10))
|
|
for tour in target_tours:
|
|
reviewers = random.sample(list(users.values()), k=random.randint(1, 3))
|
|
for user in reviewers:
|
|
persona = next((p for p in PERSONAS if p["email"] == user.email), None)
|
|
style = persona["style"] if persona else "casual"
|
|
|
|
review_text = get_random_tour_review(style, tour)
|
|
rating = random.randint(3, 5)
|
|
|
|
rev_exists = session.exec(select(Review).where(
|
|
Review.user_id == user.id,
|
|
Review.tour_id == tour.id
|
|
)).first()
|
|
|
|
if not rev_exists:
|
|
blurb = review_text.split('.')[0] + "."
|
|
if len(blurb) > 100: blurb = blurb[:97] + "..."
|
|
|
|
review = Review(
|
|
user_id=user.id,
|
|
tour_id=tour.id,
|
|
score=rating,
|
|
content=review_text,
|
|
blurb=blurb,
|
|
created_at=datetime.utcnow() - timedelta(days=random.randint(1, 365))
|
|
)
|
|
session.add(review)
|
|
count_reviews += 1
|
|
|
|
session.commit()
|
|
print(f"\n✅ Activity Generation Complete!")
|
|
print(f" + {count_attendance} Attendance records")
|
|
print(f" + {count_reviews} Reviews generated (Shows, Venues, Tours)")
|
|
|
|
def main():
|
|
with Session(engine) as session:
|
|
users_map = seed_users(session)
|
|
seed_activity(session, users_map)
|
|
|
|
if __name__ == "__main__":
|
|
main()
|