feat: Add VenueCanon for cross-band venue deduplication with across-bands endpoint
Some checks failed
Deploy Fediversion / deploy (push) Failing after 1s
Some checks failed
Deploy Fediversion / deploy (push) Failing after 1s
This commit is contained in:
parent
60e2abfb65
commit
af6a4ae5d3
2 changed files with 98 additions and 0 deletions
|
|
@ -112,6 +112,24 @@ class Vertical(SQLModel, table=True):
|
|||
songs: List["Song"] = Relationship(back_populates="vertical")
|
||||
scenes: List["Scene"] = Relationship(back_populates="verticals", link_model=VerticalScene)
|
||||
|
||||
class VenueCanon(SQLModel, table=True):
|
||||
"""Canonical venue independent of band - enables cross-band venue linking (like SongCanon for songs)"""
|
||||
id: Optional[int] = Field(default=None, primary_key=True)
|
||||
name: str = Field(index=True)
|
||||
slug: str = Field(unique=True, index=True)
|
||||
city: str
|
||||
state: Optional[str] = Field(default=None)
|
||||
country: str = Field(default="USA")
|
||||
latitude: Optional[float] = Field(default=None)
|
||||
longitude: Optional[float] = Field(default=None)
|
||||
capacity: Optional[int] = Field(default=None)
|
||||
website_url: Optional[str] = Field(default=None)
|
||||
notes: Optional[str] = Field(default=None)
|
||||
|
||||
# All venue records that point to this canonical venue
|
||||
venues: List["Venue"] = Relationship(back_populates="canon")
|
||||
|
||||
|
||||
class Venue(SQLModel, table=True):
|
||||
id: Optional[int] = Field(default=None, primary_key=True)
|
||||
name: str = Field(index=True)
|
||||
|
|
@ -122,8 +140,13 @@ class Venue(SQLModel, table=True):
|
|||
capacity: Optional[int] = Field(default=None)
|
||||
notes: Optional[str] = Field(default=None)
|
||||
|
||||
# Link to canonical venue for cross-band deduplication
|
||||
canon_id: Optional[int] = Field(default=None, foreign_key="venuecanon.id")
|
||||
canon: Optional[VenueCanon] = Relationship(back_populates="venues")
|
||||
|
||||
shows: List["Show"] = Relationship(back_populates="venue")
|
||||
|
||||
|
||||
class Tour(SQLModel, table=True):
|
||||
id: Optional[int] = Field(default=None, primary_key=True)
|
||||
name: str = Field(index=True)
|
||||
|
|
|
|||
|
|
@ -40,3 +40,78 @@ def update_venue(venue_id: int, venue: VenueUpdate, session: Session = Depends(g
|
|||
session.commit()
|
||||
session.refresh(db_venue)
|
||||
return db_venue
|
||||
|
||||
|
||||
@router.get("/{slug}/across-bands")
|
||||
def get_venue_across_bands(slug: str, session: Session = Depends(get_session)):
|
||||
"""Get aggregated stats for a venue across all bands that have played there (via VenueCanon)"""
|
||||
from models import VenueCanon, Show, Vertical
|
||||
from sqlmodel import func
|
||||
|
||||
# Find the venue by slug
|
||||
venue = session.exec(select(Venue).where(Venue.slug == slug)).first()
|
||||
if not venue:
|
||||
raise HTTPException(status_code=404, detail="Venue not found")
|
||||
|
||||
# If this venue has a canon_id, get all linked venues
|
||||
linked_venues = [venue]
|
||||
if venue.canon_id:
|
||||
linked_venues = session.exec(
|
||||
select(Venue).where(Venue.canon_id == venue.canon_id)
|
||||
).all()
|
||||
|
||||
venue_ids = [v.id for v in linked_venues]
|
||||
|
||||
# Get all shows at these venues
|
||||
shows = session.exec(
|
||||
select(Show)
|
||||
.where(Show.venue_id.in_(venue_ids))
|
||||
.order_by(Show.date.desc())
|
||||
).all()
|
||||
|
||||
# Group by vertical/band
|
||||
bands_stats = {}
|
||||
for show in shows:
|
||||
vertical = session.get(Vertical, show.vertical_id)
|
||||
if vertical:
|
||||
if vertical.id not in bands_stats:
|
||||
bands_stats[vertical.id] = {
|
||||
"vertical_id": vertical.id,
|
||||
"vertical_name": vertical.name,
|
||||
"vertical_slug": vertical.slug,
|
||||
"show_count": 0,
|
||||
"first_show": show.date,
|
||||
"last_show": show.date,
|
||||
"recent_shows": []
|
||||
}
|
||||
bands_stats[vertical.id]["show_count"] += 1
|
||||
if show.date < bands_stats[vertical.id]["first_show"]:
|
||||
bands_stats[vertical.id]["first_show"] = show.date
|
||||
if show.date > bands_stats[vertical.id]["last_show"]:
|
||||
bands_stats[vertical.id]["last_show"] = show.date
|
||||
if len(bands_stats[vertical.id]["recent_shows"]) < 3:
|
||||
bands_stats[vertical.id]["recent_shows"].append({
|
||||
"date": show.date.strftime("%Y-%m-%d") if show.date else None,
|
||||
"slug": show.slug
|
||||
})
|
||||
|
||||
# Format response
|
||||
bands_list = sorted(bands_stats.values(), key=lambda x: x["show_count"], reverse=True)
|
||||
for band in bands_list:
|
||||
band["first_show"] = band["first_show"].strftime("%Y-%m-%d") if band["first_show"] else None
|
||||
band["last_show"] = band["last_show"].strftime("%Y-%m-%d") if band["last_show"] else None
|
||||
|
||||
return {
|
||||
"venue": {
|
||||
"id": venue.id,
|
||||
"name": venue.name,
|
||||
"slug": venue.slug,
|
||||
"city": venue.city,
|
||||
"state": venue.state,
|
||||
"country": venue.country,
|
||||
"capacity": venue.capacity,
|
||||
},
|
||||
"total_shows": len(shows),
|
||||
"bands_count": len(bands_list),
|
||||
"bands": bands_list
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue