DefaultUtilityOptimizer(CustomerStructure customerStructure, List<CapacityBundle> bundles) {
    this.customerStructure = customerStructure;
    this.capacityBundles = bundles;
    this.evaluatorMap = new HashMap<>();

    // create evaluation wrappers and tariff evaluators for each bundle
    for (CapacityBundle bundle : bundles) {
      TariffSubscriberStructure subStructure = bundle.getSubscriberStructure();
      TariffEvaluator evaluator =
          new TariffEvaluator(new TariffEvaluationWrapper(bundle))
              .withChunkSize(Math.max(1, bundle.getPopulation() / 1000))
              .withTariffSwitchFactor(subStructure.getTariffSwitchFactor())
              .withPreferredContractDuration(subStructure.getExpectedDuration())
              .withInconvenienceWeight(subStructure.getInconvenienceWeight())
              .withRationality(subStructure.getLogitChoiceRationality())
              .withEvaluateAllTariffs(true);
      evaluator.initializeCostFactors(
          subStructure.getExpMeanPriceWeight(),
          subStructure.getMaxValuePriceWeight(),
          subStructure.getRealizedPriceWeight(),
          subStructure.getTariffVolumeThreshold());
      evaluator.initializeInconvenienceFactors(
          subStructure.getTouFactor(),
          subStructure.getTieredRateFactor(),
          subStructure.getVariablePricingFactor(),
          subStructure.getInterruptibilityFactor());
      evaluatorMap.put(bundle, evaluator);
    }
  }
  /**
   * Initialization must provide accessor to Customer instance and time. We assume configuration has
   * already happened. We also start with no active trucks, and the weakest batteries on
   * availableChargers. Trucks will not be active (in bootstrap mode) until the first shift change.
   */
  @Override
  public void initialize() {
    super.initialize();
    log.info("Initialize " + name);
    // fill out CustomerInfo. We label this model as thermal storage
    // because we don't allow battery discharge
    powerType = PowerType.THERMAL_STORAGE_CONSUMPTION;
    CustomerInfo info = new CustomerInfo(name, 1);
    // conservative interruptible capacity
    double interruptible = Math.min(nChargers * maxChargeKW, nBatteries * maxChargeKW / 3.0);
    info.withPowerType(powerType)
        .withCustomerClass(CustomerClass.LARGE)
        .withControllableKW(-interruptible)
        .withStorageCapacity(nBatteries * maxChargeKW / 3.0)
        .withUpRegulationKW(-nChargers * maxChargeKW)
        .withDownRegulationKW(nChargers * maxChargeKW); // optimistic, perhaps
    addCustomerInfo(info);
    ensureSeeds();

    // use default values when not configured
    ensureShifts();

    // make sure we have enough batteries and availableChargers
    validateBatteries();
    validateChargers();

    // all batteries are charging
    // energyCharging = getStateOfCharge() * getBatteryCapacity();
    // capacityInUse = 0.0;
    // energyInUse = 0.0;

    // set up the tariff evaluator. We are wide-open to variable pricing.
    tariffEvaluator = new TariffEvaluator(this);
    tariffEvaluator.withInertia(0.7).withPreferredContractDuration(14);
    tariffEvaluator.initializeInconvenienceFactors(0.0, 0.01, 0.0, 0.0);
    tariffEvaluator.initializeRegulationFactors(
        -nChargers * maxChargeKW * 0.05, 0.0, nChargers * maxChargeKW * 0.04);
  }