All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.powertac.tariffmarket.TariffMarketService Maven / Gradle / Ivy

/*
 * Copyright 2011 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an
 * "AS IS" BASIS,  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package org.powertac.tariffmarket;

import static org.powertac.util.MessageDispatcher.dispatch;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import org.apache.log4j.Logger;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.Instant;
import org.powertac.common.Competition;
import org.powertac.common.CustomerInfo;
import org.powertac.common.Broker;
import org.powertac.common.RandomSeed;
import org.powertac.common.Tariff;
import org.powertac.common.TariffMessage;
import org.powertac.common.TariffSpecification;
import org.powertac.common.TariffSubscription;
import org.powertac.common.TariffTransaction;
import org.powertac.common.TimeService;
import org.powertac.common.config.ConfigurableValue;
import org.powertac.common.enumerations.PowerType;
import org.powertac.common.interfaces.Accounting;
import org.powertac.common.interfaces.BrokerMessageListener;
import org.powertac.common.interfaces.BrokerProxy;
import org.powertac.common.interfaces.InitializationService;
import org.powertac.common.interfaces.NewTariffListener;
import org.powertac.common.interfaces.ServerConfiguration;
import org.powertac.common.interfaces.TariffMarket;
import org.powertac.common.interfaces.TimeslotPhaseProcessor;
import org.powertac.common.msg.TariffExpire;
import org.powertac.common.msg.TariffRevoke;
import org.powertac.common.msg.TariffStatus;
import org.powertac.common.msg.TariffUpdate;
import org.powertac.common.msg.VariableRateUpdate;
import org.powertac.common.repo.RandomSeedRepo;
import org.powertac.common.repo.TariffRepo;
import org.powertac.common.repo.TariffSubscriptionRepo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * Implements the Tariff Market abstraction. Incoming tariff-related
 * messages from brokers are received and processed, tariffs are published
 * periodically, and subscriptions are processed on behalf of customers.
 * @author John Collins
 */
