feat: Add the Python project
This commit is contained in:
159
src/value_sampler.py
Normal file
159
src/value_sampler.py
Normal file
@@ -0,0 +1,159 @@
|
||||
import random
|
||||
import decimal
|
||||
from src.data_loader import get_value_ranges
|
||||
|
||||
# Cache loaded data to avoid repeated file I/O
|
||||
VALUE_RANGES = None
|
||||
|
||||
def _get_value_ranges_cached():
|
||||
"""Returns cached value_ranges data, loading if not already cached."""
|
||||
global VALUE_RANGES
|
||||
if VALUE_RANGES is None:
|
||||
VALUE_RANGES = get_value_ranges()
|
||||
return VALUE_RANGES
|
||||
|
||||
def get_random_float(min_val, max_val, precision=None):
|
||||
"""Generates a random float between min_val and max_val with specified precision."""
|
||||
val = random.uniform(min_val, max_val)
|
||||
if precision is not None:
|
||||
# Using Decimal for precise rounding
|
||||
return float(decimal.Decimal(str(val)).quantize(decimal.Decimal('1e-' + str(precision)), rounding=decimal.ROUND_HALF_UP))
|
||||
return val
|
||||
|
||||
def get_random_int(min_val, max_val):
|
||||
"""Generates a random integer between min_val and max_val (inclusive)."""
|
||||
return random.randint(min_val, max_val)
|
||||
|
||||
def get_value_for_variable(variable_key):
|
||||
"""
|
||||
Generates a random value for a given variable key based on value_ranges.json.
|
||||
Returns a dictionary containing the value and its unit/currency if applicable.
|
||||
Example: {'value': 15000.50, 'currency': 'Php', 'display_str': 'Php 15,000.50'}
|
||||
{'value': 0.12, 'unit_display': '% per annum', 'display_str': '12.00% per annum'}
|
||||
{'value': 5, 'unit': 'years', 'display_str': '5 years'}
|
||||
"""
|
||||
ranges = _get_value_ranges_cached()
|
||||
if not ranges or variable_key not in ranges:
|
||||
print(f"Error: No range definition found for variable key '{variable_key}' in value_ranges.json")
|
||||
return None
|
||||
|
||||
config = ranges[variable_key]
|
||||
val = None
|
||||
result = {'key': variable_key}
|
||||
|
||||
if config.get("integer", False):
|
||||
val = get_random_int(config["min"], config["max"])
|
||||
result['value'] = val
|
||||
else:
|
||||
# For floats, use internal_precision for generation if available, otherwise default to a reasonable precision
|
||||
# Display precision will be handled separately or by a formatting function
|
||||
internal_precision = config.get("internal_precision", config.get("decimals", 4)) # Default to 4 if no precision specified
|
||||
val = get_random_float(config["min"], config["max"], internal_precision)
|
||||
result['value'] = val
|
||||
|
||||
if "currency" in config:
|
||||
result["currency"] = config["currency"]
|
||||
if "unit" in config:
|
||||
result["unit"] = config["unit"]
|
||||
if "unit_display" in config: # For rates like "% per annum"
|
||||
result["unit_display"] = config["unit_display"]
|
||||
|
||||
# Store precision details for later formatting
|
||||
result["display_precision"] = config.get("display_precision", config.get("decimals")) # Decimals is often used for currency
|
||||
|
||||
return result
|
||||
|
||||
def get_random_compounding_frequency():
|
||||
"""Selects a random compounding frequency and its 'm' value."""
|
||||
ranges = _get_value_ranges_cached()
|
||||
if not ranges or "compounding_frequency_options" not in ranges:
|
||||
print("Error: 'compounding_frequency_options' not found in value_ranges.json")
|
||||
return None, None
|
||||
|
||||
options = ranges["compounding_frequency_options"]
|
||||
frequency_name = random.choice(list(options.keys()))
|
||||
m_value = options[frequency_name]
|
||||
return {"name": frequency_name, "m_value": m_value, "key": "m_compounding_periods_per_year", "value": m_value}
|
||||
|
||||
|
||||
def format_value_for_display(value_data):
|
||||
"""
|
||||
Formats a value generated by get_value_for_variable for display.
|
||||
Example input: {'key': 'principal', 'value': 15000.5, 'currency': 'Php', 'display_precision': 2}
|
||||
Example output: "Php 15,000.50"
|
||||
Example input: {'key': 'simple_interest_rate_annual', 'value': 0.12, 'unit_display': '% per annum', 'display_precision': 2}
|
||||
Example output: "12.00% per annum"
|
||||
"""
|
||||
if not isinstance(value_data, dict) or 'value' not in value_data:
|
||||
return str(value_data) # Fallback
|
||||
|
||||
val = value_data['value']
|
||||
display_precision = value_data.get('display_precision')
|
||||
|
||||
# Format number with precision
|
||||
if display_precision is not None and isinstance(val, (float, decimal.Decimal)):
|
||||
# Check if it's a rate to be displayed as percentage
|
||||
if "unit_display" in value_data and "%" in value_data["unit_display"]:
|
||||
formatted_num = f"{val * 100:.{display_precision}f}"
|
||||
else:
|
||||
formatted_num = f"{val:.{display_precision}f}"
|
||||
# Add thousands separator for currency-like values
|
||||
if "currency" in value_data and val >= 1000:
|
||||
parts = formatted_num.split('.')
|
||||
parts[0] = "{:,}".format(int(parts[0].replace(',', ''))) # Apply to integer part
|
||||
formatted_num = ".".join(parts)
|
||||
|
||||
elif isinstance(val, int):
|
||||
formatted_num = str(val)
|
||||
if "currency" in value_data and val >= 1000:
|
||||
formatted_num = "{:,}".format(val)
|
||||
else:
|
||||
formatted_num = str(val)
|
||||
|
||||
|
||||
if "currency" in value_data:
|
||||
return f"{value_data['currency']} {formatted_num}"
|
||||
elif "unit_display" in value_data: # e.g., "% per annum"
|
||||
return f"{formatted_num}{value_data['unit_display']}" # Assumes % is part of unit_display or handled by *100
|
||||
elif "unit" in value_data: # e.g., "years"
|
||||
return f"{formatted_num} {value_data['unit']}"
|
||||
else:
|
||||
return formatted_num
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("Testing Value Sampler:")
|
||||
|
||||
# Test get_value_for_variable
|
||||
print("\n--- Testing get_value_for_variable ---")
|
||||
principal_data = get_value_for_variable("principal")
|
||||
if principal_data:
|
||||
print(f"Generated Principal Data: {principal_data}")
|
||||
print(f"Formatted Principal: {format_value_for_display(principal_data)}")
|
||||
|
||||
rate_data = get_value_for_variable("simple_interest_rate_annual")
|
||||
if rate_data:
|
||||
print(f"Generated Rate Data: {rate_data}")
|
||||
print(f"Formatted Rate: {format_value_for_display(rate_data)}")
|
||||
|
||||
time_data = get_value_for_variable("time_years")
|
||||
if time_data:
|
||||
print(f"Generated Time Data: {time_data}")
|
||||
print(f"Formatted Time: {format_value_for_display(time_data)}")
|
||||
|
||||
# Test get_random_compounding_frequency
|
||||
print("\n--- Testing get_random_compounding_frequency ---")
|
||||
comp_freq = get_random_compounding_frequency()
|
||||
if comp_freq:
|
||||
print(f"Generated Compounding Frequency: {comp_freq}")
|
||||
|
||||
# Test edge cases or specific formatting
|
||||
print("\n--- Testing Specific Formatting ---")
|
||||
test_currency_large = {'key': 'principal', 'value': 1234567.89, 'currency': 'Php', 'display_precision': 2}
|
||||
print(f"Large Currency: {format_value_for_display(test_currency_large)}")
|
||||
test_currency_small = {'key': 'principal', 'value': 123.45, 'currency': 'Php', 'display_precision': 2}
|
||||
print(f"Small Currency: {format_value_for_display(test_currency_small)}")
|
||||
test_rate_percent = {'key': 'simple_interest_rate_annual', 'value': 0.085, 'unit_display': '% per annum', 'display_precision': 2}
|
||||
print(f"Rate as Percent: {format_value_for_display(test_rate_percent)}")
|
||||
test_integer_unit = {'key': 'time_years', 'value': 5, 'unit': 'years'}
|
||||
print(f"Integer with Unit: {format_value_for_display(test_integer_unit)}")
|
||||
Reference in New Issue
Block a user