CODE-0004 · claude-opus-4-8 (low)
No inputs directory mentioned beyond the prompt. Here's the solution.

import re


def int_to_roman(n):
    if not isinstance(n, int) or isinstance(n, bool):
        raise ValueError("input must be an integer")
    if n < 1 or n > 3999:
        raise ValueError("integer out of range 1..3999")
    table = [
        (1000, "M"), (900, "CM"), (500, "D"), (400, "CD"),
        (100, "C"), (90, "XC"), (50, "L"), (40, "XL"),
        (10, "X"), (9, "IX"), (5, "V"), (4, "IV"), (1, "I"),
    ]
    result = []
    for value, symbol in table:
        count, n = divmod(n, value)
        result.append(symbol * count)
    return "".join(result)


_CANONICAL_RE = re.compile(
    r"^M{0,3}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$"
)

_VALUES = {"I": 1, "V": 5, "X": 10, "L": 50, "C": 100, "D": 500, "M": 1000}


def roman_to_int(s):
    if not isinstance(s, str):
        raise ValueError("input must be a string")
    if s == "":
        raise ValueError("empty string is not a valid Roman numeral")
    if not _CANONICAL_RE.match(s):
        raise ValueError("malformed or non-canonical Roman numeral: %r" % s)
    total = 0
    prev = 0
    for ch in reversed(s):
        value = _VALUES[ch]
        if value < prev:
            total -= value
        else:
            total += value
            prev = value
    return total