*/ private const PLAY_TO_TIER = [ 'pos_3a' => 'first', 'pos_3b' => 'second', 'pos_3c' => 'third', ]; public function __construct( private readonly OddsSnapshotReader $odds, ) {} public function match(TicketItem $item, PublishedDrawResultBoard $board, Collection $combinations): array { $tier = self::PLAY_TO_TIER[$item->play_code] ?? 'first'; $suffix = $board->suffix3ForTier($tier, 0); if ($suffix === '') { return ['win_amount' => 0, 'matched_prize_tier' => null, 'match_detail' => ['reason' => 'no_suffix']]; } $snapshot = is_array($item->odds_snapshot_json) ? $item->odds_snapshot_json : null; $oddsVal = $this->odds->oddsValueForScope($snapshot, $tier); $lines = []; $total = 0; foreach ($combinations as $c) { /** @var TicketCombination $c */ $n = (string) $c->number_4d; if (strlen($n) < 3 || substr($n, -3) !== $suffix) { continue; } $bet = (int) $c->bet_amount; $payout = (int) floor($bet * ($oddsVal / 10_000)); $total += $payout; $lines[] = ['number_4d' => $n, 'suffix3' => $suffix, 'payout' => $payout]; } return [ 'win_amount' => $total, 'matched_prize_tier' => $total > 0 ? $tier : null, 'match_detail' => ['lines' => $lines], ]; } }