#!/usr/bin/env python3 """ Convert Excel balance sheet back to SimulationConfig. Reads parameter values from the Excel file and generates a config.json. Usage: python excel_to_config.py [--input balance_sheet.xlsx] [--output config.json] """ import argparse import json import sys from pathlib import Path # Add parent directory to path for imports sys.path.insert(0, str(Path(__file__).parent.parent)) from openpyxl import load_workbook from backend.config import ( SimulationConfig, AgentStatsConfig, ResourceConfig, ActionConfig, WorldConfig, MarketConfig, EconomyConfig, ) # Parameter to config section mapping PARAM_MAPPING = { # AgentStatsConfig 'max_energy': ('agent_stats', int), 'max_hunger': ('agent_stats', int), 'max_thirst': ('agent_stats', int), 'max_heat': ('agent_stats', int), 'start_energy': ('agent_stats', int), 'start_hunger': ('agent_stats', int), 'start_thirst': ('agent_stats', int), 'start_heat': ('agent_stats', int), 'energy_decay': ('agent_stats', int), 'hunger_decay': ('agent_stats', int), 'thirst_decay': ('agent_stats', int), 'heat_decay': ('agent_stats', int), 'critical_threshold': ('agent_stats', float), 'low_energy_threshold': ('agent_stats', int), # ResourceConfig 'meat_decay': ('resources', int), 'berries_decay': ('resources', int), 'clothes_decay': ('resources', int), 'meat_hunger': ('resources', int), 'meat_energy': ('resources', int), 'berries_hunger': ('resources', int), 'berries_thirst': ('resources', int), 'water_thirst': ('resources', int), 'fire_heat': ('resources', int), # ActionConfig 'sleep_energy': ('actions', int), 'rest_energy': ('actions', int), 'hunt_energy': ('actions', int), 'gather_energy': ('actions', int), 'chop_wood_energy': ('actions', int), 'get_water_energy': ('actions', int), 'weave_energy': ('actions', int), 'build_fire_energy': ('actions', int), 'trade_energy': ('actions', int), 'hunt_success': ('actions', float), 'chop_wood_success': ('actions', float), 'hunt_meat_min': ('actions', int), 'hunt_meat_max': ('actions', int), 'hunt_hide_min': ('actions', int), 'hunt_hide_max': ('actions', int), 'gather_min': ('actions', int), 'gather_max': ('actions', int), 'chop_wood_min': ('actions', int), 'chop_wood_max': ('actions', int), # WorldConfig 'width': ('world', int), 'height': ('world', int), 'initial_agents': ('world', int), 'day_steps': ('world', int), 'night_steps': ('world', int), 'inventory_slots': ('world', int), 'starting_money': ('world', int), # MarketConfig 'turns_before_discount': ('market', int), 'discount_rate': ('market', float), 'base_price_multiplier': ('market', float), # EconomyConfig 'energy_to_money_ratio': ('economy', float), 'wealth_desire': ('economy', float), 'buy_efficiency_threshold': ('economy', float), 'min_wealth_target': ('economy', int), 'max_price_markup': ('economy', float), 'min_price_discount': ('economy', float), # SimulationConfig (root level) 'auto_step_interval': ('root', float), } def parse_excel(input_path: str) -> dict: """Parse the Excel balance sheet and extract parameter values.""" wb = load_workbook(input_path, data_only=True) # data_only=True to get calculated values ws = wb.active # Organize parameters by section config_data = { 'agent_stats': {}, 'resources': {}, 'actions': {}, 'world': {}, 'market': {}, 'economy': {}, 'auto_step_interval': 1.0, } found_params = [] skipped_rows = [] # Iterate through rows looking for parameter names in column A for row in range(1, ws.max_row + 1): param_name = ws.cell(row=row, column=1).value param_value = ws.cell(row=row, column=2).value # Skip empty rows or non-parameter rows if not param_name or not isinstance(param_name, str): continue # Strip whitespace param_name = param_name.strip() # Check if this is a known parameter if param_name in PARAM_MAPPING: section, type_converter = PARAM_MAPPING[param_name] # Skip if value is None or empty if param_value is None: skipped_rows.append(f" ⚠️ {param_name}: no value found (row {row})") continue try: # Convert to appropriate type converted_value = type_converter(param_value) if section == 'root': config_data['auto_step_interval'] = converted_value else: config_data[section][param_name] = converted_value found_params.append(f" ✓ {param_name} = {converted_value}") except (ValueError, TypeError) as e: skipped_rows.append(f" ⚠️ {param_name}: conversion error ({e})") return config_data, found_params, skipped_rows def create_config_from_data(config_data: dict) -> SimulationConfig: """Create a SimulationConfig from parsed data.""" return SimulationConfig( agent_stats=AgentStatsConfig(**config_data['agent_stats']), resources=ResourceConfig(**config_data['resources']), actions=ActionConfig(**config_data['actions']), world=WorldConfig(**config_data['world']), market=MarketConfig(**config_data['market']), economy=EconomyConfig(**config_data['economy']), auto_step_interval=config_data['auto_step_interval'], ) def save_config_json(config: SimulationConfig, output_path: str): """Save config to JSON file.""" with open(output_path, 'w') as f: json.dump(config.to_dict(), f, indent=2) def main(): parser = argparse.ArgumentParser(description="Convert Excel balance sheet to config") parser.add_argument("--input", "-i", default="balance_sheet.xlsx", help="Input Excel file path") parser.add_argument("--output", "-o", default="config.json", help="Output JSON config file path") parser.add_argument("--verbose", "-v", action="store_true", help="Show detailed parsing output") args = parser.parse_args() # Check if input file exists if not Path(args.input).exists(): print(f"❌ Error: Input file not found: {args.input}") sys.exit(1) print(f"📂 Reading Excel file: {args.input}") # Parse Excel config_data, found_params, skipped_rows = parse_excel(args.input) if args.verbose: print("\n📊 Found parameters:") for param in found_params: print(param) if skipped_rows: print("\n⚠️ Skipped rows:") for skip in skipped_rows: print(skip) # Create config try: config = create_config_from_data(config_data) except TypeError as e: print(f"❌ Error creating config: {e}") print(" Some required parameters may be missing from the Excel file.") sys.exit(1) # Save to JSON save_config_json(config, args.output) # Summary sections = ['agent_stats', 'resources', 'actions', 'world', 'market', 'economy'] total_params = sum(len(config_data[k]) for k in sections) total_params += 1 # auto_step_interval print(f"\n✅ Config saved to: {args.output}") print(f" 📊 {total_params} parameters imported") print(f" 🔧 {len(skipped_rows)} rows skipped") # Print summary of values by section print("\n📋 Configuration summary:") print(f" 👤 Agent Stats: {len(config_data['agent_stats'])} params") print(f" 🍖 Resources: {len(config_data['resources'])} params") print(f" ⚡ Actions: {len(config_data['actions'])} params") print(f" 🌍 World: {len(config_data['world'])} params") print(f" 💰 Market: {len(config_data['market'])} params") print(f" 📈 Economy: {len(config_data['economy'])} params") if __name__ == "__main__": main()