feat: Enhance musician API with cross-band sit-in tracking
Some checks failed
Deploy Fediversion / deploy (push) Failing after 1s
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:
parent
5b236608f8
commit
159cbc853c
2 changed files with 152 additions and 1 deletions
|
|
@ -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
116
backend/seed_musicians.py
Normal 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()
|
||||||
Loading…
Add table
Reference in a new issue