fix: evaluated field on event list + abort race condition

- Backend: add evaluated subquery column to list SELECT so each event
  returns evaluated=1/0 (was missing, causing all events to appear
  as unevaluated regardless of filter)
- Frontend: AbortController cancels the previous in-flight fetch when
  a new load fires, preventing stale results from overwriting current
  filter state

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
OpenSquared
2026-06-25 21:36:12 +02:00
parent 984e6860ad
commit 332d5ad743
2 changed files with 24 additions and 8 deletions

View File

@@ -171,7 +171,13 @@ def list_events(
# When joining with instrument_impacts an event can appear multiple times — use GROUP BY # When joining with instrument_impacts an event can appear multiple times — use GROUP BY
group_sql = "GROUP BY me.id" if instrument else "" group_sql = "GROUP BY me.id" if instrument else ""
# Extra column: best instrument score for display # Extra columns: evaluated flag + optional instrument score
evaluated_col = (
", (CASE WHEN EXISTS ("
"SELECT 1 FROM instrument_impacts _ei "
"WHERE _ei.source_type='event' AND _ei.source_id=me.id"
") THEN 1 ELSE 0 END) as evaluated"
)
inst_col = ( inst_col = (
", MAX(COALESCE(ii.adjusted_score, ii.impact_score)) as inst_score" ", MAX(COALESCE(ii.adjusted_score, ii.impact_score)) as inst_score"
", ii.direction as inst_direction" ", ii.direction as inst_direction"
@@ -181,7 +187,7 @@ def list_events(
SELECT COUNT(DISTINCT me.id) FROM market_events me {join_clause} {where_sql} SELECT COUNT(DISTINCT me.id) FROM market_events me {join_clause} {where_sql}
""" """
data_sql = f""" data_sql = f"""
SELECT me.* {inst_col} SELECT me.* {evaluated_col} {inst_col}
FROM market_events me {join_clause} FROM market_events me {join_clause}
{where_sql} {group_sql} {order_sql} {where_sql} {group_sql} {order_sql}
LIMIT ? OFFSET ? LIMIT ? OFFSET ?

View File

@@ -1173,9 +1173,15 @@ export default function MarketEvents() {
setDateTo(to) setDateTo(to)
}, []) }, [])
const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null) const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null)
const abortRef = useRef<AbortController | null>(null)
const load = useCallback(async () => { const load = useCallback(async () => {
// Cancel any in-flight fetch
abortRef.current?.abort()
abortRef.current = new AbortController()
const signal = abortRef.current.signal
setLoading(true) setLoading(true)
const params = new URLSearchParams() const params = new URLSearchParams()
if (search) params.set('search', search) if (search) params.set('search', search)
@@ -1193,11 +1199,15 @@ export default function MarketEvents() {
params.set('sort_by', instTicker ? 'instrument_score' : sortBy) params.set('sort_by', instTicker ? 'instrument_score' : sortBy)
params.set('sort_dir', sortDir) params.set('sort_dir', sortDir)
params.set('limit', '500') params.set('limit', '500')
const r = await fetch(`/api/market-events?${params}`) try {
if (r.ok) { const r = await fetch(`/api/market-events?${params}`, { signal })
const d = await r.json() if (r.ok) {
setEvents(d.events) const d = await r.json()
setTotal(d.total) setEvents(d.events)
setTotal(d.total)
}
} catch (e: any) {
if (e.name === 'AbortError') return // superseded by a newer load
} }
setLoading(false) setLoading(false)
}, [search, category, level, minScore, dateFrom, dateTo, evaluated, }, [search, category, level, minScore, dateFrom, dateTo, evaluated,