Deduplicate listen events from multiple HA entities
When a track plays, multiple HA entities (Cast, WiFi, MA) all fire the automation simultaneously, creating 3x duplicate listen events. Now skips logging if the same track was recorded within the last 60s. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -36,4 +36,6 @@ async def receive_webhook(payload: WebhookPayload, session: AsyncSession = Depen
|
|||||||
listened_at=payload.listened_at,
|
listened_at=payload.listened_at,
|
||||||
raw_payload=payload.model_dump(mode="json"),
|
raw_payload=payload.model_dump(mode="json"),
|
||||||
)
|
)
|
||||||
|
if event is None:
|
||||||
|
return {"ok": True, "duplicate": True}
|
||||||
return {"ok": True, "track_id": event.track_id, "event_id": event.id}
|
return {"ok": True, "track_id": event.track_id, "event_id": event.id}
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
from datetime import datetime
|
import logging
|
||||||
|
from datetime import datetime, timedelta, timezone
|
||||||
|
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from haunt_fm.models.track import ListenEvent, Track
|
from haunt_fm.models.track import ListenEvent, Track
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def make_fingerprint(artist: str, title: str) -> str:
|
def make_fingerprint(artist: str, title: str) -> str:
|
||||||
return f"{artist.lower().strip()}::{title.lower().strip()}"
|
return f"{artist.lower().strip()}::{title.lower().strip()}"
|
||||||
@@ -47,9 +50,22 @@ async def ingest_listen_event(
|
|||||||
source: str,
|
source: str,
|
||||||
listened_at: datetime,
|
listened_at: datetime,
|
||||||
raw_payload: dict | None = None,
|
raw_payload: dict | None = None,
|
||||||
) -> ListenEvent:
|
) -> ListenEvent | None:
|
||||||
track = await upsert_track(session, title, artist, album)
|
track = await upsert_track(session, title, artist, album)
|
||||||
|
|
||||||
|
# Deduplicate: skip if this track was logged within the last 60 seconds.
|
||||||
|
# Multiple HA entities (Cast, WiFi, MA) fire simultaneously for the same play event.
|
||||||
|
cutoff = datetime.now(timezone.utc) - timedelta(seconds=60)
|
||||||
|
recent = await session.execute(
|
||||||
|
select(ListenEvent)
|
||||||
|
.where(ListenEvent.track_id == track.id)
|
||||||
|
.where(ListenEvent.listened_at > cutoff)
|
||||||
|
.limit(1)
|
||||||
|
)
|
||||||
|
if recent.scalar_one_or_none() is not None:
|
||||||
|
logger.debug("Skipping duplicate listen event for %s - %s", artist, title)
|
||||||
|
return None
|
||||||
|
|
||||||
event = ListenEvent(
|
event = ListenEvent(
|
||||||
track_id=track.id,
|
track_id=track.id,
|
||||||
source=source,
|
source=source,
|
||||||
|
|||||||
Reference in New Issue
Block a user