// 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; }