Spaces:
Running
Running
Fix dateBounds update: remove early pagination stop and add periodic dateBounds refresh
Browse files- src/lib/dataService.js +99 -25
- src/views/LeaderboardView.vue +19 -1
src/lib/dataService.js
CHANGED
|
@@ -191,52 +191,73 @@ class DataService {
|
|
| 191 |
console.log(`[DataService] Page ${pageCount}: ${pageData.length} rows, date range: ${pageDates[0]} to ${lastFetchedDate}`)
|
| 192 |
}
|
| 193 |
|
| 194 |
-
// If we got less than pageSize, we're done
|
| 195 |
if (pageData.length < pageSize) {
|
| 196 |
console.log(`[DataService] Last page reached (got ${pageData.length} rows, expected ${pageSize})`)
|
| 197 |
break
|
| 198 |
}
|
| 199 |
|
| 200 |
-
//
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
const dbMaxDateStr = typeof dbMaxDate === 'string' ? dbMaxDate.split('T')[0] : dbMaxDate
|
| 204 |
-
if (lastFetchedDateStr >= dbMaxDateStr) {
|
| 205 |
-
console.log(`[DataService] Reached target max date ${dbMaxDateStr}, stopping`)
|
| 206 |
-
break
|
| 207 |
-
}
|
| 208 |
-
}
|
| 209 |
-
|
| 210 |
from += pageSize
|
| 211 |
}
|
| 212 |
|
| 213 |
// Log the final date range
|
| 214 |
if (all.length > 0) {
|
| 215 |
const dates = all.map(r => r && r.date).filter(Boolean).sort()
|
| 216 |
-
|
|
|
|
|
|
|
| 217 |
console.log(`[DataService] Last 10 dates:`, dates.slice(-10))
|
| 218 |
|
| 219 |
-
// Verify we got the max date
|
| 220 |
-
const fetchedMaxDate = dates[dates.length - 1]
|
| 221 |
if (dbMaxDate) {
|
| 222 |
const fetchedMaxDateStr = typeof fetchedMaxDate === 'string' ? fetchedMaxDate.split('T')[0] : fetchedMaxDate
|
| 223 |
const dbMaxDateStr = typeof dbMaxDate === 'string' ? dbMaxDate.split('T')[0] : dbMaxDate
|
| 224 |
console.log(`[DataService] Verification: DB max date = ${dbMaxDateStr}, Fetched max date = ${fetchedMaxDateStr}`)
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
console.log(`[DataService]
|
| 229 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 230 |
.from('trading_decisions')
|
| 231 |
.select('id, agent_name, asset, model, date, price, recommended_action, news_count, sentiment, created_at, updated_at')
|
|
|
|
|
|
|
| 232 |
.order('date', { ascending: true })
|
| 233 |
-
|
| 234 |
-
if (!
|
| 235 |
-
console.log(`[DataService]
|
| 236 |
-
|
| 237 |
-
const
|
| 238 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 239 |
}
|
|
|
|
|
|
|
| 240 |
}
|
| 241 |
}
|
| 242 |
} else {
|
|
@@ -593,6 +614,59 @@ class DataService {
|
|
| 593 |
return this.load(true)
|
| 594 |
}
|
| 595 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 596 |
/**
|
| 597 |
* 获取当前状态
|
| 598 |
*/
|
|
|
|
| 191 |
console.log(`[DataService] Page ${pageCount}: ${pageData.length} rows, date range: ${pageDates[0]} to ${lastFetchedDate}`)
|
| 192 |
}
|
| 193 |
|
| 194 |
+
// If we got less than pageSize, we're done (no more data)
|
| 195 |
if (pageData.length < pageSize) {
|
| 196 |
console.log(`[DataService] Last page reached (got ${pageData.length} rows, expected ${pageSize})`)
|
| 197 |
break
|
| 198 |
}
|
| 199 |
|
| 200 |
+
// Continue to next page - don't stop early based on date comparison
|
| 201 |
+
// because pagination with order('date', ascending: true) might have
|
| 202 |
+
// multiple pages with the same date, and we need to fetch all pages
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 203 |
from += pageSize
|
| 204 |
}
|
| 205 |
|
| 206 |
// Log the final date range
|
| 207 |
if (all.length > 0) {
|
| 208 |
const dates = all.map(r => r && r.date).filter(Boolean).sort()
|
| 209 |
+
const fetchedMinDate = dates[0]
|
| 210 |
+
const fetchedMaxDate = dates[dates.length - 1]
|
| 211 |
+
console.log(`[DataService] Total fetched: ${all.length} rows across ${pageCount} pages, date range: ${fetchedMinDate} to ${fetchedMaxDate}`)
|
| 212 |
console.log(`[DataService] Last 10 dates:`, dates.slice(-10))
|
| 213 |
|
| 214 |
+
// Verify we got the max date - if not, fetch missing data by date range
|
|
|
|
| 215 |
if (dbMaxDate) {
|
| 216 |
const fetchedMaxDateStr = typeof fetchedMaxDate === 'string' ? fetchedMaxDate.split('T')[0] : fetchedMaxDate
|
| 217 |
const dbMaxDateStr = typeof dbMaxDate === 'string' ? dbMaxDate.split('T')[0] : dbMaxDate
|
| 218 |
console.log(`[DataService] Verification: DB max date = ${dbMaxDateStr}, Fetched max date = ${fetchedMaxDateStr}`)
|
| 219 |
+
|
| 220 |
+
if (fetchedMaxDateStr !== dbMaxDateStr && fetchedMaxDateStr < dbMaxDateStr) {
|
| 221 |
+
console.warn(`[DataService] Missing data detected! DB has ${dbMaxDateStr} but we only fetched up to ${fetchedMaxDateStr}`)
|
| 222 |
+
console.log(`[DataService] Fetching missing data from ${fetchedMaxDateStr} to ${dbMaxDateStr}...`)
|
| 223 |
+
|
| 224 |
+
// Fetch all data from the day after fetchedMaxDate to dbMaxDate
|
| 225 |
+
// Use gte (greater than or equal) to include the next day
|
| 226 |
+
const nextDay = new Date(fetchedMaxDateStr)
|
| 227 |
+
nextDay.setUTCDate(nextDay.getUTCDate() + 1)
|
| 228 |
+
const nextDayStr = nextDay.toISOString().split('T')[0]
|
| 229 |
+
|
| 230 |
+
const { data: missingData, error: missingError } = await supabase
|
| 231 |
.from('trading_decisions')
|
| 232 |
.select('id, agent_name, asset, model, date, price, recommended_action, news_count, sentiment, created_at, updated_at')
|
| 233 |
+
.gte('date', nextDayStr)
|
| 234 |
+
.lte('date', dbMaxDateStr)
|
| 235 |
.order('date', { ascending: true })
|
| 236 |
+
|
| 237 |
+
if (!missingError && missingData && missingData.length > 0) {
|
| 238 |
+
console.log(`[DataService] Fetched ${missingData.length} missing rows`)
|
| 239 |
+
// Merge missing data, avoiding duplicates by id
|
| 240 |
+
const existingIds = new Set(all.map(r => r.id))
|
| 241 |
+
const newRows = missingData.filter(r => !existingIds.has(r.id))
|
| 242 |
+
all.push(...newRows)
|
| 243 |
+
console.log(`[DataService] Added ${newRows.length} new rows (${missingData.length - newRows.length} duplicates skipped)`)
|
| 244 |
+
|
| 245 |
+
// Re-sort all data by date
|
| 246 |
+
all.sort((a, b) => {
|
| 247 |
+
const dateA = typeof a.date === 'string' ? a.date.split('T')[0] : a.date
|
| 248 |
+
const dateB = typeof b.date === 'string' ? b.date.split('T')[0] : b.date
|
| 249 |
+
return dateA > dateB ? 1 : (dateA < dateB ? -1 : 0)
|
| 250 |
+
})
|
| 251 |
+
|
| 252 |
+
const finalDates = all.map(r => r && r.date).filter(Boolean).sort()
|
| 253 |
+
console.log(`[DataService] After fetching missing data: ${all.length} total rows, date range: ${finalDates[0]} to ${finalDates[finalDates.length - 1]}`)
|
| 254 |
+
} else if (missingError) {
|
| 255 |
+
console.error(`[DataService] Failed to fetch missing data:`, missingError)
|
| 256 |
+
} else {
|
| 257 |
+
console.warn(`[DataService] No missing data found (this might indicate a data inconsistency)`)
|
| 258 |
}
|
| 259 |
+
} else if (fetchedMaxDateStr === dbMaxDateStr) {
|
| 260 |
+
console.log(`[DataService] ✓ Successfully fetched all data up to ${dbMaxDateStr}`)
|
| 261 |
}
|
| 262 |
}
|
| 263 |
} else {
|
|
|
|
| 614 |
return this.load(true)
|
| 615 |
}
|
| 616 |
|
| 617 |
+
/**
|
| 618 |
+
* 更新 dateBounds,直接查询数据库的最新日期
|
| 619 |
+
* 这个方法可以独立调用,不需要重新加载所有数据
|
| 620 |
+
*/
|
| 621 |
+
async updateDateBounds() {
|
| 622 |
+
try {
|
| 623 |
+
// 查询数据库的最小和最大日期
|
| 624 |
+
const [minDateResult, maxDateResult] = await Promise.all([
|
| 625 |
+
supabase
|
| 626 |
+
.from('trading_decisions')
|
| 627 |
+
.select('date')
|
| 628 |
+
.order('date', { ascending: true })
|
| 629 |
+
.limit(1),
|
| 630 |
+
supabase
|
| 631 |
+
.from('trading_decisions')
|
| 632 |
+
.select('date')
|
| 633 |
+
.order('date', { ascending: false })
|
| 634 |
+
.limit(1)
|
| 635 |
+
])
|
| 636 |
+
|
| 637 |
+
let dbMinDate = null
|
| 638 |
+
let dbMaxDate = null
|
| 639 |
+
|
| 640 |
+
if (!minDateResult.error && minDateResult.data && minDateResult.data.length > 0) {
|
| 641 |
+
dbMinDate = minDateResult.data[0].date
|
| 642 |
+
}
|
| 643 |
+
if (!maxDateResult.error && maxDateResult.data && maxDateResult.data.length > 0) {
|
| 644 |
+
dbMaxDate = maxDateResult.data[0].date
|
| 645 |
+
}
|
| 646 |
+
|
| 647 |
+
// 如果查询到了新的日期,更新 dateBounds
|
| 648 |
+
if (dbMinDate || dbMaxDate) {
|
| 649 |
+
const oldMax = this.dateBounds.max ? this.dateBounds.max.toISOString().split('T')[0] : null
|
| 650 |
+
const newMax = dbMaxDate ? (typeof dbMaxDate === 'string' ? dbMaxDate.split('T')[0] : dbMaxDate) : null
|
| 651 |
+
|
| 652 |
+
// 只有当新日期确实更新时才更新
|
| 653 |
+
if (newMax && (!oldMax || newMax > oldMax)) {
|
| 654 |
+
this.dateBounds = {
|
| 655 |
+
min: dbMinDate ? new Date(dbMinDate) : this.dateBounds.min,
|
| 656 |
+
max: dbMaxDate ? new Date(dbMaxDate) : this.dateBounds.max
|
| 657 |
+
}
|
| 658 |
+
console.log(`[DataService] updateDateBounds: Updated dateBounds to min = ${this.dateBounds.min?.toISOString().split('T')[0]}, max = ${this.dateBounds.max?.toISOString().split('T')[0]}`)
|
| 659 |
+
// 通知所有订阅者
|
| 660 |
+
this._notify()
|
| 661 |
+
} else if (newMax && oldMax && newMax === oldMax) {
|
| 662 |
+
console.log(`[DataService] updateDateBounds: Date bounds already up to date (max = ${newMax})`)
|
| 663 |
+
}
|
| 664 |
+
}
|
| 665 |
+
} catch (e) {
|
| 666 |
+
console.error('[DataService] Error updating dateBounds:', e)
|
| 667 |
+
}
|
| 668 |
+
}
|
| 669 |
+
|
| 670 |
/**
|
| 671 |
* 获取当前状态
|
| 672 |
*/
|
src/views/LeaderboardView.vue
CHANGED
|
@@ -118,7 +118,8 @@ export default {
|
|
| 118 |
updatedClickCount: 0,
|
| 119 |
asset: '',
|
| 120 |
drawerVisible: false,
|
| 121 |
-
unsubscribe: null
|
|
|
|
| 122 |
}
|
| 123 |
},
|
| 124 |
watch: {
|
|
@@ -320,6 +321,18 @@ export default {
|
|
| 320 |
if (!dataService.loaded && !dataService.loading) {
|
| 321 |
dataService.load()
|
| 322 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 323 |
},
|
| 324 |
beforeUnmount() {
|
| 325 |
// 取消订阅
|
|
@@ -327,6 +340,11 @@ export default {
|
|
| 327 |
this.unsubscribe()
|
| 328 |
this.unsubscribe = null
|
| 329 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 330 |
}
|
| 331 |
}
|
| 332 |
</script>
|
|
|
|
| 118 |
updatedClickCount: 0,
|
| 119 |
asset: '',
|
| 120 |
drawerVisible: false,
|
| 121 |
+
unsubscribe: null,
|
| 122 |
+
dateBoundsUpdateTimer: null
|
| 123 |
}
|
| 124 |
},
|
| 125 |
watch: {
|
|
|
|
| 321 |
if (!dataService.loaded && !dataService.loading) {
|
| 322 |
dataService.load()
|
| 323 |
}
|
| 324 |
+
|
| 325 |
+
// 定期更新 dateBounds(每30秒检查一次数据库的最新日期)
|
| 326 |
+
this.dateBoundsUpdateTimer = setInterval(() => {
|
| 327 |
+
dataService.updateDateBounds().catch(e => {
|
| 328 |
+
console.error('[LeaderboardView] Error updating dateBounds:', e)
|
| 329 |
+
})
|
| 330 |
+
}, 30000) // 30秒
|
| 331 |
+
|
| 332 |
+
// 组件挂载后立即更新一次
|
| 333 |
+
dataService.updateDateBounds().catch(e => {
|
| 334 |
+
console.error('[LeaderboardView] Error updating dateBounds on mount:', e)
|
| 335 |
+
})
|
| 336 |
},
|
| 337 |
beforeUnmount() {
|
| 338 |
// 取消订阅
|
|
|
|
| 340 |
this.unsubscribe()
|
| 341 |
this.unsubscribe = null
|
| 342 |
}
|
| 343 |
+
// 清除定时器
|
| 344 |
+
if (this.dateBoundsUpdateTimer) {
|
| 345 |
+
clearInterval(this.dateBoundsUpdateTimer)
|
| 346 |
+
this.dateBoundsUpdateTimer = null
|
| 347 |
+
}
|
| 348 |
}
|
| 349 |
}
|
| 350 |
</script>
|