245 lines
8.1 KiB
Python
245 lines
8.1 KiB
Python
#!/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()
|
|
|