"""Tolerant GSM8K reward.

verl's built-in gsm8k scorer is strict: it requires ``#### <number>`` in the
last 300 chars. Qwen3 chat models frequently answer with ``\boxed{...}`` or
``#### **Answer: $18**``, so correct answers score 0 and GRPO starves.

Extraction cascade (all over the tail of the response, where answers live):
1. canonical ``#### <number>``
2. a number after ``####`` on the same line (``#### **Answer: $18**``)
3. ``\boxed{...}``
4. last number anywhere in the tail (verl's "flexible" fallback)

Numbers are compared after stripping ``$``, commas, spaces and a trailing
``.0``, with a float fallback for e.g. ``0.5`` vs ``0.50``.
"""

import re

_STRICT = re.compile(r"####\s+(-?[\d,]+(?:\.\d+)?)")
_ANY_NUM = re.compile(r"-?[\d,]+(?:\.\d+)?")
_BOXED = re.compile(r"\\boxed\{([^{}]+)\}")
_TAIL_CHARS = 1500


def _norm(value) -> str | None:
    if value is None:
        return None
    text = str(value).strip().strip("$").replace(",", "").replace(" ", "")
    if text.endswith(".0"):
        text = text[:-2]
    return text or None


def _extract(text: str) -> str | None:
    tail = text[-_TAIL_CHARS:]

    match = _STRICT.findall(tail)
    if match:
        return match[-1]

    # ``#### **Answer: $18**`` style: any number after #### on its line.
    for line in reversed(tail.splitlines()):
        if "####" in line:
            after_hash = line.split("####", 1)[1]
            numbers = _ANY_NUM.findall(after_hash)
            if numbers:
                return numbers[-1]

    match = _BOXED.findall(tail)
    if match:
        boxed = _norm(match[-1])
        if boxed:
            return boxed

    match = _ANY_NUM.findall(tail)
    return match[-1] if match else None


def compute_score(data_source, solution_str, ground_truth, extra_info=None, **kwargs) -> float:
    answer = _norm(_extract(solution_str or ""))
    target = _norm(ground_truth)
    if answer is None or target is None:
        return 0.0
    if answer == target:
        return 1.0
    try:
        return 1.0 if abs(float(answer) - float(target)) < 1e-6 else 0.0
    except ValueError:
        return 0.0
