feat: Enhance musician API with cross-band sit-in tracking
Some checks failed
Deploy Fediversion / deploy (push) Failing after 1s

- Add sit_in_summary to GET /musicians/{slug}
- Add stats: total_bands, current_bands, total_sit_ins
- Include song_title, show_date, vertical in guest appearances
- Add seed_musicians.py for initial musician data
This commit is contained in:
fullsizemalt 2025-12-28 16:30:38 -08:00
parent 5b236608f8
commit 159cbc853c
2 changed files with 152 additions and 1 deletions

View file

@ -60,6 +60,8 @@ def list_musicians(
@router.get("/{slug}") @router.get("/{slug}")
def get_musician(slug: str, session: Session = Depends(get_session)): def get_musician(slug: str, session: Session = Depends(get_session)):
"""Get musician details with band memberships and guest appearances""" """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() musician = session.exec(select(Musician).where(Musician.slug == slug)).first()
if not musician: if not musician:
raise HTTPException(status_code=404, detail="Musician not found") raise HTTPException(status_code=404, detail="Musician not found")
@ -70,8 +72,10 @@ def get_musician(slug: str, session: Session = Depends(get_session)):
).all() ).all()
bands = [] bands = []
primary_band_ids = set()
for m in memberships: for m in memberships:
artist = session.get(Artist, m.artist_id) artist = session.get(Artist, m.artist_id)
primary_band_ids.add(m.artist_id)
bands.append({ bands.append({
"id": m.id, "id": m.id,
"artist_id": m.artist_id, "artist_id": m.artist_id,
@ -80,20 +84,44 @@ def get_musician(slug: str, session: Session = Depends(get_session)):
"role": m.role, "role": m.role,
"start_date": str(m.start_date) if m.start_date else None, "start_date": str(m.start_date) if m.start_date else None,
"end_date": str(m.end_date) if m.end_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( appearances = session.exec(
select(PerformanceGuest).where(PerformanceGuest.musician_id == musician.id) select(PerformanceGuest).where(PerformanceGuest.musician_id == musician.id)
).all() ).all()
guests = [] guests = []
sit_in_verticals = {} # Track which bands they've sat in with
for g in appearances: for g in appearances:
perf = session.get(Performance, g.performance_id) 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({ guests.append({
"id": g.id, "id": g.id,
"performance_id": g.performance_id, "performance_id": g.performance_id,
"performance_slug": perf.slug if perf else None, "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, "instrument": g.instrument,
}) })
@ -108,6 +136,13 @@ def get_musician(slug: str, session: Session = Depends(get_session)):
}, },
"bands": bands, "bands": bands,
"guest_appearances": guests, "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) --- # --- Admin Endpoints (for now, no auth check - can be added later) ---

116
backend/seed_musicians.py Normal file
View file

@ -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()