Event Visibility Audit: Stale Events & Freshness Logic

by Dimemap Team 55 views

Hey guys! Today, we're diving deep into an event visibility logic audit. Turns out, there are a few kinks in the system that are causing some stale events to hang around longer than they should. Let's break it down and see how we can fix this, alright?

Executive Summary

Current Grade: C-Event visibility filtering is partially implemented but applies freshness logic to the wrong occurrence types. Basically, it's like having a super-efficient bouncer at the wrong door. 😅

Critical Finding: PublicEventsEnhanced has last_seen_at freshness filtering, but it ONLY applies to occurrence_type = "unknown". Exhibition and pattern events rely solely on unreliable scraped dates, causing stale events to remain visible indefinitely. This is a major issue because users are seeing outdated info. Imagine showing up to an event that ended ages ago! Not cool.


Audit Results by Occurrence Type

Let's get granular and look at each type of event.

1. EXPLICIT (Events with Clear Start/End Dates)

Grade: A+

Current Implementation:

# PublicEvents.ex & PublicEventsEnhanced.ex
where:
 (not is_nil(pe.ends_at) and pe.ends_at > now) or
 (is_nil(pe.ends_at) and pe.starts_at > now)

How it Works:

Explicit events, like a one-time concert or a specific festival date, are doing just fine. These events have clearly defined starts_at and ends_at fields that are parsed and stored. The system checks if those dates have passed, and if they have, the event is hidden. Simple, right?

The current implementation checks these dates, and filters them accordingly. This means that the dates are reliable and filtering works as expected.

Status:Works Correctly - Explicit dates are reliable and filtering works as expected.

Example: One-time concerts, specific festival dates, single performances


2. PATTERN (Recurring Events like Weekly Trivia)

Grade: D

Current Implementation:

  • Pattern events use the same date-based filtering as explicit events
  • NO freshness filtering applied (only applied to "unknown" type)

How it Works (Poorly):

Here's where things start to get a bit dicey. Pattern events, like a weekly trivia night or a monthly meetup, are treated like explicit events, but they shouldn't be. The starts_at field is usually set to the date of the first occurrence, which means the filter checks if starts_at > now. Obviously, that fails if the event started in the past. Then, it falls back to checking ends_at > now, but pattern events often don't have an end date. 😬

Result: Events stay visible forever unless manually given an end date. Yikes! This is a major problem. Imagine seeing a weekly event from five years ago still listed. Super confusing for users!

Status: ⚠️ Partially Broken - Pattern events from years ago may still be visible if ends_at is not set or is far in future

Example: Weekly trivia nights, monthly meetups, recurring workshops

What Should Happen:

We need to check if the event was seen in the last 14 days via public_event_sources.last_seen_at. If it hasn't been seen recently, we should hide it from index pages but still make it accessible on show pages with a "may no longer be current" warning. This gives users the info they need while keeping the index pages fresh.


3. EXHIBITION (Open-Ended Date Ranges)

Grade: F

Current Implementation:

  • Uses same date-based filtering as explicit events
  • NO freshness filtering applied (only applied to "unknown" type)

How it Works (Broken):

Exhibition events are even worse! These often have vague or wrong dates scraped from various sources. For example, an exhibition might show "October 15, 2025 to January 24, 2026" when the actual dates are "September 2023 onwards." The filter relies on these unreliable scraped dates, leading to chaos. 😵‍💫

Result: Exhibitions with wrong dates stay visible indefinitely. It's completely broken. There's no reliable end date or freshness filtering, which is super misleading for users.

Status:Completely Broken - No reliable end date, no freshness filtering, misleading users with stale information

Example: Museum exhibitions, art galleries, installations

Current Behavior:

An exhibition from 2023 with the wrong scraped dates is showing as current. Users see outdated information and might even plan a visit based on false data. Not a good look.

What Should Happen:

