'use client'; import type { DailyCardItem } from '@/lib/analytics-api'; type ActionableBetCardProps = { item: DailyCardItem; onAddToSlip: (item: DailyCardItem) => void; isInSlip?: boolean; className?: string; }; function isConditionalPick(item: DailyCardItem): boolean { return ( item.has_market_odds === false || item.selection.includes('預掛條件') || item.selection.includes('參考盤監控') || item.rationale.includes('尚未取得可用即時盤口') || item.rationale.includes('尚未取得完整實盤賠率') || Boolean(item.legs?.some((leg) => leg.selection.includes('預掛條件'))) ); } function isReferenceMarket(item: DailyCardItem): boolean { return item.odds_source_kind === 'reference_market'; } function recommendationLabel(item: DailyCardItem): string { const isConditional = isConditionalPick(item); if (item.recommendation === 'SAFE_SINGLE') return isConditional ? '觀察單關' : '核心單關'; if (item.recommendation === 'HIGH_RISK_SINGLE') return '小注高賠'; if (item.recommendation === 'SAFE_PARLAY') return isConditional ? '觀察串關' : '跨場串關'; if (item.recommendation === 'SGP_LOTTERY') return '同場小注'; return '研究候選'; } function riskLabel(value?: string): string { if (value === 'core') return '核心候選'; if (value === 'speculative') return '小注高波動'; if (value === 'parlay') return '串關組合'; if (value === 'sgp') return '同場高波動'; return '研究候選'; } function betMode(item: DailyCardItem): string { if (item.recommendation === 'SAFE_PARLAY') return '到投注平台選「串關」,依下方每一腿逐一加入。'; if (item.recommendation === 'SGP_LOTTERY') return '到投注平台選「同場串關」,只用很小的注碼。'; return '到投注平台選「單關」,找到同一場比賽與同一個市場後下注。'; } function formatTwd(value: number): string { return new Intl.NumberFormat('zh-TW', { style: 'currency', currency: 'TWD', maximumFractionDigits: 0, }).format(value); } function stakeAmountTwd(item: DailyCardItem): number { const unitSize = item.unit_size_twd ?? 1000; return item.stake_amount_twd ?? Math.round(item.stake_units * unitSize); } function stakeGuide(item: DailyCardItem): string { const unitSize = item.unit_size_twd ?? 1000; return `建議參考上限 ${formatTwd(stakeAmountTwd(item))},約 ${item.stake_units.toFixed(2)}u,系統目前以 1u 等於 ${formatTwd(unitSize)} 換算。`; } function oddsRule(item: DailyCardItem): string { const source = item.odds_source_label ? `目前來源:${item.odds_source_label}。` : ''; return `${source}只有在你拿到的賠率大於或等於 ${item.target_odds.toFixed(2)} 時才考慮下注;如果平台賠率低於這個數字,期望值會被吃掉,直接跳過。`; } function executionStatusLabel(item: DailyCardItem): string { if (isReferenceMarket(item)) return '台灣盤參考'; return isConditionalPick(item) ? '預掛條件' : '實盤可檢查'; } function executionStatusDetail(item: DailyCardItem): string { if (isReferenceMarket(item)) { return '這張已有台灣運彩公開盤作為參考價,可以用來比對最低可接受賠率;但它仍不是多莊家正式盤,下注前要再確認同一玩法、同一分線與最新盤口。'; } if (isConditionalPick(item)) { return '目前尚未取得完整實盤或分線盤口,這張不是叫你立刻下注,而是先設定最低賠率門檻。等平台開盤後,只有賠率達標才進場。'; } return '這張已有可比對的盤口資料,仍需在下注前確認同一市場、同一選項、同一分線與最低可接受賠率都一致。'; } function dataQualityLabel(item: DailyCardItem): string { if (isReferenceMarket(item)) return '資料品質:台灣運彩參考盤,已和模型門檻比對'; if (item.data_quality === 'rank_elo_prior') return '資料品質:國際排名與實力分數估計,信心已降低'; if (item.data_quality === 'fallback_prior') return '資料品質:基礎估計,僅能觀察'; if (item.data_quality === 'mixed') return '資料品質:串關腿數混合來源'; if (isConditionalPick(item)) return '資料品質:等待實盤確認'; if (item.data_checks?.some((check) => check.includes('去水') || check.includes('莊家'))) return '資料品質:已檢查平台抽成影響'; return '資料品質:基礎盤口檢查'; } function plainCheckLabel(check: string): string { return check .replaceAll('Poisson', '進球分布') .replaceAll('xG', '預期進球') .replaceAll('EV', '期望值') .replaceAll('edge', '模型優勢') .replaceAll('CLV', '收盤價差') .replaceAll('FIFA 排名/Elo', '國際排名與實力分數') .replaceAll('FIFA/Elo', '國際排名與實力分數') .replaceAll('市場隱含機率', '市場估計機率') .replaceAll('正 期望值', '期望值為正') .replaceAll('正期望值', '期望值為正') .replaceAll('小倉位', '小注碼') .replaceAll('預掛總賠率門檻', '等待總賠率達標') .replaceAll('串關 EV 重新計算', '串關期望值重新計算') .replaceAll('高 期望值 門檻', '高期望值門檻'); } function plainLanguage(item: DailyCardItem): string { const implied = typeof item.market_implied_prob === 'number' ? item.market_implied_prob.toFixed(2) : null; const edge = typeof item.edge_percent === 'number' ? item.edge_percent.toFixed(2) : null; const isConditional = isConditionalPick(item); if (implied && edge) { const quality = item.data_quality && item.data_quality !== 'observed' ? ' 但此項目前資料品質不是完整即時盤,信心與倉位已被系統折扣。' : ''; const sourceNote = isReferenceMarket(item) ? ' 這裡的市場機率來自台灣運彩參考盤,不是多莊家共識。' : ''; if (isConditional) { return `模型估這個選項有 ${item.win_prob.toFixed(2)}% 機率打出;目前還沒有完整可驗證賠率,所以 ${implied}% 代表「最低可接受賠率」換算出的門檻,不是真實市場共識。模型多看好 ${edge} 個百分點,期望值 ${item.ev_percent.toFixed(2)}% 代表長期同類型條件可能有優勢,不代表這場一定會中。${quality}`; } return `模型估這個選項有 ${item.win_prob.toFixed(2)}% 機率打出,市場目前大約只反映 ${implied}% 的機率,等於模型多看好 ${edge} 個百分點。期望值 ${item.ev_percent.toFixed(2)}% 代表長期同類型下注的平均報酬為正,不代表這場一定會中。${sourceNote}${quality}`; } return `模型估這個選項有 ${item.win_prob.toFixed(2)}% 機率打出,期望值 ${item.ev_percent.toFixed(2)}%。這是長期平均報酬判斷,不是單場保證;若資料品質不是完整即時盤,請只列入觀察。`; } export function ActionableBetCard({ item, onAddToSlip, isInSlip = false, className = '' }: ActionableBetCardProps) { const confidence = typeof item.confidence_score === 'number' ? item.confidence_score : null; const checks = item.data_checks ?? []; const confidenceFactors = item.confidence_factors ?? []; const isCombo = item.legs?.length; const isConditional = isConditionalPick(item); const referenceMarket = isReferenceMarket(item); const actionLabel = isInSlip ? isConditional ? '已在賠率監控清單' : '已在下注前檢查清單' : isConditional ? '加入賠率監控清單' : '加入下注前檢查清單'; return (

{recommendationLabel(item)}

{riskLabel(item.risk_level)} {executionStatusLabel(item)} {item.odds_source_label ? ( {item.odds_source_label} ) : null} {confidence !== null ? ( 信心 {confidence.toFixed(1)} ) : null} {item.confidence_band ? ( {item.confidence_band} ) : null}

{item.match_label}

投注市場:{item.market_type}

投注選項:{item.selection}

推薦狀態

{executionStatusDetail(item)}

{dataQualityLabel(item)}

下注方式

{betMode(item)}

市場:{item.market_type}

選項:{item.selection}

最低可接受賠率:{item.target_odds.toFixed(2)}

建議上限:{formatTwd(stakeAmountTwd(item))}

{oddsRule(item)}

{stakeGuide(item)}

{isCombo ? (

串關組成

{item.legs?.map((leg, index) => (

第 {index + 1} 腿:{leg.selection},賠率至少 {leg.odds.toFixed(2)}

))}
) : null}

白話分析

{plainLanguage(item)}

模型勝率:{item.win_prob.toFixed(2)}%

{typeof item.market_implied_prob === 'number' ? (

{isConditional ? '賠率門檻換算機率' : '市場估計機率'}:{item.market_implied_prob.toFixed(2)}%

) : null} {typeof item.edge_percent === 'number' ?

模型優勢:{item.edge_percent.toFixed(2)} 百分點

: null}

預期期望值:{item.ev_percent.toFixed(2)}%

{confidenceFactors.length || checks.length ? (

信心與計算依據

{confidenceFactors.slice(0, 5).map((factor) => ( {plainCheckLabel(factor)} ))} {checks.slice(0, 6).map((check) => ( {plainCheckLabel(check)} ))}
) : null}

風險提醒

這是研究候選,不是結果承諾。若賠率低於最低可接受賠率、先發名單異常、傷停新聞改變、或資料源延遲,就不要下注,等待下一次刷新。

下注前最後確認:同一場比賽、同一市場、同一分線、同一選項、賠率達標、倉位不超過建議單位。

{isInSlip ? (

已加入清單,請到本頁上方「賠率監控清單」集中檢查。

) : null}
); }