fix: avoid false partial research failures
This commit is contained in:
+21
-11
@@ -468,22 +468,31 @@ class PolymarketActivityWsService(
|
||||
if (shouldThrottleResearchCaptureHealth(status)) {
|
||||
return
|
||||
}
|
||||
researchSourceHealthProvider.getIfAvailable()?.record(
|
||||
sourceType = LeaderResearchSourceType.GLOBAL_ACTIVITY_CAPTURE,
|
||||
status = status,
|
||||
candidateCount = candidateCount,
|
||||
errorClass = errorClass,
|
||||
errorMessage = errorMessage,
|
||||
disabledReason = disabledReason,
|
||||
lastCursor = lastCursor
|
||||
)
|
||||
try {
|
||||
researchSourceHealthProvider.getIfAvailable()?.record(
|
||||
sourceType = LeaderResearchSourceType.GLOBAL_ACTIVITY_CAPTURE,
|
||||
status = status,
|
||||
candidateCount = candidateCount,
|
||||
errorClass = errorClass,
|
||||
errorMessage = errorMessage,
|
||||
disabledReason = disabledReason,
|
||||
lastCursor = lastCursor
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
logger.warn("记录研究全局 activity 来源健康失败: status={}, error={}", status, e.message)
|
||||
}
|
||||
}
|
||||
|
||||
private fun shouldThrottleResearchCaptureHealth(status: LeaderResearchSourceStatus): Boolean {
|
||||
val now = System.currentTimeMillis()
|
||||
val throttle = status != LeaderResearchSourceStatus.SUCCESS &&
|
||||
val throttleWindow = when (status) {
|
||||
LeaderResearchSourceStatus.DISABLED -> RESEARCH_CAPTURE_DISABLED_HEALTH_THROTTLE_MS
|
||||
LeaderResearchSourceStatus.SUCCESS -> 0L
|
||||
else -> RESEARCH_CAPTURE_HEALTH_THROTTLE_MS
|
||||
}
|
||||
val throttle = throttleWindow > 0 &&
|
||||
status == researchCaptureLastHealthStatus &&
|
||||
now - researchCaptureLastHealthWriteAt < RESEARCH_CAPTURE_HEALTH_THROTTLE_MS
|
||||
now - researchCaptureLastHealthWriteAt < throttleWindow
|
||||
if (!throttle) {
|
||||
researchCaptureLastHealthStatus = status
|
||||
researchCaptureLastHealthWriteAt = now
|
||||
@@ -678,5 +687,6 @@ class PolymarketActivityWsService(
|
||||
|
||||
companion object {
|
||||
private const val RESEARCH_CAPTURE_HEALTH_THROTTLE_MS = 60_000L
|
||||
private const val RESEARCH_CAPTURE_DISABLED_HEALTH_THROTTLE_MS = 60L * 60L * 1000L
|
||||
}
|
||||
}
|
||||
|
||||
+4
-1
@@ -3,6 +3,7 @@ package com.wrbug.polymarketbot.service.copytrading.research
|
||||
import com.wrbug.polymarketbot.entity.LeaderResearchRun
|
||||
import com.wrbug.polymarketbot.enums.LeaderResearchEventType
|
||||
import com.wrbug.polymarketbot.enums.LeaderResearchRunStatus
|
||||
import com.wrbug.polymarketbot.enums.LeaderResearchSourceStatus
|
||||
import com.wrbug.polymarketbot.enums.LeaderResearchState
|
||||
import com.wrbug.polymarketbot.enums.LeaderResearchTriggerType
|
||||
import com.wrbug.polymarketbot.repository.LeaderActivityEventRepository
|
||||
@@ -97,7 +98,9 @@ class LeaderResearchJobService(
|
||||
}
|
||||
val lastEventCursor = activityEventRepository.findTopByOrderByEventTimeDesc()
|
||||
?.let { "${it.eventTime}:${it.stableEventKey}" }
|
||||
val hasSourceProblems = sourceResults.any { it.status.name == "FAILURE" || it.status.name == "DEGRADED" }
|
||||
val hasSourceProblems = sourceResults.any {
|
||||
!it.expectedLimitation && (it.status == LeaderResearchSourceStatus.FAILURE || it.status == LeaderResearchSourceStatus.DEGRADED)
|
||||
}
|
||||
run = runRepository.save(
|
||||
run.copy(
|
||||
status = if (hasSourceProblems) LeaderResearchRunStatus.PARTIAL_FAILURE else LeaderResearchRunStatus.SUCCESS,
|
||||
|
||||
+14
-7
@@ -25,7 +25,8 @@ data class LeaderResearchSourceRunResult(
|
||||
val status: LeaderResearchSourceStatus,
|
||||
val errorClass: String? = null,
|
||||
val errorMessage: String? = null,
|
||||
val limitation: String? = null
|
||||
val limitation: String? = null,
|
||||
val expectedLimitation: Boolean = false
|
||||
)
|
||||
|
||||
private data class SourceDiscovery(
|
||||
@@ -108,7 +109,8 @@ class LeaderResearchSourceService(
|
||||
LeaderResearchSourceType.ACTIVITY_DERIVED,
|
||||
activity,
|
||||
if (globalCaptureEnabled) LeaderResearchSourceStatus.SUCCESS else LeaderResearchSourceStatus.DEGRADED,
|
||||
limitation = if (globalCaptureEnabled) null else GLOBAL_CAPTURE_DISABLED_LIMITATION
|
||||
limitation = if (globalCaptureEnabled) null else GLOBAL_CAPTURE_DISABLED_LIMITATION,
|
||||
expectedLimitation = !globalCaptureEnabled
|
||||
)
|
||||
)
|
||||
if (!globalCaptureEnabled) {
|
||||
@@ -116,14 +118,16 @@ class LeaderResearchSourceService(
|
||||
LeaderResearchSourceType.GLOBAL_ACTIVITY_CAPTURE,
|
||||
emptyList(),
|
||||
LeaderResearchSourceStatus.DISABLED,
|
||||
limitation = GLOBAL_CAPTURE_DISABLED_LIMITATION
|
||||
limitation = GLOBAL_CAPTURE_DISABLED_LIMITATION,
|
||||
expectedLimitation = true
|
||||
)
|
||||
}
|
||||
results += LeaderResearchSourceRunResult(
|
||||
LeaderResearchSourceType.PUBLIC_LEADERBOARD,
|
||||
emptyList(),
|
||||
LeaderResearchSourceStatus.DISABLED,
|
||||
limitation = PUBLIC_LEADERBOARD_DISABLED_LIMITATION
|
||||
limitation = PUBLIC_LEADERBOARD_DISABLED_LIMITATION,
|
||||
expectedLimitation = true
|
||||
)
|
||||
return results
|
||||
}
|
||||
@@ -406,7 +410,8 @@ class LeaderResearchSourceService(
|
||||
)
|
||||
return result.copy(
|
||||
status = LeaderResearchSourceStatus.DEGRADED,
|
||||
limitation = GLOBAL_CAPTURE_DISABLED_LIMITATION
|
||||
limitation = GLOBAL_CAPTURE_DISABLED_LIMITATION,
|
||||
expectedLimitation = true
|
||||
)
|
||||
}
|
||||
|
||||
@@ -429,7 +434,8 @@ class LeaderResearchSourceService(
|
||||
sourceType = LeaderResearchSourceType.PUBLIC_LEADERBOARD,
|
||||
candidates = emptyList(),
|
||||
status = LeaderResearchSourceStatus.DISABLED,
|
||||
limitation = PUBLIC_LEADERBOARD_DISABLED_LIMITATION
|
||||
limitation = PUBLIC_LEADERBOARD_DISABLED_LIMITATION,
|
||||
expectedLimitation = true
|
||||
)
|
||||
}
|
||||
|
||||
@@ -452,7 +458,8 @@ class LeaderResearchSourceService(
|
||||
sourceType = LeaderResearchSourceType.GLOBAL_ACTIVITY_CAPTURE,
|
||||
candidates = emptyList(),
|
||||
status = LeaderResearchSourceStatus.DISABLED,
|
||||
limitation = GLOBAL_CAPTURE_DISABLED_LIMITATION
|
||||
limitation = GLOBAL_CAPTURE_DISABLED_LIMITATION,
|
||||
expectedLimitation = true
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+10
@@ -34,6 +34,16 @@ class PolymarketActivityWsResearchCaptureTest {
|
||||
assertEquals("Global activity capture is disabled", invocation.arguments[5])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `disabled global capture health is written once to avoid source state lock churn`() {
|
||||
val service = service(globalCaptureEnabled = false)
|
||||
|
||||
invokeHandleMessage(service, "not-json")
|
||||
invokeHandleMessage(service, "still-not-json")
|
||||
|
||||
assertEquals(1, Mockito.mockingDetails(healthService).invocations.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `write cap records degraded source health`() {
|
||||
val service = service(globalCaptureEnabled = true, maxWritesPerMinute = 0)
|
||||
|
||||
+38
@@ -77,6 +77,44 @@ class LeaderResearchJobServiceTest {
|
||||
Mockito.verify(paperTradingService).processPaperCandidates(run.id)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `expected disabled sources do not mark run partial failure`() {
|
||||
val service = service()
|
||||
stubRunSaves()
|
||||
Mockito.`when`(sourceService.discoverCandidates(1L)).thenReturn(
|
||||
listOf(
|
||||
LeaderResearchSourceRunResult(LeaderResearchSourceType.WATCHLIST, emptyList(), LeaderResearchSourceStatus.SUCCESS),
|
||||
LeaderResearchSourceRunResult(
|
||||
LeaderResearchSourceType.ACTIVITY_DERIVED,
|
||||
emptyList(),
|
||||
LeaderResearchSourceStatus.DEGRADED,
|
||||
limitation = "Global activity capture is disabled",
|
||||
expectedLimitation = true
|
||||
),
|
||||
LeaderResearchSourceRunResult(
|
||||
LeaderResearchSourceType.GLOBAL_ACTIVITY_CAPTURE,
|
||||
emptyList(),
|
||||
LeaderResearchSourceStatus.DISABLED,
|
||||
limitation = "Global activity capture is disabled",
|
||||
expectedLimitation = true
|
||||
),
|
||||
LeaderResearchSourceRunResult(
|
||||
LeaderResearchSourceType.PUBLIC_LEADERBOARD,
|
||||
emptyList(),
|
||||
LeaderResearchSourceStatus.DISABLED,
|
||||
limitation = "Public leaderboard source is intentionally disabled",
|
||||
expectedLimitation = true
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
val run = service.runOnce(dryRun = false, triggerType = LeaderResearchTriggerType.MANUAL)
|
||||
|
||||
assertEquals(LeaderResearchRunStatus.SUCCESS, run.status)
|
||||
assertFalse(run.partialFailure)
|
||||
Mockito.verify(paperTradingService).processPaperCandidates(run.id)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `preview run does not score advance or paper trade`() {
|
||||
val service = service()
|
||||
|
||||
Reference in New Issue
Block a user