We should ignore the scraped dates (they're unreliable, remember?). Instead, we need to check if the event was seen in the last 30 days via public_event_sources.last_seen_at. If it hasn't been seen recently, hide it from index pages. We can show a generic message like "Open dates vary" (already implemented via #1897) and keep it accessible on show pages with a "may no longer be current" warning. Transparency is key!


4. RECURRING (Similar to Pattern)

Grade: D

Current Implementation:

  • Uses same date-based filtering as explicit events
  • NO freshness filtering applied (only applied to "unknown" type)

Status: ⚠️ Partially Broken - Same issues as pattern events. Basically, just copy and paste the problems from the pattern events section. 😅


5. UNKNOWN (Fallback Type)

Grade: B+

Current Implementation in PublicEventsEnhanced:

# lib/eventasaurus_discovery/public_events_enhanced.ex:173-194
defp filter_past_events(query, _) do
 current_time = DateTime.utc_now()
 freshness_threshold = DateTime.add(current_time, -7, :day)

 from(pe in query,
 left_join: es in EventasaurusDiscovery.PublicEvents.PublicEventSource,
 on: es.event_id == pe.id,
 where:
 # Known dates: check starts_at/ends_at
 (not is_nil(pe.ends_at) and pe.ends_at > ^current_time) or
 (is_nil(pe.ends_at) and pe.starts_at > ^current_time) or
 # Unknown occurrence type: check last_seen_at for freshness
 (fragment("? ->> 'occurrence_type'", es.metadata) == "unknown" and
 es.last_seen_at >= ^freshness_threshold),
 distinct: pe.id
 )
end

How it Works:

Events with occurrence_type = "unknown" in public_event_sources.metadata get a 7-day freshness check. If last_seen_at is older than 7 days, it's hidden. This actually works! 🎉

Status:Works in Enhanced Module - Has proper freshness filtering

Issue: This is the ONLY occurrence type with freshness filtering, but it should apply to exhibition/pattern/recurring instead. It's like having the right tool, but using it on the wrong project. 🤦


Module Usage Analysis

PublicEventsEnhanced (Partial Freshness Logic)

Used by:

  • City index pages (lib/eventasaurus_web/live/city_live/index.ex:57)
  • Event listings
  • Search results
  • Public-facing queries

Has freshness logic: ✅ YES, but only for "unknown" type

PublicEvents (No Freshness Logic)

Used by:

  • Direct database queries
  • Admin interfaces
  • Background jobs
  • Stats/analytics

Has freshness logic: ❌ NO


Overall System Grade: C-

What Works:

  • ✅ Infrastructure exists (last_seen_at field in public_event_sources table)
  • ✅ Freshness filtering logic exists in PublicEventsEnhanced
  • ✅ Explicit events work perfectly

What's Broken:

  • ❌ Freshness logic applied to wrong occurrence type ("unknown" instead of "exhibition"/"pattern"/"recurring")
  • ❌ Exhibition events rely on unreliable scraped dates
  • ❌ Pattern/recurring events never expire without manual end dates
  • ❌ Stale events from years ago may still be visible

Recommended Solution

Option 1: Apply Freshness to All Non-Explicit Types (Recommended)

This is the easiest fix and a huge improvement. Let's get those stale events outta here!

Change lib/eventasaurus_discovery/public_events_enhanced.ex line 190:

FROM:

(fragment("? ->> 'occurrence_type'", es.metadata) == "unknown" and
 es.last_seen_at >= ^freshness_threshold)

TO:

(fragment("? ->> 'occurrence_type'", es.metadata) in ["unknown", "exhibition", "pattern", "recurring"] and
 es.last_seen_at >= ^freshness_threshold)

Option 2: Type-Specific Freshness Thresholds (Better)

This is a more nuanced approach. Different occurrence types need different freshness windows. Makes sense, right?

defp filter_past_events(query, _) do
 current_time = DateTime.utc_now()

 from(pe in query,
 left_join: es in EventasaurusDiscovery.PublicEvents.PublicEventSource,
 on: es.event_id == pe.id,
 where:
 # Explicit dates: check starts_at/ends_at
 (not is_nil(pe.ends_at) and pe.ends_at > ^current_time) or
 (is_nil(pe.ends_at) and pe.starts_at > ^current_time) or
 # Exhibition: 30-day freshness window (exhibitions change slowly)
 (fragment("? ->> 'occurrence_type'", es.metadata) == "exhibition" and
 es.last_seen_at >= ^DateTime.add(current_time, -30, :day)) or
 # Pattern/Recurring: 14-day freshness window (should be seen regularly)
 (fragment("? ->> 'occurrence_type'", es.metadata) in ["pattern", "recurring"] and
 es.last_seen_at >= ^DateTime.add(current_time, -14, :day)) or
 # Unknown: 7-day freshness window (conservative)
 (fragment("? ->> 'occurrence_type'", es.metadata) == "unknown" and
 es.last_seen_at >= ^DateTime.add(current_time, -7, :day)),
 distinct: pe.id
 )
end

Suggested Thresholds:

  • Exhibition: 30 days (exhibitions change slowly, venues may not be scraped daily)
  • Pattern: 14 days (weekly/monthly events should be seen every 1-2 weeks)
  • Recurring: 14 days (similar to pattern)
  • Unknown: 7 days (conservative, unknown provenance)

Expected Behavior After Fix

Index Pages (City Events List)

  • ✅ Only show events that are current (explicit dates valid OR recently seen by scraper)
  • ❌ Hide stale events that haven't been seen in threshold period
  • ✅ Automatic cleanup as scraper data ages

Show Pages (Individual Event Detail)

  • ✅ Still accessible via direct URL (for SEO, bookmarks, etc.)
  • ✅ Display "may no longer be current" warning if last_seen_at is old
  • ✅ Show "Open dates vary" for exhibitions (already implemented via #1897)

Related Issues

  • #1897 - Exhibition date display (implemented Option 4: generic message)
  • Sortiraparis scraper misclassifying events as "exhibition" type (separate issue)

Testing Checklist

After implementing the fix, testing is crucial. Make sure everything works as expected.

  • [ ] Verify exhibition events disappear after the threshold (30 days)
  • [ ] Verify pattern events disappear after the threshold (14 days)
  • [ ] Verify explicit events still work correctly (date-based only)
  • [ ] Check show pages are still accessible for old events
  • [ ] Verify aggregation logic handles mixed visibility correctly
  • [ ] Test edge case: event with both explicit dates AND old last_seen_at
  • [ ] Confirm PublicEvents module (used by admin) still shows all events

Files Referenced

  • lib/eventasaurus_discovery/public_events_enhanced.ex:173-194 - filter_past_events (needs fix)
  • lib/eventasaurus_discovery/public_events.ex:136-145 - basic filter (no freshness logic)
  • lib/eventasaurus_web/live/city_live/index.ex:57 - uses PublicEventsEnhanced
  • lib/eventasaurus_web/helpers/public_event_display_helpers.ex:101-105 - exhibition message
  • Database: public_event_sources table with last_seen_at and metadata fields

Okay, team, that's the lowdown! Let's get these fixes implemented and make sure our users are seeing the most up-to-date and accurate event information. Happy coding! 🚀