diff --git a/backend/routers/musicians.py b/backend/routers/musicians.py index c327249..982b82a 100644 --- a/backend/routers/musicians.py +++ b/backend/routers/musicians.py @@ -60,6 +60,8 @@ def list_musicians( @router.get("/{slug}") def get_musician(slug: str, session: Session = Depends(get_session)): """Get musician details with band memberships and guest appearances""" + from models import Show, Song, Vertical + musician = session.exec(select(Musician).where(Musician.slug == slug)).first() if not musician: raise HTTPException(status_code=404, detail="Musician not found") @@ -70,8 +72,10 @@ def get_musician(slug: str, session: Session = Depends(get_session)): ).all() bands = [] + primary_band_ids = set() for m in memberships: artist = session.get(Artist, m.artist_id) + primary_band_ids.add(m.artist_id) bands.append({ "id": m.id, "artist_id": m.artist_id, @@ -80,20 +84,44 @@ def get_musician(slug: str, session: Session = Depends(get_session)): "role": m.role, "start_date": str(m.start_date) if m.start_date else None, "end_date": str(m.end_date) if m.end_date else None, + "is_current": m.end_date is None, }) - # Get guest appearances + # Get guest appearances with full context appearances = session.exec( select(PerformanceGuest).where(PerformanceGuest.musician_id == musician.id) ).all() guests = [] + sit_in_verticals = {} # Track which bands they've sat in with + for g in appearances: perf = session.get(Performance, g.performance_id) + if not perf: + continue + + show = session.get(Show, perf.show_id) if perf else None + song = session.get(Song, perf.song_id) if perf else None + vertical = session.get(Vertical, show.vertical_id) if show else None + + if vertical: + if vertical.id not in sit_in_verticals: + sit_in_verticals[vertical.id] = { + "vertical_id": vertical.id, + "vertical_name": vertical.name, + "vertical_slug": vertical.slug, + "count": 0 + } + sit_in_verticals[vertical.id]["count"] += 1 + guests.append({ "id": g.id, "performance_id": g.performance_id, "performance_slug": perf.slug if perf else None, + "song_title": song.title if song else None, + "show_date": str(show.date.date()) if show and show.date else None, + "vertical_name": vertical.name if vertical else None, + "vertical_slug": vertical.slug if vertical else None, "instrument": g.instrument, }) @@ -108,6 +136,13 @@ def get_musician(slug: str, session: Session = Depends(get_session)): }, "bands": bands, "guest_appearances": guests, + "sit_in_summary": list(sit_in_verticals.values()), + "stats": { + "total_bands": len(bands), + "current_bands": len([b for b in bands if b.get("is_current")]), + "total_sit_ins": len(guests), + "bands_sat_in_with": len(sit_in_verticals), + } } # --- Admin Endpoints (for now, no auth check - can be added later) --- diff --git a/backend/seed_musicians.py b/backend/seed_musicians.py new file mode 100644 index 0000000..2f57312 --- /dev/null +++ b/backend/seed_musicians.py @@ -0,0 +1,116 @@ +""" +Seed script to create common cross-band musicians. + +These are musicians known for sitting in with multiple bands. +""" + +from sqlmodel import Session, select +from database import engine +from models import Musician, Artist, BandMembership +import re + + +def generate_slug(name: str) -> str: + """Generate URL-safe slug""" + slug = name.lower() + slug = re.sub(r'[^\w\s-]', '', slug) + slug = re.sub(r'[\s_]+', '-', slug) + return slug.strip('-') + + +# Notable jam scene musicians who appear across bands +CROSS_BAND_MUSICIANS = [ + # Grateful Dead / Dead Family + {"name": "Bob Weir", "primary_instrument": "Guitar", "bands": ["grateful-dead", "dead-and-company"]}, + {"name": "Mickey Hart", "primary_instrument": "Drums", "bands": ["grateful-dead", "dead-and-company"]}, + {"name": "Bill Kreutzmann", "primary_instrument": "Drums", "bands": ["grateful-dead", "dead-and-company"]}, + {"name": "John Mayer", "primary_instrument": "Guitar", "bands": ["dead-and-company"]}, + {"name": "Oteil Burbridge", "primary_instrument": "Bass", "bands": ["dead-and-company"]}, + {"name": "Jeff Chimenti", "primary_instrument": "Keyboards", "bands": ["dead-and-company"]}, + + # Goose + {"name": "Rick Mitarotonda", "primary_instrument": "Guitar, Vocals", "bands": ["goose"]}, + {"name": "Peter Anspach", "primary_instrument": "Keyboards, Guitar", "bands": ["goose"]}, + {"name": "Trevor Weekz", "primary_instrument": "Bass", "bands": ["goose"]}, + {"name": "Ben Atkind", "primary_instrument": "Drums", "bands": ["goose"]}, + {"name": "Jeff Arevalo", "primary_instrument": "Percussion", "bands": ["goose"]}, + + # Phish + {"name": "Trey Anastasio", "primary_instrument": "Guitar", "bands": ["phish"]}, + {"name": "Page McConnell", "primary_instrument": "Keyboards", "bands": ["phish"]}, + {"name": "Mike Gordon", "primary_instrument": "Bass", "bands": ["phish"]}, + {"name": "Jon Fishman", "primary_instrument": "Drums", "bands": ["phish"]}, + + # Billy Strings + {"name": "Billy Strings", "primary_instrument": "Guitar, Vocals", "bands": ["billy-strings"]}, + {"name": "Billy Failing", "primary_instrument": "Banjo", "bands": ["billy-strings"]}, + {"name": "Royal Masat", "primary_instrument": "Bass", "bands": ["billy-strings"]}, + {"name": "Jarrod Walker", "primary_instrument": "Mandolin", "bands": ["billy-strings"]}, + + # Cross-band sit-in regulars + {"name": "Marcus King", "primary_instrument": "Guitar, Vocals", "bands": []}, + {"name": "Pigeons Playing Ping Pong", "primary_instrument": "Funk", "bands": []}, + {"name": "Karina Rykman", "primary_instrument": "Bass, Vocals", "bands": []}, +] + + +def seed_musicians(): + """Create musicians if they don't exist""" + print("Seeding musicians...\n") + + with Session(engine) as session: + created = 0 + + for m in CROSS_BAND_MUSICIANS: + slug = generate_slug(m["name"]) + + existing = session.exec( + select(Musician).where(Musician.slug == slug) + ).first() + + if existing: + print(f" Exists: {m['name']}") + musician = existing + else: + musician = Musician( + name=m["name"], + slug=slug, + primary_instrument=m["primary_instrument"] + ) + session.add(musician) + session.commit() + session.refresh(musician) + created += 1 + print(f" Created: {m['name']}") + + # Create band memberships + for band_slug in m.get("bands", []): + # Find vertical by slug to get artist + from models import Vertical + vertical = session.exec( + select(Vertical).where(Vertical.slug == band_slug) + ).first() + + if vertical and vertical.primary_artist_id: + # Check if membership exists + existing_membership = session.exec( + select(BandMembership) + .where(BandMembership.musician_id == musician.id) + .where(BandMembership.artist_id == vertical.primary_artist_id) + ).first() + + if not existing_membership: + membership = BandMembership( + musician_id=musician.id, + artist_id=vertical.primary_artist_id, + role=m["primary_instrument"] + ) + session.add(membership) + print(f" -> Added to {band_slug}") + + session.commit() + print(f"\nCreated {created} new musicians") + + +if __name__ == "__main__": + seed_musicians()