Four-Stage-Supply-Chain-1 Model Description Back to simulation

The four stage (single-product) supply chain of the classical "Beer Game" consists of a retailer, a wholesaler, a distributor and a factory.

You can inspect the model's OESjs code on the OES GitHub repo.

Conceptual Model

A conceptual model, also called domain model, describes the real-world system under investigation by identifying the relevant types of objects and events, and describing their dynamics, allowing to understand what's going on in the real system. It does not describe any software/design artifact.

Conceptual Information Model

A conceptual information model describes the subject matter vocabulary used, e.g., in the system narrative, in a semi-formal way. Such a vocabulary essentially consists of names for

  • types, corresponding to classes in OO modeling, or unary predicates in formal logic,
  • properties corresponding to binary predicates in formal logic,
  • associations corresponding to n-ary predicates (with n > 1) in formal logic.

The main categories of types are object types and event types. A simple form of conceptual information model is obtained by providing a list of each of them, while a more elaborated model, preferably in the form of a UML class diagram, also defines properties and associations, including the participation of objects (of certain types) in events (of certain types).

The potentially relevant object types are:

  1. top supply chain nodes (like the factory)
  2. intermediate supply chain nodes (like the wholesaler and distributor)
  3. bottom supply chain nodes (like the retailer)

Potentially relevant types of events are:

  1. receive order (from the downstream node or from end customer),
  2. end of week,
  3. send order (to the upstream node),
  4. ship items (to the downstream node),
  5. perceive reception of items (receive delivery).

Both object types and event types, together with their participation associations, can be visually described in a conceptual information model in the form of a UML class diagram, as shown below.

A conceptual information model describing object, event and activity types.
???
Conceptual Process Model

A DPMN diagram showing a conceptual process model
Implementation with OESjs
Implementing the Information Design Model

For implementing the information design model, we have to code all object types and event types specified in the model in the form of classes.

The agent class AbstractSupplyChainNode can be coded with OESjs-Core4 in the following way:

class AbstractSupplyChainNode extends aGENT {
  constructor({ id, name, stockQuantity, safetyStock=3, backorderQuantity=0}) {
    super({id, name});
    if (stockQuantity !== undefined) this.stockQuantity = stockQuantity;
    // extra inventory beyond the expected lead time demand
    if (safetyStock !== undefined) this.safetyStock = safetyStock;
    // orders of previous cycles that aren't fulfilled yet
    if (backorderQuantity !== undefined) this.backorderQuantity = backorderQuantity;
    // the quantity ordered last time by the downstream customer from this node
    this.lastSalesOrderQuantity = 0;
    // the accumulated inventory costs of this node
    this.accumulatedInventoryCosts = 0;
  }
  onReceiveOrder( quantity) {
    // store order quantity for later processing
    this.lastSalesOrderQuantity = quantity;
  }
  // not used by TopSupplyChainNode
  onPerceive( percept) {
    switch (percept.type) {
      case "InDelivery":
        this.stockQuantity += percept.quantity;
        break;
    }
  }
  // overwritten by TopSupplyChainNode
  onTimeEvent( e) {
    switch (e.type) {
    case "EndOfWeek":
      ...
    }
  }
}

All agent classes inherit an id attribute and a name attribute from the pre-defined OES foundation class aGENT.

The onTimeEvent method of the class AbstractSupplyChainNode contains most of the model logic:

  onTimeEvent( e) {
    switch (e.type) {
      case "EndOfWeek":
        /********************************************************
         *** ship items to downstream node or end customer ******
         ********************************************************/
        const stockQuantityAtStartOfWeek = this.stockQuantity;
        let deliveryQuantity = 0, stockoutCosts = 0;
        if (this.stockQuantity < this.lastSalesOrderQuantity + this.backorderQuantity) {
          // not enough stock quantity for fulfilling the sum of order and backorder quantity
          deliveryQuantity = this.stockQuantity;
          if (this.lastSalesOrderQuantity > this.stockQuantity) {
            const newBackorderQuantity = this.lastSalesOrderQuantity - this.stockQuantity;
            stockoutCosts = newBackorderQuantity * sim.model.p.stockoutCostsPerUnit;
            // increment backorder quantity
            this.backorderQuantity += newBackorderQuantity;
          } else if (this.stockQuantity > this.lastSalesOrderQuantity) {
            // decrement backorder quantity
            this.backorderQuantity -= this.stockQuantity - this.lastSalesOrderQuantity;
          }
          // stock quantity is reset to zero
          this.stockQuantity = 0;
        } else {  // enough stock quantity for fulfilling the sum of order and backorder quantity
          deliveryQuantity = this.lastSalesOrderQuantity + this.backorderQuantity;
          // decrement stock quantity
          this.stockQuantity -= deliveryQuantity;
          // backorder quantity is reset to zero
          this.backorderQuantity = 0;
        }
        // only ship non-zero quantities
        if (deliveryQuantity > 0) {
          sim.schedule( new ShipItems({quantity: deliveryQuantity, performer: this}));
        }
        /***********************************************
         *** Send purchase order to upstream node ******
         ***********************************************/
        let orderQuantity = 0;
        // Try to keep the inventory as big as the latest order received by this node (plus a bit extra quantity)
        if (this.stockQuantity > 0) {
          orderQuantity = Math.max( this.lastSalesOrderQuantity -
              this.stockQuantity + this.safetyStock, 0);
        } else {
          orderQuantity = this.lastSalesOrderQuantity + this.safetyStock;
        }
        //TODO: DELETE this.lastPuchaseOrderQuantity = orderQuantity;
        // only place orders with values greater than zero
        if (orderQuantity > 0) {
          sim.schedule( new PurchaseOrder({ quantity: orderQuantity,
            sender: this, receiver: this.upStreamNode}));
        }
        /***********************************************
         *** Calculate inventory costs *****************
         ***********************************************/
            // the average inventory is the stock quantity at the beginning of the week plus the stock quantity
            // at the end of the week divided by two
        const averageStockQuantity = (stockQuantityAtStartOfWeek + this.stockQuantity) / 2,
            totalHoldingCostsPerWeek = averageStockQuantity * sim.model.p.holdingCostsPerUnitPerWeek;
        this.accumulatedInventoryCosts = totalHoldingCostsPerWeek + stockoutCosts;
        break;
    }
  }

See also...

Back to simulation