"""Temperature conversion utilities for SNS Network Solutions.""" from enum import Enum class TemperatureUnit(str, Enum): """Supported temperature units.""" CELSIUS = "Celsius" FAHRENHEIT = "Fahrenheit" KELVIN = "Kelvin" # Absolute zero in Celsius — used for Kelvin validation _ABSOLUTE_ZERO_CELSIUS = -273.15 _ABSOLUTE_ZERO_FAHRENHEIT = -459.67 def convert_temperature( value: float, from_unit: str, to_unit: str, ) -> float: """Convert a temperature value between Celsius, Fahrenheit, and Kelvin. Validates that the input value is physically possible (i.e., not below absolute zero for the given unit) and that both unit strings are recognised before performing the conversion. Args: value: The numeric temperature to convert. from_unit: The unit of the supplied value. Must be one of ``'Celsius'``, ``'Fahrenheit'``, or ``'Kelvin'``. to_unit: The target unit for the result. Same allowed values as *from_unit*. Returns: The converted temperature as a ``float``, rounded to six decimal places to avoid floating-point noise. Raises: TypeError: If *value* is not an ``int`` or ``float``. ValueError: If either unit string is not recognised, or if *value* is below absolute zero for the given *from_unit*. Examples: >>> convert_temperature(100, 'Celsius', 'Fahrenheit') 212.0 >>> convert_temperature(32, 'Fahrenheit', 'Celsius') 0.0 >>> convert_temperature(0, 'Kelvin', 'Celsius') -273.15 >>> convert_temperature(300, 'Kelvin', 'Fahrenheit') 80.33 """ # --- type guard --- if not isinstance(value, (int, float)): raise TypeError( f"'value' must be a numeric type (int or float), got {type(value).__name__!r}." ) # --- unit validation (guard clauses) --- valid_units = {u.value for u in TemperatureUnit} if from_unit not in valid_units: raise ValueError( f"Unrecognised from_unit {from_unit!r}. " f"Allowed values: {sorted(valid_units)}." ) if to_unit not in valid_units: raise ValueError( f"Unrecognised to_unit {to_unit!r}. " f"Allowed values: {sorted(valid_units)}." ) # --- physical validity guard --- _validate_above_absolute_zero(value, from_unit) # --- trivial case --- if from_unit == to_unit: return float(value) # --- convert to Celsius as the common intermediate --- celsius = _to_celsius(value, from_unit) # --- convert from Celsius to the target unit --- result = _from_celsius(celsius, to_unit) return round(result, 6) # --------------------------------------------------------------------------- # Private helpers # --------------------------------------------------------------------------- def _validate_above_absolute_zero(value: float, unit: str) -> None: """Raise ValueError if *value* is below absolute zero for *unit*. Args: value: The temperature value to check. unit: The unit string (already validated as a recognised unit). Raises: ValueError: When *value* is physically impossible. """ if unit == TemperatureUnit.CELSIUS and value < _ABSOLUTE_ZERO_CELSIUS: raise ValueError( f"Temperature {value} °C is below absolute zero " f"({_ABSOLUTE_ZERO_CELSIUS} °C)." ) if unit == TemperatureUnit.FAHRENHEIT and value < _ABSOLUTE_ZERO_FAHRENHEIT: raise ValueError( f"Temperature {value} °F is below absolute zero " f"({_ABSOLUTE_ZERO_FAHRENHEIT} °F)." ) if unit == TemperatureUnit.KELVIN and value < 0: raise ValueError( f"Temperature {value} K is below absolute zero (0 K)." ) def _to_celsius(value: float, from_unit: str) -> float: """Convert *value* (in *from_unit*) to degrees Celsius. Args: value: Source temperature. from_unit: Source unit (pre-validated). Returns: Equivalent temperature in Celsius. """ if from_unit == TemperatureUnit.CELSIUS: return float(value) if from_unit == TemperatureUnit.FAHRENHEIT: # °C = (°F − 32) × 5/9 return (value - 32) * 5 / 9 # Kelvin → °C = K − 273.15 return value - 273.15 def _from_celsius(celsius: float, to_unit: str) -> float: """Convert a Celsius value to *to_unit*. Args: celsius: Temperature in degrees Celsius. to_unit: Target unit (pre-validated). Returns: Equivalent temperature in *to_unit*. """ if to_unit == TemperatureUnit.CELSIUS: return celsius if to_unit == TemperatureUnit.FAHRENHEIT: # °F = °C × 9/5 + 32 return celsius * 9 / 5 + 32 # °C → K = °C + 273.15 return celsius + 273.15