fix: separate reference stale matches from source health
This commit is contained in:
@@ -712,7 +712,10 @@ class SourceHealthResponse(BaseModel):
|
||||
formal_provider_blocker: str | None = None
|
||||
upcoming_odds_matches: int = 0
|
||||
stale_unsettled_matches: int = 0
|
||||
reference_only_stale_matches: int = 0
|
||||
stale_unsettled_threshold_hours: int = 3
|
||||
stale_unsettled_examples: list[dict[str, Any]] = Field(default_factory=list)
|
||||
reference_only_stale_examples: list[dict[str, Any]] = Field(default_factory=list)
|
||||
odds_rows: int
|
||||
matches: int
|
||||
finished_matches: int
|
||||
@@ -802,12 +805,16 @@ async def analytics_source_health() -> SourceHealthResponse:
|
||||
matches = int(match_count_result.scalar_one() or 0)
|
||||
finished_matches = int(finished_count_result.scalar_one() or 0)
|
||||
stale_unsettled_matches = int(stale_unsettled_result.scalar_one() or 0)
|
||||
reference_only_stale_matches = 0
|
||||
stale_unsettled_examples: list[dict[str, Any]] = []
|
||||
reference_only_stale_examples: list[dict[str, Any]] = []
|
||||
try:
|
||||
logical_matches = await _query_match_list(limit=5000)
|
||||
if logical_matches:
|
||||
matches = len(logical_matches)
|
||||
finished_matches = sum(1 for item in logical_matches if item.get('status') == '已完賽')
|
||||
logical_stale = 0
|
||||
logical_reference_stale = 0
|
||||
for item in logical_matches:
|
||||
if item.get('status') == '已完賽':
|
||||
continue
|
||||
@@ -819,8 +826,23 @@ async def analytics_source_health() -> SourceHealthResponse:
|
||||
if kickoff_at.tzinfo is None:
|
||||
kickoff_at = kickoff_at.replace(tzinfo=timezone.utc)
|
||||
if kickoff_at < now - timedelta(hours=stale_threshold_hours):
|
||||
logical_stale += 1
|
||||
example = {
|
||||
'match_id': item.get('match_id'),
|
||||
'home_team': item.get('home_team'),
|
||||
'away_team': item.get('away_team'),
|
||||
'kickoff_utc': kickoff_at.isoformat(),
|
||||
'status': item.get('status'),
|
||||
}
|
||||
if str(item.get('match_id') or '').startswith('tsl-'):
|
||||
logical_reference_stale += 1
|
||||
if len(reference_only_stale_examples) < 5:
|
||||
reference_only_stale_examples.append(example)
|
||||
else:
|
||||
logical_stale += 1
|
||||
if len(stale_unsettled_examples) < 5:
|
||||
stale_unsettled_examples.append(example)
|
||||
stale_unsettled_matches = logical_stale
|
||||
reference_only_stale_matches = logical_reference_stale
|
||||
except Exception:
|
||||
pass
|
||||
venues = int(venue_count_result.scalar_one() or 0)
|
||||
@@ -873,7 +895,10 @@ async def analytics_source_health() -> SourceHealthResponse:
|
||||
formal_provider_blocker=formal_provider_blocker,
|
||||
upcoming_odds_matches=upcoming_odds_matches,
|
||||
stale_unsettled_matches=stale_unsettled_matches,
|
||||
reference_only_stale_matches=reference_only_stale_matches,
|
||||
stale_unsettled_threshold_hours=stale_threshold_hours,
|
||||
stale_unsettled_examples=stale_unsettled_examples,
|
||||
reference_only_stale_examples=reference_only_stale_examples,
|
||||
odds_rows=odds_rows,
|
||||
matches=matches,
|
||||
finished_matches=finished_matches,
|
||||
|
||||
@@ -37,6 +37,9 @@ type SourceHealth = {
|
||||
venues?: number;
|
||||
high_altitude_venues?: number;
|
||||
stale_unsettled_matches?: number;
|
||||
reference_only_stale_matches?: number;
|
||||
stale_unsettled_examples?: Array<Record<string, unknown>>;
|
||||
reference_only_stale_examples?: Array<Record<string, unknown>>;
|
||||
latest_odds_recorded_at?: string | null;
|
||||
latest_result_synced_at?: string | null;
|
||||
ingestion_status?: WorkerStatus | null;
|
||||
@@ -93,7 +96,8 @@ export default async function SourceHealthPage() {
|
||||
['賽事總數', data?.matches ?? '-', '目前資料庫可見賽事'],
|
||||
['已完賽', data?.finished_matches ?? '-', '可用於賽後校準'],
|
||||
['賠率列數', data?.odds_rows ?? '-', '盤口歷史資料量'],
|
||||
['逾時賽果', data?.stale_unsettled_matches ?? '-', '不為 0 時不升級正式推薦'],
|
||||
['正式賽果逾時', data?.stale_unsettled_matches ?? '-', '不為 0 時不升級正式推薦'],
|
||||
['參考盤待核對', data?.reference_only_stale_matches ?? '-', '台灣參考盤殘留,不等於正式比分壞掉'],
|
||||
];
|
||||
const workerCards = [
|
||||
[
|
||||
@@ -151,7 +155,7 @@ export default async function SourceHealthPage() {
|
||||
) : null}
|
||||
</section>
|
||||
|
||||
<section className="grid gap-4 md:grid-cols-4">
|
||||
<section className="grid gap-4 md:grid-cols-5">
|
||||
{cards.map(([label, value, helper]) => (
|
||||
<article key={String(label)} className="panel-glow rounded-2xl p-5">
|
||||
<p className="text-xs font-semibold tracking-[0.2em] text-[#8a6b58]">{helper}</p>
|
||||
@@ -161,6 +165,25 @@ export default async function SourceHealthPage() {
|
||||
))}
|
||||
</section>
|
||||
|
||||
{(data?.reference_only_stale_matches ?? 0) > 0 ? (
|
||||
<section className="rounded-3xl border border-[#eadcb9] bg-[#fff8e6] p-5">
|
||||
<p className="dot-matrix text-sm font-bold text-[#7d2a15]">參考盤待核對提醒</p>
|
||||
<h2 className="mt-2 text-2xl font-black text-[#3f2f25]">目前有台灣參考盤賽事尚未對到正式賽果</h2>
|
||||
<p className="mt-2 text-sm leading-7 text-[#7a5b46]">
|
||||
這類資料只用來監控賠率,不會被當成正式比分來源,也不會自動補造下注推薦。正式賽果逾時若為 0,代表核心比分同步沒有被這些參考盤殘留誤判成壞掉。
|
||||
</p>
|
||||
<div className="mt-4 grid gap-3 md:grid-cols-2">
|
||||
{(data?.reference_only_stale_examples ?? []).map((item) => (
|
||||
<div key={String(item.match_id)} className="rounded-2xl border border-[#eadcb9] bg-white/75 p-4 text-sm leading-6 text-[#5f4330]">
|
||||
<p className="font-black text-[#7d2a15]">{String(item.home_team ?? '待確認')} vs {String(item.away_team ?? '待確認')}</p>
|
||||
<p>來源編號:{String(item.match_id ?? '未知')}</p>
|
||||
<p>目前狀態:{String(item.status ?? '待確認')}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
) : null}
|
||||
|
||||
<section className="grid gap-4 lg:grid-cols-3">
|
||||
{workerCards.map(([title, status, detail]) => (
|
||||
<article key={String(title)} className="rounded-3xl border border-[#eadcb9] bg-white/75 p-5">
|
||||
|
||||
Reference in New Issue
Block a user