// Check production transactions
  @Test
  public void testProduction() {
    Instant exp = now.plus(TimeService.WEEK * 10);
    TariffSpecification tariffSpec =
        new TariffSpecification(broker, PowerType.PRODUCTION)
            .withExpiration(exp)
            .withMinDuration(TimeService.WEEK * 4)
            .withSignupPayment(-34.2)
            .withEarlyWithdrawPayment(35.0)
            .addRate(new Rate().withValue(0.102));
    tariff = new Tariff(tariffSpec);
    tariff.init();

    // subscribe and consume in the first timeslot
    TariffSubscription tsub = tariffMarketService.subscribeToTariff(tariff, customer, 4);
    assertEquals("four customers committed", 4, tsub.getCustomersCommitted());
    tsub.usePower(-244.6); // production
    assertEquals("correct total usage", -244.6 / 4, tsub.getTotalUsage(), 1e-6);
    assertEquals("correct realized price", 0.102, tariff.getRealizedPrice(), 1e-6);
    // def txs = TariffTransaction.findAllByPostedTime(timeService.currentTime);
    // assertEquals("two transactions", 2, txs.size())
    // TariffTransaction ttx = TariffTransaction.findByPostedTimeAndTxType(timeService.currentTime,
    // TariffTransactionType.SIGNUP)
    // assertNotNull("found signup tx", ttx)
    // assertEquals("correct charge", -34.2 * 4, ttx.charge, 1e-6)
    // ttx = TariffTransaction.findByPostedTimeAndTxType(timeService.currentTime,
    // TariffTransactionType.PRODUCE)
    // assertNotNull("found production tx", ttx)
    // assertEquals("correct amount", -244.6, ttx.quantity)
    // assertEquals("correct charge", -0.102 * 244.6, ttx.charge, 1e-6)
  }
 // create a Subscription from a Tariff
 @Test
 public void testSimpleSub() {
   TariffSubscription ts = tariffMarketService.subscribeToTariff(tariff, customer, 3);
   assertNotNull("non-null subscription", ts);
   assertEquals("correct customer", customer, ts.getCustomer());
   assertEquals("correct tariff", tariff, ts.getTariff());
   assertEquals("correct customer count", 3, ts.getCustomersCommitted());
 }
  // subscription with non-zero signup bonus
  @Test
  public void testSignupBonus() {
    Instant exp = now.plus(TimeService.WEEK * 10);
    TariffSpecification tariffSpec =
        new TariffSpecification(broker, PowerType.CONSUMPTION)
            .withExpiration(exp)
            .withMinDuration(TimeService.WEEK * 4)
            .withSignupPayment(-33.2)
            .addRate(new Rate().withValue(0.121));
    tariff = new Tariff(tariffSpec);
    tariff.init();

    TariffSubscription tsub = tariffMarketService.subscribeToTariff(tariff, customer, 5);
    assertNotNull("non-null subscription", tsub);
    assertEquals("five customers committed", 5, tsub.getCustomersCommitted());
    verify(mockAccounting)
        .addTariffTransaction(
            TariffTransaction.Type.SIGNUP, tariff, customer.getCustomerInfo(), 5, 0.0, -33.2 * 5);
  }
 private void usePower(Timeslot timeslot) {
   for (CapacityBundle bundle : capacityBundles) {
     List<TariffSubscription> subscriptions =
         getTariffSubscriptionRepo().findActiveSubscriptionsForCustomer(bundle.getCustomerInfo());
     double totalCapacity = 0.0;
     double totalUsageCharge = 0.0;
     for (TariffSubscription subscription : subscriptions) {
       double usageSign = bundle.getPowerType().isConsumption() ? +1 : -1;
       double currCapacity = usageSign * useCapacity(subscription, bundle);
       if (Config.getInstance().isUsageChargesLogging()) {
         double charge =
             subscription
                 .getTariff()
                 .getUsageCharge(currCapacity, subscription.getTotalUsage(), false);
         totalUsageCharge += charge;
       }
       subscription.usePower(currCapacity);
       totalCapacity += currCapacity;
     }
     log.info(
         bundle.getName()
             + ": Total "
             + bundle.getPowerType()
             + " capacity for timeslot "
             + timeslot.getSerialNumber()
             + " = "
             + totalCapacity);
     logUsageCharges(
         bundle.getName()
             + ": Total "
             + bundle.getPowerType()
             + " usage charge for timeslot "
             + timeslot.getSerialNumber()
             + " = "
             + totalUsageCharge);
   }
 }
  // Check two-part tariff
  @Test
  public void testTwoPart() {
    Instant exp = now.plus(TimeService.WEEK * 10);
    TariffSpecification tariffSpec =
        new TariffSpecification(broker, PowerType.CONSUMPTION)
            .withExpiration(exp)
            .withMinDuration(TimeService.WEEK * 4)
            .withSignupPayment(-31.2)
            .withPeriodicPayment(1.3)
            .addRate(new Rate().withValue(0.112));
    tariff = new Tariff(tariffSpec);
    tariff.init();

    // subscribe and consume in the first timeslot
    TariffSubscription tsub = tariffMarketService.subscribeToTariff(tariff, customer, 6);
    assertEquals("six customers committed", 6, tsub.getCustomersCommitted());
    tsub.usePower(28.8); // consumption
    assertEquals("correct total usage", 28.8 / 6, tsub.getTotalUsage(), 1e-6);
    assertEquals(
        "correct realized price", (0.112 * 28.8 + 6 * 1.3) / 28.8, tariff.getRealizedPrice(), 1e-6);
    // def txs = TariffTransaction.findAllByPostedTime(timeService.currentTime);
    // assertEquals("two transactions", 3, txs.size())
    // TariffTransaction ttx = TariffTransaction.findByPostedTimeAndTxType(timeService.currentTime,
    // TariffTransactionType.SIGNUP)
    // assertNotNull("found signup tx", ttx)
    // assertEquals("correct charge", -31.2 * 6, ttx.charge, 1e-6)
    // ttx = TariffTransaction.findByPostedTimeAndTxType(timeService.currentTime,
    // TariffTransactionType.CONSUME)
    // assertNotNull("found consumption tx", ttx)
    // assertEquals("correct amount", 28.8, ttx.quantity)
    // assertEquals("correct charge", 0.112 * 28.8, ttx.charge, 1e-6)
    // ttx = TariffTransaction.findByPostedTimeAndTxType(timeService.currentTime,
    // TariffTransactionType.PERIODIC)
    // assertNotNull("found periodoc tx", ttx)
    // assertEquals("correct charge", 6 * 1.3, ttx.charge, 1e-6)
  }
  // subscription withdrawal without and with penalty
  @Test
  public void testEarlyWithdraw() {
    Instant exp = now.plus(TimeService.WEEK * 10);
    TariffSpecification tariffSpec =
        new TariffSpecification(broker, PowerType.CONSUMPTION)
            .withExpiration(exp)
            .withMinDuration(TimeService.WEEK * 4)
            .withSignupPayment(-33.2)
            .withEarlyWithdrawPayment(42.1)
            .addRate(new Rate().withValue(0.121));
    tariff = new Tariff(tariffSpec);
    tariff.init();
    TariffSubscription tsub = tariffMarketService.subscribeToTariff(tariff, customer, 5);

    // move time forward 2 weeks, withdraw 2 customers
    Instant wk2 = now.plus(TimeService.WEEK * 2);
    timeService.setCurrentTime(wk2);
    tsub.unsubscribe(2);
    verify(mockAccounting)
        .addTariffTransaction(
            TariffTransaction.Type.WITHDRAW, tariff, customer.getCustomerInfo(), 2, 0.0, 42.1 * 2);
    // def txs = TariffTransaction.findAllByPostedTime(wk2)
    // assertEquals("one transaction", 1, txs.size())
    // assertEquals("correct txType", TariffTransactionType.WITHDRAW, txs[0].txType)
    // assertEquals("correct charge", 42.1*2, txs[0].charge)
    assertEquals("three customers committed", 3, tsub.getCustomersCommitted());

    // move time forward another week, add 4 customers and drop 1
    Instant wk3 = now.plus(TimeService.WEEK * 2 + TimeService.HOUR * 6);
    timeService.setCurrentTime(wk3);
    TariffSubscription tsub1 = tariffMarketService.subscribeToTariff(tariff, customer, 4);
    assertEquals("same subscription", tsub, tsub1);
    tsub1.unsubscribe(1);
    // txs = TariffTransaction.findAllByPostedTime(wk3)
    // assertEquals("two transactions", 2, txs.size())
    // TariffTransaction ttx = TariffTransaction.findByPostedTimeAndTxType(timeService.currentTime,
    //
    // TariffTransactionType.SIGNUP)
    // assertNotNull("found signup tx", ttx)
    // assertEquals("correct charge", -33.2 * 4, ttx.charge)
    // ttx = TariffTransaction.findByPostedTimeAndTxType(timeService.currentTime,
    //                                                  TariffTransactionType.WITHDRAW)
    // assertNotNull("found withdraw tx", ttx)
    // assertEquals("correct charge", 42.1, ttx.charge)
    assertEquals("six customers committed", 6, tsub1.getCustomersCommitted());
  }
  // Check consumption transactions
  @Test
  public void testConsumption() {
    Instant exp = now.plus(TimeService.WEEK * 10);
    TariffSpecification tariffSpec =
        new TariffSpecification(broker, PowerType.CONSUMPTION)
            .withExpiration(exp)
            .withMinDuration(TimeService.WEEK * 4)
            .withSignupPayment(-33.2)
            .addRate(new Rate().withValue(0.121));
    tariff = new Tariff(tariffSpec);
    tariff.init();

    // subscribe and consume in the first timeslot
    TariffSubscription tsub = tariffMarketService.subscribeToTariff(tariff, customer, 4);
    assertEquals("four customers committed", 4, tsub.getCustomersCommitted());
    tsub.usePower(24.4); // consumption
    assertEquals("correct total usage", 24.4 / 4, tsub.getTotalUsage(), 1e-6);
    assertEquals("correct realized price", 0.121, tariff.getRealizedPrice(), 1e-6);
    // def txs = TariffTransaction.findAllByPostedTime(timeService.getCurrentTime());
    // assertEquals("two transactions", 2, txs.size())
    // TariffTransaction ttx =
    //    TariffTransaction.findByPostedTimeAndTxType(timeService.currentTime,
    // TariffTransactionType.SIGNUP)
    // assertNotNull("found signup tx", ttx)
    // assertEquals("correct charge", -33.2 * 4, ttx.charge, 1e-6)
    // ttx = TariffTransaction.findByPostedTimeAndTxType(timeService.currentTime,
    // TariffTransactionType.CONSUME)
    // assertNotNull("found consumption tx", ttx)
    // assertEquals("correct amount", 24.4, ttx.quantity)
    // assertEquals("correct charge", 0.121 * 24.4, ttx.charge, 1e-6)

    // just consume in the second timeslot
    Instant hour = now.plus(TimeService.HOUR);
    timeService.setCurrentTime(hour);
    tsub.usePower(32.8); // consumption
    assertEquals("correct total usage", (24.4 + 32.8) / 4, tsub.getTotalUsage(), 1e-6);
    assertEquals("correct realized price", 0.121, tariff.getRealizedPrice(), 1e-6);
    // txs = TariffTransaction.findAllByPostedTime(timeService.getCurrentTime())
    // assertEquals("one transaction", 1, txs.size())
    // ttx = TariffTransaction.findByPostedTimeAndTxType(timeService.getCurrentTime(),
    // TariffTransactionType.CONSUME)
    // assertNotNull("found consumption tx", ttx)
    // assertEquals("correct amount", 32.8, ttx.quantity)
    // assertEquals("correct charge", 0.121 * 32.8, ttx.charge, 1e-6)
  }
  // Computes energy use by chargers in the current timeslot.
  // Remember that the plan has computed usage in terms of AC power,
  // while the energy going into the batteries is lower by
  // the chargeEfficiency value.
  double useEnergy(double regulation) {
    TariffSubscription subscription = getSubscription();
    Tariff tariff = subscription.getTariff();
    ensureCapacityPlan(tariff);

    // positive regulation means we lost energy in the last timeslot
    // and should make it up in the remainder of the shift
    addEnergyCharging(-regulation * chargeEfficiency);
    ShiftEnergy need = plan.getCurrentNeed(getNowInstant());
    if (need.getDuration() <= 0) {
      log.error(getName() + " negative need duration " + need.getDuration());
    }
    need.addEnergy(-regulation);

    // Compute the max and min we could possibly use in this timeslot
    // -- start with max and avail for remainder of shift
    double max = nChargers * maxChargeKW * need.getDuration(); //  shift
    double avail = // for remainder of shift
        nBatteries * batteryCapacity - getCapacityInUse() - getEnergyCharging();
    double maxUsable = Math.min(max, avail) / chargeEfficiency;
    double needed = need.getEnergyNeeded();

    double used = 0;
    RegulationCapacity regCapacity = null;
    if (needed >= maxUsable) {
      // we just use the max, and allow no regulation capacity
      log.info(
          getName()
              + ": no slack - need "
              + needed
              + ", max "
              + max
              + ", avail "
              + avail
              + ", dur "
              + need.getDuration());
      used = Math.min(maxUsable, (needed / need.getDuration()));
      regCapacity = new RegulationCapacity(subscription, 0.0, 0.0);
    } else if (tariff.isTimeOfUse() || tariff.isVariableRate()) {
      // if the current tariff is not a flat rate, we will just use the
      // planned amout, without offering regulation capacity
      // TODO - figure out how to combine variable prices with regulation
      used = need.getRecommendedUsage()[need.getUsageIndex()];
      regCapacity = new RegulationCapacity(subscription, 0.0, 0.0);
    } else {
      // otherwise use energy to maximize regulation capacity
      double slack = (maxUsable - needed) / need.getDuration() / 2.0;
      log.info(
          getName()
              + " needed "
              + needed
              + ", maxUsable "
              + maxUsable
              + ", duration "
              + need.getDuration());
      used = needed / need.getDuration() + slack;
      regCapacity = new RegulationCapacity(subscription, slack, -slack);
    }

    // use it
    addEnergyCharging(used * chargeEfficiency);
    getSubscription().setRegulationCapacity(regCapacity);
    log.info(
        getName()
            + " uses "
            + used
            + "kWh, reg cap ("
            + regCapacity.getUpRegulationCapacity()
            + ", "
            + regCapacity.getDownRegulationCapacity()
            + ")");
    need.tick();
    need.addEnergy(used);
    return used;
  }