@Service
public class TariffMarketService
  extends TimeslotPhaseProcessor 
  implements TariffMarket, BrokerMessageListener, InitializationService
{
  static private Logger log = Logger.getLogger(TariffMarketService.class.getName());

  @Autowired
  private TimeService timeService;
  
  @Autowired
  private Accounting accountingService;
  
  @Autowired
  private BrokerProxy brokerProxyService;
  
  @Autowired
  private TariffRepo tariffRepo;
  
  @Autowired
  private TariffSubscriptionRepo tariffSubscriptionRepo;
  
  @Autowired
  private ServerConfiguration serverProps;

  @Autowired
  private RandomSeedRepo randomSeedService;

  // maps power type to id of corresponding default tariff
  private HashMap defaultTariff;

  // configuration
  @ConfigurableValue(valueType = "Double",
      description = "low end of tariff publication fee range")
  private double minPublicationFee = -100.0;

  @ConfigurableValue(valueType = "Double",
      description = "high end of tariff publication fee range")
  private double maxPublicationFee = -500.0;

  @ConfigurableValue(valueType = "Double",
      publish = true,
      description = "set publication fee directly to override random selection")
  private Double publicationFee = null;
  
  @ConfigurableValue(valueType = "Double",
      description = "low end of tariff revocation fee range")
  private double minRevocationFee = -100.0;
  
  @ConfigurableValue(valueType = "Double",
      description = "high end of tariff revocation fee range")
  private double maxRevocationFee = -500.0;
  
  @ConfigurableValue(valueType = "Double",
      publish = true,
      description = "Set revocation fee directly to override random selection")
  private Double revocationFee = null;

  // these properties are constrained, so we provide explicit setters for them
  private int publicationInterval = 6;
  private int publicationOffset = 0;
  private boolean firstPublication;
  
  /**
   * Default constructor
   */
  public TariffMarketService ()
  {
    super();
  }

  /**
   * Reads configuration parameters, registers for timeslot phase activation.
   */
  @Override
  public String initialize (Competition competition, List completedInits)
  {
    int index = completedInits.indexOf("AccountingService");
    if (index == -1) {
      return null;
    }


    defaultTariff = new HashMap();
    brokerProxyService.registerBrokerTariffListener(this);
    firstPublication = false;
    registrations.clear();
    publicationFee = null;
    revocationFee = null;
    
    super.init();

    serverProps.configureMe(this);

    // compute the fees
    RandomSeed random =
        randomSeedService.getRandomSeed("AccountingService",
                                        0l, "interest");
    if (publicationFee == null) {
      // interest will be non-null in case it was overridden in the config
      publicationFee = (minPublicationFee +
                             (random.nextDouble() *
                                 (maxPublicationFee - minPublicationFee)));
      log.info("set publication fee: " + publicationFee);
    }
    if (revocationFee == null) {
      // interest will be non-null in case it was overridden in the config
      revocationFee = (minRevocationFee +
                             (random.nextDouble() *
                                 (maxRevocationFee - minRevocationFee)));
      log.info("set revocation fee: " + revocationFee);
    }
    serverProps.publishConfiguration(this);
    return "TariffMarket";
  }

  // ------------ Data access and configuration support ---------------

  // publication fee
  public double getMinPublicationFee ()
  {
    return minPublicationFee;
  }

  public double getMaxPublicationFee ()
  {
    return maxPublicationFee;
  }

  public Double getPublicationFee ()
  {
    return publicationFee;
  }

  // revocation fee
  public double getMinRevocationFee ()
  {
    return minRevocationFee;
  }

  public double getMaxRevocationFee ()
  {
    return maxRevocationFee;
  }

  public Double getRevocationFee ()
  {
    return revocationFee;
  }

  public int getPublicationInterval ()
  {
    return publicationInterval;
  }
  
  @ConfigurableValue(valueType = "Integer",
      description = "Number of timeslots between tariff publication events. " +
                    "Must be at most 24.")
  public void setPublicationInterval (int interval)
  {
    if (interval > 24) {
      log.error("tariff publication interval " + interval + " > 24 hr");
      interval = 24;
    }
    publicationInterval = interval;
  }
  
  public int getPublicationOffset ()
  {
    return publicationOffset;
  }
  
  @ConfigurableValue(valueType = "Integer",
      description = "Number of timeslots from the first timeslot to delay " +
          "the first publication event. It does not work well " +
          "to make this zero, because brokers do not have an opportunity " +
          "to post tariffs in timeslot 0.")
  public void setPublicationOffset (int offset)
  {
    if (offset >= publicationInterval) {
      log.error("tariff publication offset " + publicationOffset
                + " >= publication interval " + offset);
    }
    else {
      publicationOffset = offset;
    }
  }

  List getRegistrations ()
  {
    return registrations;
  }

  // ----------------- Broker message API --------------------
  /**
   * Receives and dispatches an incoming broker message. We do this
   * synchronously with the incoming message traffic, rather than on
   * the timeslot phase signal, to minimize latency for broker feedback.
   */
  @Override
  public void receiveMessage (Object msg)
  {
    if (msg != null && msg instanceof TariffMessage) {
      // dispatch incoming message, using reflection to keep the 
      // message types clean.
      TariffMessage message = (TariffMessage)msg;
      TariffStatus result = 
        (TariffStatus)dispatch(this, "processTariff", new Object[]{message});
      // Check result, send error message if we know who the broker was
      if (result == null) {
        result = new TariffStatus(message.getBroker(), 
                                  0l, message.getId(),
                                  TariffStatus.Status.illegalOperation);
      }
      // if we have something to send back to the broker, then send it
      if (result != null) {
        brokerProxyService.sendMessage(result.getBroker(), result);
      }
    }
  }  

  /**
   * Processes a newly-published tariff.
   */
  @Override
  public TariffStatus processTariff (TariffSpecification spec)
  {
    tariffRepo.addSpecification(spec);
    Tariff tariff = new Tariff(spec);
    tariffRepo.addTariff(tariff);
    tariff.init();
    log.info("new tariff " + spec.getId());
    accountingService.addTariffTransaction(TariffTransaction.Type.PUBLISH,
                                           tariff, null, 0, 0.0, publicationFee);
    return new TariffStatus(spec.getBroker(), spec.getId(), spec.getId(),
                            TariffStatus.Status.success);
  }

  /**
   * Handles changes in tariff expiration date.
   */
  @Override
  public TariffStatus processTariff (TariffExpire update)
  {
    ValidationResult result = validateUpdate(update);
    if (result.tariff == null)
      return result.message;
    else {
      Instant newExp = update.getNewExpiration();
      if (newExp != null && newExp.isBefore(timeService.getCurrentTime())) {
        // new expiration date in the past
        log.warn("attempt to set expiration for tariff " +
                 result.tariff.getId() + " in the past:" +
                 newExp.toString());
        return new TariffStatus(update.getBroker(), update.getTariffId(), update.getId(),
                                TariffStatus.Status.invalidUpdate)
            .withMessage("attempt to set expiration in the past");
      }
      else {
        // update expiration date
        result.tariff.setExpiration(newExp);
        log.info("Tariff " + update.getTariffId() + 
                 "now expires at " + new DateTime(result.tariff.getExpiration(), DateTimeZone.UTC).toString());
        return success(update);
      }
    }
  }

  /**
   * Handles tariff revocation.
   */
  @Override
  public TariffStatus processTariff (TariffRevoke update)
  {
    ValidationResult result = validateUpdate(update);
    if (result.tariff == null)
      return result.message;
    else {
      result.tariff.setState(Tariff.State.KILLED);
      log.info("Revoke tariff " + update.getTariffId());
      // The actual revocation processing is delegated to the Customer,
      // who is obligated to call getRevokedSubscriptions periodically.

      // If there are active subscriptions, then we have to charge a fee.
      List activeSubscriptions =
          tariffSubscriptionRepo.findSubscriptionsForTariff(result.tariff);
      for (Iterator subs = activeSubscriptions.iterator();
           subs.hasNext(); ) {
        TariffSubscription sub = subs.next();
        if (sub.getCustomersCommitted() <= 0) {
          subs.remove();
        }
      }
      // check whether there are any remaining active subscriptions
      if (activeSubscriptions.size() > 0) {
        log.info("Revoked tariff has " + activeSubscriptions.size() + " active subscriptions");
        accountingService.addTariffTransaction(TariffTransaction.Type.REVOKE,
                                               result.tariff, null, 0, 0.0,
                                               revocationFee);
      }
    }
    return success(update);
  }

  /**
   * Applies a new HourlyCharge to an existing Tariff with a variable Rate.
   */
  @Override
  public TariffStatus processTariff (VariableRateUpdate update)
  {
    ValidationResult result = validateUpdate(update);
    if (result.tariff == null)
      return result.message;
    else if (result.tariff.addHourlyCharge(update.getHourlyCharge(), update.getRateId())) {
      return success(update);
    }
    else {
      // failed to add hourly charge
      return new TariffStatus(update.getBroker(), update.getTariffId(), update.getId(),
                              TariffStatus.Status.invalidUpdate)
          .withMessage("update: could not add hourly charge");
    }
  }

  // ----------------------- Customer API --------------------------

  private List registrations = new ArrayList();

  @Override
  public void registerNewTariffListener (NewTariffListener listener)
  {
    registrations.add(listener);
  }

  // Process queued messages, then
  // handle distribution of new tariffs to customers
  @Override
  public void activate (Instant time, int phase)
  {
    log.info("Activate");
    long msec = timeService.getCurrentTime().getMillis();
    if (!firstPublication ||
        (msec / TimeService.HOUR) % publicationInterval == publicationOffset) {
      // time to publish or never published
      publishTariffs();
      firstPublication = true;
    }
  }

  /**
   * Publishes pending tariffs to customers and brokers
   */
  private void publishTariffs ()
  {
    List publishedTariffs = tariffRepo.findTariffsByState(Tariff.State.PENDING);
    log.info("publishing " + publishedTariffs.size() + " new tariffs");
    for (Tariff tariff : publishedTariffs) {
      tariff.setState(Tariff.State.OFFERED);
    }

    List publishedTariffSpecs = new ArrayList();
    for (Tariff tariff : publishedTariffs) {
      TariffSpecification spec = tariff.getTariffSpecification();
      publishedTariffSpecs.add(spec);
      log.info("publishing spec " + spec.getId() + " broker: " + spec.getBroker().getUsername() + ", exp: " + spec.getExpiration());
    }

    for (NewTariffListener listener : registrations) {
      listener.publishNewTariffs(publishedTariffs);
    }
    brokerProxyService.broadcastMessages(publishedTariffSpecs);
  }

  /**
   * Subscribes a block of Customers from a single Customer model to
   * this Tariff, as long as this Tariff has not expired. If the
   * subscription succeeds, then the TariffSubscription instance is
   * return, otherwise null.
   * 

* Note that you cannot unsubscribe directly from a Tariff -- you have to do * that from the TariffSubscription that represents the Tariff you want * to unsubscribe from.

*/ @Override public TariffSubscription subscribeToTariff (Tariff tariff, CustomerInfo customer, int customerCount) { if (tariff.isExpired()) return null; TariffSubscription sub = tariffSubscriptionRepo.getSubscription(customer, tariff); sub.subscribe(customerCount); return sub; } @Override public List getActiveTariffList(PowerType type) { return tariffRepo.findActiveTariffs(type); } /** * Returns the default tariff */ @Override public Tariff getDefaultTariff (PowerType type) { Long defaultId = defaultTariff.get(type); if (defaultId == null) return null; return tariffRepo.findTariffById(defaultId); } @Override public boolean setDefaultTariff (TariffSpecification newSpec) { tariffRepo.addSpecification(newSpec); Tariff tariff = new Tariff(newSpec); tariff.init(); tariffRepo.addTariff(tariff); defaultTariff.put(newSpec.getPowerType(), tariff.getId()); return true; } private TariffStatus success (TariffUpdate update) { Broker broker = update.getBroker(); return new TariffStatus(broker, update.getTariffId(), update.getId(), TariffStatus.Status.success); } private ValidationResult validateUpdate (TariffUpdate update) { Broker broker = update.getBroker(); Tariff tariff = tariffRepo.findTariffById(update.getTariffId()); if (tariff == null) { log.error("update - no such tariff " + update.getTariffId() + ", broker " + update.getBroker().getUsername()); return new ValidationResult(null, new TariffStatus(broker, update.getTariffId(), update.getId(), TariffStatus.Status.noSuchTariff)); } return new ValidationResult(tariff, null); } private class ValidationResult { Tariff tariff; TariffStatus message; ValidationResult (Tariff tariff, TariffStatus msg) { super(); this.tariff = tariff; this.message = msg; } } @Override public void setDefaults () { } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy