This commit is contained in:
counterweight 2023-12-09 18:05:57 +01:00
parent a67287eeb7
commit ee8dd41f85
129 changed files with 91712 additions and 0 deletions

Binary file not shown.

View file

@ -0,0 +1,80 @@
You have been hired by Beanie Limited, the coffee company, for a new project on their Diemen distribution center (DC). Your contact for this engagement is Jeroen Schotten, the manager of the facility.
The Diemen DC is the entrypoint of Beanie Limited's supply chain in Europe. Beanie Limited purchases raw coffee beans in different regions of Latin America that are sent by ship to docks in Europe. Once the beans reach Europe, all the stock is centralized in Diemen before continuing its path through Beanie Limited's network. The Diemen DC does not serve customers directly, but rather other distribution centers of Beanie Limited and some partner companies. These regional DCs are smaller and are the ones responsible for interacting with clients directly in their assigned areas.
As of today, Beanie Limited only handles raw coffee beans through it's sales network and supply chain. But this is going to change very soon, which is the reason Jeroen has decided to hire you.
Beanie Limited is currently working on an expansion of their Diemen DC, with the goal of adding a processing facility for coffee. This processing facility will be capable of producing roasted coffee beans, both regular and decaffeinated. The company expects to have this new extension operational by the start of next year, when it will expand its product portfolio from the current one (only raw coffee beans) to also include the new products (raw coffee beans, roasted coffee beans and decaffeinated coffee beans).
Roasted coffee beans are obtained by placing raw coffee beans into cylinders where hot air is blown. Beans are heated to ~250ºC for around 12 minutes. The cylinder rotates to ensure that beans are roasted evenly. On the other hand, decaff coffee can be obtained through several methods. Beanie Limited employs the chemical solvent method, which consists on steaming and rinsing the raw coffee beans with Ethyl Acetate. After removing the chemical agent, the beans are roasted just like regular coffee. The new facility comes with one limitation: there is only one cylinder production line. This means that, on any given day, it can only produce roasted or decaff coffee, but not both. Changing from one product to the other is a hefty task known as a changeover, which typically leaves the line out of order for some time.
Jeroen faces two challenges:
- On one hand, he must decide how will the production line be managed regarding how capacity gets split between the two processed products, roasted and decaff beans.
- On the other hand, the new changes also imply that Diemen's raw coffee beans stock will not only play the role of providing other regional DCs with the raw beans they need, but will also be a raw material for the processing facility. Jeroen thinks that his replenishment policy should be reviewed to ensure that it satisfies the current situation.
In order to tackle this, Jeroen would like to receive proposals from you regarding the management of the production line and the policy to order shipments from Latin America. He trusts that your simulation and optimization skills will assist in providing a good solution, since the complexities of the operation have proven to be a tough bone for his team.
## Detailed task definition
- Below you will find four levels of questions. Levels 1 to 3 are compulsory.
Level 4 is optional.
- You need to write a report document where you answer the questions of the
different levels. This report should be directed towards Jeroen, should give
her clear recommendations and should justify these recommendations. It's
important for you to reflect your methodology to back your proposals.
- Each level is worth 2 points out of a total of 10. The 2 missing points will
grade the clarity and structure of your report and code.
- You need to use a Python notebook to solve all levels. A helper notebook is
provided. Please attach a notebook that shows your
solution/proposal/analysis.
- Include your team number, names and student IDs in all your deliverables.
## Data and other facts
A few general facts provided by Jeroen's team:
- The production line can roast 125.000 daily kilograms of coffee, or 70.000 daily kilograms if it's decaffeinated.
- Since the company was not selling roasted and decaf coffee before, there is no historical data for sales. Nevertheless, these are the forecasts you have received:
- Jeroen expects to receive about 3 orders per week for roasted coffee. He expects each order to be somewhere between 200.000 and 300.000, with 250.000 being the expected "typical" amount.
- As for the decaff, he expects 1 order per week, with a similar sizing of the roasted coffee ones.
- Switching the line from one product to another takes somewhere between 24 to 48 hours. This also includes stopping the line to not produce anything, and starting the line again.
- You can assume that producing one kilogram of roasted coffee or one kilogram of decaff coffee consumes one kilogram of raw coffee beans.
- Jeroen's team have indicated that you should ensure that the line always runs production batches of at least 5 days. This means, once the line gets prepared for a specific product, it should produce for at least 5 days before switching to another product or stopping. Running batches of more than 5 days is perfectly fine.
- Only one order from Latin America to Diemen can be active at the same time.
You have also received two tables that contain real data from the past 2 years:
- served_orders: this table shows every order that was served by the Diemen DC in the past years. This means, each record corresponds to one request of raw coffee beans that one of the regional DCs placed to Diemen and Diemen served. The units are kilograms.
- sourcing_events: this table shows the Purchase Orders Diemen placed to it's Latin American providers. For each order, there are two dates: the date when
the order was placed, and the date where the beans actually reached Diemen. The units are kilograms.
## Notebook
A notebook with some helping code has been provided. The code contains a small
simulation engine that can help you simulate a year of activity for the
distribution center. The instructions on how to use the code are in the notebook itself.
## Levels
- Level 1
- Jeroen wants you to provide a purchasing policy and a production line policy. This means your policies define when to buy more beans and how much, and what production should be on the production line everyday.
- He would like to achieve a service level of 99% for raw coffee beans, and of 95% for roasted and decaff beans.
- Level 2
- As part of the new processing facility, additional warehousing space will also be added to the Diemen DC. Currently, the location can hold up to 20,000 tons of beans. Jeroen would like your advice on how much additional storage should be built to guarantee that the warehouse never exceeds its capacity.
- Level 3
- Traditionally, Jeroen's team has followed a rule of only having one shipment coming from Latin America to Diemen at the same time, mostly for simplicity's sake. Nevertheless, Jeroen is wondering: how much would allowing up to 3 shipments to be on the way simultaneously help Diemen? Would it improve any metrics significantly?
- Bear in mind that any orders placed to Latin American providers should be of, at least, 3000 tons of beans.
- Level 4
- Out of the three products that Diemen will handle, decaffeinated beans are the smallest one in volume and relevance. Jeroen is wondering: if the service level was downgraded from the original 95% to 75%, how much would that benefit the DC and its metrics?

View file

@ -0,0 +1,686 @@
{
"nbformat": 4,
"nbformat_minor": 0,
"metadata": {
"colab": {
"provenance": []
},
"kernelspec": {
"name": "python3",
"display_name": "Python 3"
},
"language_info": {
"name": "python"
}
},
"cells": [
{
"cell_type": "markdown",
"source": [
"# Case 2 - Student Notebook"
],
"metadata": {
"id": "oHWpkTqMeBMq"
}
},
{
"cell_type": "markdown",
"source": [
"## Imports and data loading"
],
"metadata": {
"id": "BhR7Z_UqeEgm"
}
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "HQwlyagxfXRU"
},
"outputs": [],
"source": [
"import io\n",
"import pandas as pd\n",
"import numpy as np\n",
"import seaborn as sns\n",
"from google.colab import files\n",
"from datetime import datetime, timedelta"
]
},
{
"cell_type": "code",
"source": [
"# This avoids scientific notation on millions and larger\n",
"pd.set_option('display.float_format', lambda x: '%.2f' % x)"
],
"metadata": {
"id": "wsv3gEB9qmp6"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"source": [
"# Upload files from your computer here\n",
"# Run the cell and click the \"Browse\" button to upload the provided CSV \n",
"# files\n",
"uploaded = files.upload()"
],
"metadata": {
"id": "4psao7htcAwr"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"source": [
"# Read the files as pandas dataframes and print them so you can check that the\n",
"# process went fine\n",
"\n",
"served_orders = pd.read_csv(io.BytesIO(uploaded['served_orders.csv']))\n",
"sourcing_events = pd.read_csv(io.BytesIO(uploaded['sourcing_events.csv']))\n",
"\n",
"for table in (served_orders, sourcing_events):\n",
" print(table.head())"
],
"metadata": {
"id": "X8N0PZ4qcOls"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"## Analysis"
],
"metadata": {
"id": "GKty74ZfuBEG"
}
},
{
"cell_type": "code",
"source": [
"# You can use this space to analyse the provided data as you see fit."
],
"metadata": {
"id": "CuYSBC2auDIG"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"## Provided code\n"
],
"metadata": {
"id": "KM5HVJiIfYPC"
}
},
{
"cell_type": "code",
"source": [
"# This cell includes provided code to run simulations.\n",
"# You do not need to understand the internals of this code. Feel free to just\n",
"# run it and move forward.\n",
"# The next section explains how you can call this code to run your simulations.\n",
"\n",
"base = datetime(2022,1,1)\n",
"dates_in_2022 = [base + timedelta(days=x) for x in range(365)]\n",
"\n",
"class SimulationResult:\n",
"\n",
" def __init__(self, stock_states, demand_by_day, sourcing_events):\n",
" self.stock_states = stock_states\n",
" self.demand_by_day = demand_by_day\n",
" self.sourcing_events = sourcing_events\n",
"\n",
" def plot_stock_history(self):\n",
" sns.lineplot(x=dates_in_2022, y=self.stock_states)\n",
"\n",
" def plot_stock_distribution(self):\n",
" sns.histplot(x=self.stock_states, kde=True)\n",
"\n",
" def service_level(self):\n",
" return (self.stock_states > 0 ).astype(int).mean()\n",
"\n",
" def stock_level_summary(self):\n",
" print(\n",
" pd.DataFrame(self.stock_states).describe()\n",
" ) \n",
" \n",
" def mean_stock_level(self):\n",
" return self.stock_states.mean()\n",
"\n",
" def median_stock_level(self):\n",
" return np.median(self.stock_states)\n",
"\n",
" def stdev_stock_level(self):\n",
" return self.stock_states.std()\n",
"\n",
" def mean_demand(self):\n",
" return self.demand_by_day.mean()\n",
" \n",
" def number_of_purchase_orders_placed(self):\n",
" return len(self.sourcing_events)\n",
"\n",
"\n",
"class SimulationConfig:\n",
"\n",
" def __init__(\n",
" self, \n",
" starting_stock_raw_beans,\n",
" starting_stock_roasted_beans,\n",
" starting_stock_decaff_beans, \n",
" demand_generator_raw_beans,\n",
" demand_generator_roasted_beans,\n",
" demand_generator_decaff_beans,\n",
" lead_time_generator_raw_beans, \n",
" purchaser,\n",
" production_line_switcher,\n",
" roasted_beans_daily_production,\n",
" decaff_beans_daily_production\n",
" ):\n",
" self.starting_stock_raw_beans = starting_stock_raw_beans\n",
" self.starting_stock_roasted_beans = starting_stock_roasted_beans\n",
" self.starting_stock_decaff_beans = starting_stock_decaff_beans\n",
" self.demand_generator_raw_beans = demand_generator_raw_beans\n",
" self.demand_generator_roasted_beans = demand_generator_roasted_beans\n",
" self.demand_generator_decaff_beans = demand_generator_decaff_beans\n",
" self.lead_time_generator_raw_beans = lead_time_generator_raw_beans\n",
" self.purchaser = purchaser\n",
" self.production_line_switcher = production_line_switcher\n",
" self.roasted_beans_daily_production = roasted_beans_daily_production\n",
" self.decaff_beans_daily_production = decaff_beans_daily_production\n",
"\n",
"class PurchaseOrder:\n",
" \n",
" def __init__(self, amount, request_date, delivery_date):\n",
" self.amount = amount\n",
" self.request_date = request_date\n",
" self.delivery_date = delivery_date\n",
"\n",
" def __repr__(self):\n",
" return f\"Order of {self.amount:.0f}, requested on {self.request_date}, delivery on {self.delivery_date}.\"\n",
"\n",
"class ProductionLine:\n",
"\n",
" def __init__(self, products_and_rates, starting_product=None):\n",
" self.products_and_rates = products_and_rates\n",
" self.on_the_line = starting_product\n",
" self.next_on_the_line = None\n",
" self.days_on_current_batch = 0\n",
"\n",
" def tick(self):\n",
" if self.on_the_line in self.products_and_rates:\n",
" self.days_on_current_batch += 1\n",
" return self.products_and_rates[self.on_the_line]\n",
"\n",
" if self.on_the_line is None and self.next_on_the_line is None:\n",
" self.days_on_current_batch += 1\n",
" return 0\n",
" if self.on_the_line is None:\n",
" self.on_the_line = self.next_on_the_line\n",
" self.next_on_the_line = None\n",
" self.days_on_current_batch = 0\n",
" return 0\n",
" \n",
" def switch_to_product(self, next_product):\n",
" self.on_the_line = None\n",
" self.next_on_the_line = next_product\n",
" self.days_on_current_batch = -1\n",
"\n",
"\n",
"class Simulation:\n",
" \n",
" def __init__(self, config: SimulationConfig, verbose=False):\n",
" self._config = config\n",
" self.verbose = verbose\n",
"\n",
" def run(self):\n",
"\n",
" stock_raw_beans = np.array([self._config.starting_stock_raw_beans])\n",
" stock_roasted_beans = np.array([self._config.starting_stock_roasted_beans])\n",
" stock_decaff_beans = np.array([self._config.starting_stock_decaff_beans])\n",
"\n",
" opened_orders = []\n",
" ongoing_orders = {}\n",
"\n",
" demand_by_day_raw_beans = np.array(list())\n",
" demand_by_day_roasted_beans = np.array(list())\n",
" demand_by_day_decaff_beans = np.array(list())\n",
"\n",
" production_line = ProductionLine(\n",
" products_and_rates={\n",
" \"roasted_beans\": self._config.roasted_beans_daily_production,\n",
" \"decaff_beans\": self._config.decaff_beans_daily_production\n",
" },\n",
" starting_product=\"roasted_beans\"\n",
" )\n",
"\n",
" production_line_switcher = self._config.production_line_switcher\n",
" \n",
" for day in dates_in_2022:\n",
"\n",
" # General\n",
" if self.verbose:\n",
" print(\"-------------------------\")\n",
" print(f\"Simulating day: {day}\")\n",
" current_stock_raw_beans = stock_raw_beans[-1]\n",
" current_stock_roasted_beans = stock_roasted_beans[-1]\n",
" current_stock_decaff_beans = stock_decaff_beans[-1]\n",
"\n",
" # Generate demand\n",
" if self.verbose:\n",
" print(f\"Starting stock raw beans: {current_stock_raw_beans:.0f}\")\n",
" print(f\"Starting stock roasted beans: {current_stock_roasted_beans:.0f}\")\n",
" print(f\"Starting stock decaff beans: {current_stock_decaff_beans:.0f}\")\n",
" demand_for_this_day_raw_beans = self._config.demand_generator_raw_beans()\n",
" demand_for_this_day_roasted_beans = self._config.demand_generator_roasted_beans()\n",
" demand_for_this_day_decaff_beans = self._config.demand_generator_decaff_beans()\n",
" if self.verbose:\n",
" print(f\"Generated raw beans demand for today: {demand_for_this_day_raw_beans:.0f}\")\n",
" print(f\"Generated roasted beans demand for today: {demand_for_this_day_roasted_beans:.0f}\")\n",
" print(f\"Generated decaff beans demand for today: {demand_for_this_day_decaff_beans:.0f}\")\n",
" demand_by_day_raw_beans = np.append(demand_by_day_raw_beans, [demand_for_this_day_raw_beans])\n",
" demand_by_day_roasted_beans = np.append(demand_by_day_roasted_beans, [demand_for_this_day_roasted_beans])\n",
" demand_by_day_decaff_beans = np.append(demand_by_day_decaff_beans, [demand_for_this_day_decaff_beans])\n",
"\n",
"\n",
" # Receive orders and place orders\n",
" raw_beans_received_this_day = 0\n",
" if day in ongoing_orders:\n",
" order_delivered_today = ongoing_orders.pop(day)\n",
" raw_beans_received_this_day = order_delivered_today.amount\n",
" if self.verbose:\n",
" print(f\"Raw beans received today: {raw_beans_received_this_day:.0f}\")\n",
" \n",
" order_to_make = self._config.purchaser(\n",
" day, \n",
" current_stock_raw_beans, \n",
" ongoing_orders,\n",
" self._config.lead_time_generator_raw_beans\n",
" )\n",
" if order_to_make:\n",
" if self.verbose:\n",
" print(f\"Placing a new order: {order_to_make}\")\n",
" opened_orders.append(order_to_make)\n",
" ongoing_orders[order_to_make.delivery_date] = order_to_make\n",
"\n",
"\n",
" # Decide on production today and produce whatever gets produced or wait during the changeover\n",
"\n",
" print(f\"Product on the line: {production_line.on_the_line}\")\n",
"\n",
" production_line_switcher(\n",
" production_line,\n",
" {\n",
" \"raw_beans_stock\": current_stock_raw_beans,\n",
" \"roasted_beans_stock\": current_stock_roasted_beans,\n",
" \"decaff_beans_stock\": current_stock_decaff_beans\n",
" }\n",
" )\n",
"\n",
" if production_line.on_the_line == \"roasted_beans\":\n",
" roasted_beans_produced_this_day = production_line.tick()\n",
" decaff_beans_produced_this_day = 0\n",
" if production_line.on_the_line == \"decaff_beans\":\n",
" roasted_beans_produced_this_day = 0\n",
" decaff_beans_produced_this_day = production_line.tick()\n",
" if production_line.on_the_line not in (\"roasted_beans\", \"decaff_beans\"):\n",
" production_line.tick()\n",
" roasted_beans_produced_this_day = 0\n",
" decaff_beans_produced_this_day = 0\n",
"\n",
" raw_beans_consumed_in_production = roasted_beans_produced_this_day + decaff_beans_produced_this_day\n",
" \n",
" if self.verbose:\n",
" print(f\"Roasted beans produced today: {roasted_beans_produced_this_day}\")\n",
" print(f\"Decaff beans produced today: {decaff_beans_produced_this_day}\")\n",
" print(f\"Product {production_line.on_the_line} has been on the line for {production_line.days_on_current_batch} days.\")\n",
"\n",
" # Update stocks with the changes of the day\n",
"\n",
" current_stock_raw_beans = (\n",
" current_stock_raw_beans + \n",
" raw_beans_received_this_day - \n",
" demand_for_this_day_raw_beans -\n",
" raw_beans_consumed_in_production\n",
" )\n",
" stock_raw_beans = np.append(stock_raw_beans, [current_stock_raw_beans])\n",
" current_stock_roasted_beans = current_stock_roasted_beans + roasted_beans_produced_this_day - demand_for_this_day_roasted_beans\n",
" stock_roasted_beans = np.append(stock_roasted_beans, [current_stock_roasted_beans])\n",
" current_stock_decaff_beans = current_stock_decaff_beans + decaff_beans_produced_this_day - demand_for_this_day_decaff_beans\n",
" stock_decaff_beans = np.append(stock_decaff_beans, [current_stock_decaff_beans])\n",
" \n",
" # Remove starting stock\n",
" stock_raw_beans = np.delete(stock_raw_beans, 0) \n",
" stock_roasted_beans = np.delete(stock_roasted_beans, 0)\n",
" stock_decaff_beans = np.delete(stock_decaff_beans, 0)\n",
" \n",
" raw_beans_results = SimulationResult(\n",
" stock_states=stock_raw_beans, \n",
" demand_by_day=demand_by_day_raw_beans, \n",
" sourcing_events=opened_orders\n",
" )\n",
" roasted_beans_results = SimulationResult(\n",
" stock_states=stock_roasted_beans, \n",
" demand_by_day=demand_by_day_roasted_beans, \n",
" sourcing_events=None\n",
" )\n",
" decaff_beans_results = SimulationResult(\n",
" stock_states=stock_decaff_beans, \n",
" demand_by_day=demand_by_day_decaff_beans, \n",
" sourcing_events=opened_orders\n",
" )\n",
"\n",
"\n",
" return raw_beans_results, roasted_beans_results, decaff_beans_results\n",
" \n",
"\n",
"\n"
],
"metadata": {
"id": "cGcEzAIDfa8s"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"## Usage Example"
],
"metadata": {
"id": "Xul3y3LpYKiY"
}
},
{
"cell_type": "code",
"source": [
"# Read this block carefully to understand how to prepare parameters,\n",
"# run simulations and fetch the results.\n",
"\n",
"# These are the steps we will follow:\n",
"# 1. Prepare a purchaser function\n",
"# 2. Prepare a production line management function\n",
"# 3. Assemble a simulation configuration with your parameters and assumptions\n",
"# 4. Run a simulation\n",
"# 5. Fetch results\n",
"\n",
"\n",
"###\n",
"# 1. Prepare a purchaser function\n",
"###\n",
"\n",
"# The purchase function handles the decisions of whether to buy more raw coffee\n",
"# beans to send to Diemen, and how much to buy. It gets called once per simulated\n",
"# day, so you have an oportunity to place orders each day.\n",
"\n",
"\n",
"# You can name your function whatever you like, but the arguments should have\n",
"# the same names and order as show below.\n",
"def a_simple_purchaser(\n",
" day, # The current day\n",
" current_stock, # The level of raw beans stock on that day\n",
" ongoing_orders, # A dictionary with the open purchase orders\n",
" lead_time_generator # The same lead time generator you pass to the Simulation Config\n",
" ):\n",
" # Your code goes here. You can make any logic you want. Just make sure to return\n",
" # None if you don't want to place an order and to return a PurchaseOrder when\n",
" # you want to buy. The policies below are a simple example to inspire you: you\n",
" # definitely want to modify the numbers and/or followed logic.\n",
" \n",
" if ongoing_orders or current_stock > 15_000_000:\n",
" # If we are already waiting for an order to arrive or we have enough stock\n",
" # we don't request more goods.\n",
" return None\n",
"\n",
" if current_stock <= 15_000_000:\n",
" # If the stock is going low, we request more.\n",
" return PurchaseOrder(\n",
" amount=15_000_000, # The amount to order. This is the only bit you change.\n",
" request_date=day, # Always copy paste this.\n",
" delivery_date=day + timedelta(days=lead_time_generator()) # Always copy paste this.\n",
" )\n",
"\n",
"\n",
"###\n",
"# 2. Prepare a production line management function\n",
"### \n",
"\n",
"# The line manager function handles the decision of whether the production line\n",
"# should change to a different product (or no product at all). It gets called \n",
"# once per day.\n",
"\n",
"# You can name your function whatever you like, but the arguments should have\n",
"# the same names and order as show below.\n",
"def a_simple_line_manager(\n",
" production_line, # Details about the production line\n",
" stock_by_product # A summary of the stock that updates each day\n",
"):\n",
" # Your code goes here. You can make any logic you want. Just make sure to \n",
" # switch to None if you don't want to change the product on the line. If you want\n",
" # to switch the product on the line, call production_line.switch_to_product(\"product name\"). \n",
" # The policies below are a simple example to inspire you: you definitely want \n",
" # to modify the numbers and/or followed logic.\n",
" \n",
" # If the current product has been less than 21 days on the line, we don't \n",
" # change anything.\n",
" if production_line.days_on_current_batch < 14:\n",
" return\n",
" \n",
" if (\n",
" stock_by_product[\"roasted_beans_stock\"] > 2_000_000 and \n",
" stock_by_product[\"decaff_beans_stock\"] > 2_000_000 and\n",
" production_line.on_the_line is not None\n",
" ):\n",
" # If we have plenty of stock and we are still producing, we stop the line\n",
" # by switching to None.\n",
" production_line.switch_to_product(None)\n",
" print(\"Too much inventory. I'm switching to None!\")\n",
" return\n",
"\n",
" if (\n",
" stock_by_product[\"roasted_beans_stock\"] > 2_000_000 and \n",
" stock_by_product[\"decaff_beans_stock\"] > 2_000_000 and\n",
" production_line.on_the_line is None\n",
" ):\n",
" # If we have plenty of stock and we are stopped, we remain stopped.\n",
" print(\"Too much inventory. Staying in None!\")\n",
" return\n",
"\n",
" \n",
" if (\n",
" (\n",
" max(stock_by_product[\"roasted_beans_stock\"], 1) / max(stock_by_product[\"decaff_beans_stock\"], 1) < 2\n",
" ) and (\n",
" production_line.on_the_line != \"roasted_beans\"\n",
" )\n",
" ):\n",
" # If we are not producing roasted beans, and there is less than 2kg of roasted beans\n",
" # for each kg of decaff beans in stock, we switch to roasted beans.\n",
" production_line.switch_to_product(\"roasted_beans\")\n",
" print(\"I'm switching to roasted!\")\n",
" return\n",
"\n",
" if (\n",
" (\n",
" max(stock_by_product[\"roasted_beans_stock\"], 1) / max(stock_by_product[\"decaff_beans_stock\"], 1) > 2\n",
" ) and (\n",
" production_line.on_the_line != \"decaff_beans\"\n",
" )\n",
" ):\n",
" # If we are not producing decaff beans, and there is more than 2kg of roasted beans\n",
" # for each kg of decaff beans in stock, we switch to decaff beans.\n",
" production_line.switch_to_product(\"decaff_beans\")\n",
" print(\"I'm switching to decaff!\")\n",
" return\n",
"\n",
"\n",
"###\n",
"# 3. Assemble a simulation configuration\n",
"### \n",
"\n",
"# In order to run as Simulation, you must prepare a config. The config allows \n",
"# you to pass in your policies as well as to modify different parts of the \n",
"# simulation so you can recreate reality accurately. You can find each argument\n",
"# explained below.\n",
"\n",
"an_example_config = SimulationConfig(\n",
" starting_stock_raw_beans=20_000_000, \n",
" # ^ How many kgs of raw coffee beans does the warehouse start with.\n",
" starting_stock_roasted_beans=1_000_000,\n",
" # ^ How many kgs of roasted coffee beans does the warehouse start with. \n",
" starting_stock_decaff_beans=500_000,\n",
" # ^ How many kgs of decaff coffee beans does the warehouse start with.\n",
" demand_generator_raw_beans=lambda: np.random.poisson(5/7) * np.random.normal(300_000, 50_000),\n",
" # ^ A function that generates demand for raw beans. This gets called daily.\n",
" # The return units should be kilograms.\n",
" demand_generator_roasted_beans=lambda: np.random.poisson(4/7) * np.random.triangular(200_000, 250_000, 300_000),\n",
" # ^ Same as above but for roasted beans.\n",
" demand_generator_decaff_beans=lambda: np.random.poisson(1/7) * np.random.triangular(200_000, 250_000, 300_000),\n",
" # ^ Same as above but for decaff beans.\n",
" lead_time_generator_raw_beans=lambda: int(np.random.normal(30, 1)), \n",
" # ^ A function that generates the lead times for ships going from Latin America\n",
" # to Diemen. This gets called everytime you place an order to get more raw\n",
" # beans. Should return an integer number of days. \n",
" purchaser=a_simple_purchaser,\n",
" # ^ Here you pass your purchasing policy function.\n",
" production_line_switcher=a_simple_line_manager,\n",
" # ^ Here you pass your production line management policy.\n",
" roasted_beans_daily_production=250_000,\n",
" # ^ The capacity of normal bean roasting, in kgs per day.\n",
" decaff_beans_daily_production=150_000\n",
" # ^ The capacity of decaff bean roasting, in kgs per day.\n",
")\n",
"\n",
"###\n",
"# 4. Run a simulation\n",
"###\n",
"\n",
"# The Simulation class is the code that actually runs a simulation. It takes a \n",
"# SimulationConfig as an input, and returns a SimulationResult as an output.\n",
"\n",
"example_simulation = Simulation(\n",
" config=an_example_config, # The config you build goes here\n",
" verbose=True # This shows daily details. Turn to False if you don't want to see them.\n",
")\n",
"\n",
"# Let's run the simulation and store the results\n",
"raw_results, roasted_results, decaff_results = example_simulation.run()\n",
"\n",
"\n",
"###\n",
"# 5. Fetch results\n",
"###\n",
"\n",
"# The simulation will provide you back with three SimulationResult variables, \n",
"# one for each type of coffee bean. You can use this objects to make some quick\n",
"# plots, and also to access the raw data about the stock and demand throughout\n",
"# the simulation for each product.\n",
"\n",
"# In the next cells, you will find a few examples on how to explore these \n",
"# results\n"
],
"metadata": {
"id": "cYHFWRrm3-ns"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"source": [
"raw_results.plot_stock_history()\n",
"roasted_results.plot_stock_history()\n",
"decaff_results.plot_stock_history()"
],
"metadata": {
"id": "iXeAFhIp7848"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"source": [
"raw_results.plot_stock_distribution()\n",
"roasted_results.plot_stock_distribution()\n",
"decaff_results.plot_stock_distribution()"
],
"metadata": {
"id": "RLRHLyVOiysH"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"source": [
"for product, result in ((\"raw\", raw_results), (\"roasted\", roasted_results), (\"decaff\", decaff_results)):\n",
" print(f\"{product} beans service level: {result.service_level()}\")\n",
" print(f\"{product} beans mean stock: {result.mean_stock_level()}\")\n",
" print(f\"{product} beans median stock: {result.median_stock_level()}\")\n",
" print(f\"{product} beans stdev stock: {result.stdev_stock_level()}\")\n",
" print(f\"{product} beans mean demand: {result.mean_demand()}\")"
],
"metadata": {
"id": "Cr-Txnk9jFJK"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"source": [
"for product, result in ((\"raw\", raw_results), (\"roasted\", roasted_results), (\"decaff\", decaff_results)):\n",
" print(f\"Daily stock distribution summary for {product} beans:\")\n",
" print(result.stock_level_summary())"
],
"metadata": {
"id": "z1Ixv0bHqD65"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"source": [
"# Finally, you can access the raw data with the following attributes\n",
"print(roasted_results.stock_states)\n",
"print(roasted_results.demand_by_day)"
],
"metadata": {
"id": "-pw38uGpm-Aa"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"# Your turn\n",
"\n",
"Run the previous cells in order to load the required packages and code. Once you\n",
"have done that, you can start building your own code below."
],
"metadata": {
"id": "qSOiFi9OmgUR"
}
},
{
"cell_type": "code",
"source": [],
"metadata": {
"id": "fOZ7KhC5rgYc"
},
"execution_count": null,
"outputs": []
}
]
}

BIN
pds/cases/case_2/case_2.zip Normal file

Binary file not shown.

View file

@ -0,0 +1,689 @@
{
"nbformat": 4,
"nbformat_minor": 0,
"metadata": {
"colab": {
"provenance": []
},
"kernelspec": {
"name": "python3",
"display_name": "Python 3"
},
"language_info": {
"name": "python"
}
},
"cells": [
{
"cell_type": "markdown",
"source": [
"# Case 2 - Student Notebook"
],
"metadata": {
"id": "oHWpkTqMeBMq"
}
},
{
"cell_type": "markdown",
"source": [
"## Imports and data loading"
],
"metadata": {
"id": "BhR7Z_UqeEgm"
}
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "HQwlyagxfXRU"
},
"outputs": [],
"source": [
"import io\n",
"import pandas as pd\n",
"import numpy as np\n",
"import seaborn as sns\n",
"from google.colab import files\n",
"from datetime import datetime, timedelta"
]
},
{
"cell_type": "code",
"source": [
"# This avoids scientific notation on millions and larger\n",
"pd.set_option('display.float_format', lambda x: '%.2f' % x)"
],
"metadata": {
"id": "wsv3gEB9qmp6"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"source": [
"# Upload files from your computer here\n",
"# Run the cell and click the \"Browse\" button to upload the provided CSV \n",
"# files\n",
"uploaded = files.upload()"
],
"metadata": {
"id": "4psao7htcAwr"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"source": [
"# Read the files as pandas dataframes and print them so you can check that the\n",
"# process went fine\n",
"\n",
"served_orders = pd.read_csv(io.BytesIO(uploaded['served_orders.csv']))\n",
"sourcing_events = pd.read_csv(io.BytesIO(uploaded['sourcing_events.csv']))\n",
"\n",
"for table in (served_orders, sourcing_events):\n",
" print(table.head())"
],
"metadata": {
"id": "X8N0PZ4qcOls"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"## Analysis"
],
"metadata": {
"id": "GKty74ZfuBEG"
}
},
{
"cell_type": "code",
"source": [
"# You can use this space to analyse the provided data as you see fit."
],
"metadata": {
"id": "CuYSBC2auDIG"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"## Provided code\n"
],
"metadata": {
"id": "KM5HVJiIfYPC"
}
},
{
"cell_type": "code",
"source": [
"# This cell includes provided code to run simulations.\n",
"# You do not need to understand the internals of this code. Feel free to just\n",
"# run it and move forward.\n",
"# The next section explains how you can call this code to run your simulations.\n",
"\n",
"base = datetime(2022,1,1)\n",
"dates_in_2022 = [base + timedelta(days=x) for x in range(365)]\n",
"\n",
"class SimulationResult:\n",
"\n",
" def __init__(self, stock_states, demand_by_day, sourcing_events):\n",
" self.stock_states = stock_states\n",
" self.demand_by_day = demand_by_day\n",
" self.sourcing_events = sourcing_events\n",
"\n",
" def plot_stock_history(self):\n",
" sns.lineplot(x=dates_in_2022, y=self.stock_states)\n",
"\n",
" def plot_stock_distribution(self):\n",
" sns.histplot(x=self.stock_states, kde=True)\n",
"\n",
" def service_level(self):\n",
" return (self.stock_states > 0 ).astype(int).mean()\n",
"\n",
" def stock_level_summary(self):\n",
" print(\n",
" pd.DataFrame(self.stock_states).describe()\n",
" ) \n",
" \n",
" def mean_stock_level(self):\n",
" return self.stock_states.mean()\n",
"\n",
" def median_stock_level(self):\n",
" return np.median(self.stock_states)\n",
"\n",
" def stdev_stock_level(self):\n",
" return self.stock_states.std()\n",
"\n",
" def mean_demand(self):\n",
" return self.demand_by_day.mean()\n",
" \n",
" def number_of_purchase_orders_placed(self):\n",
" return len(self.sourcing_events)\n",
"\n",
"\n",
"class SimulationConfig:\n",
"\n",
" def __init__(\n",
" self, \n",
" starting_stock_raw_beans,\n",
" starting_stock_roasted_beans,\n",
" starting_stock_decaff_beans, \n",
" demand_generator_raw_beans,\n",
" demand_generator_roasted_beans,\n",
" demand_generator_decaff_beans,\n",
" lead_time_generator_raw_beans, \n",
" purchaser,\n",
" production_line_switcher,\n",
" roasted_beans_daily_production,\n",
" decaff_beans_daily_production\n",
" ):\n",
" self.starting_stock_raw_beans = starting_stock_raw_beans\n",
" self.starting_stock_roasted_beans = starting_stock_roasted_beans\n",
" self.starting_stock_decaff_beans = starting_stock_decaff_beans\n",
" self.demand_generator_raw_beans = demand_generator_raw_beans\n",
" self.demand_generator_roasted_beans = demand_generator_roasted_beans\n",
" self.demand_generator_decaff_beans = demand_generator_decaff_beans\n",
" self.lead_time_generator_raw_beans = lead_time_generator_raw_beans\n",
" self.purchaser = purchaser\n",
" self.production_line_switcher = production_line_switcher\n",
" self.roasted_beans_daily_production = roasted_beans_daily_production\n",
" self.decaff_beans_daily_production = decaff_beans_daily_production\n",
"\n",
"class PurchaseOrder:\n",
" \n",
" def __init__(self, amount, request_date, delivery_date):\n",
" self.amount = amount\n",
" self.request_date = request_date\n",
" self.delivery_date = delivery_date\n",
"\n",
" def __repr__(self):\n",
" return f\"Order of {self.amount:.0f}, requested on {self.request_date}, delivery on {self.delivery_date}.\"\n",
"\n",
"class ProductionLine:\n",
"\n",
" def __init__(self, products_and_rates, starting_product=None):\n",
" self.products_and_rates = products_and_rates\n",
" self.on_the_line = starting_product\n",
" self.next_on_the_line = None\n",
" self.days_on_current_batch = 0\n",
"\n",
" def tick(self):\n",
" if self.on_the_line in self.products_and_rates:\n",
" self.days_on_current_batch += 1\n",
" return self.products_and_rates[self.on_the_line]\n",
"\n",
" if self.on_the_line is None and self.next_on_the_line is None:\n",
" self.days_on_current_batch += 1\n",
" return 0\n",
" if self.on_the_line is None:\n",
" self.on_the_line = self.next_on_the_line\n",
" self.next_on_the_line = None\n",
" self.days_on_current_batch = 0\n",
" return 0\n",
" \n",
" def switch_to_product(self, next_product):\n",
" self.on_the_line = None\n",
" self.next_on_the_line = next_product\n",
" self.days_on_current_batch = -1\n",
"\n",
"\n",
"class Simulation:\n",
" \n",
" def __init__(self, config: SimulationConfig, verbose=False):\n",
" self._config = config\n",
" self.verbose = verbose\n",
"\n",
" def run(self):\n",
"\n",
" stock_raw_beans = np.array([self._config.starting_stock_raw_beans])\n",
" stock_roasted_beans = np.array([self._config.starting_stock_roasted_beans])\n",
" stock_decaff_beans = np.array([self._config.starting_stock_decaff_beans])\n",
"\n",
" opened_orders = []\n",
" ongoing_orders = {}\n",
"\n",
" demand_by_day_raw_beans = np.array(list())\n",
" demand_by_day_roasted_beans = np.array(list())\n",
" demand_by_day_decaff_beans = np.array(list())\n",
"\n",
" production_line = ProductionLine(\n",
" products_and_rates={\n",
" \"roasted_beans\": self._config.roasted_beans_daily_production,\n",
" \"decaff_beans\": self._config.decaff_beans_daily_production\n",
" },\n",
" starting_product=\"roasted_beans\"\n",
" )\n",
"\n",
" production_line_switcher = self._config.production_line_switcher\n",
" \n",
" for day in dates_in_2022:\n",
"\n",
" # General\n",
" if self.verbose:\n",
" print(\"-------------------------\")\n",
" print(f\"Simulating day: {day}\")\n",
" current_stock_raw_beans = stock_raw_beans[-1]\n",
" current_stock_roasted_beans = stock_roasted_beans[-1]\n",
" current_stock_decaff_beans = stock_decaff_beans[-1]\n",
"\n",
" # Generate demand\n",
" if self.verbose:\n",
" print(f\"Starting stock raw beans: {current_stock_raw_beans:.0f}\")\n",
" print(f\"Starting stock roasted beans: {current_stock_roasted_beans:.0f}\")\n",
" print(f\"Starting stock decaff beans: {current_stock_decaff_beans:.0f}\")\n",
" demand_for_this_day_raw_beans = self._config.demand_generator_raw_beans()\n",
" demand_for_this_day_roasted_beans = self._config.demand_generator_roasted_beans()\n",
" demand_for_this_day_decaff_beans = self._config.demand_generator_decaff_beans()\n",
" if self.verbose:\n",
" print(f\"Generated raw beans demand for today: {demand_for_this_day_raw_beans:.0f}\")\n",
" print(f\"Generated roasted beans demand for today: {demand_for_this_day_roasted_beans:.0f}\")\n",
" print(f\"Generated decaff beans demand for today: {demand_for_this_day_decaff_beans:.0f}\")\n",
" demand_by_day_raw_beans = np.append(demand_by_day_raw_beans, [demand_for_this_day_raw_beans])\n",
" demand_by_day_roasted_beans = np.append(demand_by_day_roasted_beans, [demand_for_this_day_roasted_beans])\n",
" demand_by_day_decaff_beans = np.append(demand_by_day_decaff_beans, [demand_for_this_day_decaff_beans])\n",
"\n",
"\n",
" # Receive orders and place orders\n",
" raw_beans_received_this_day = 0\n",
" if day in ongoing_orders:\n",
" order_delivered_today = ongoing_orders.pop(day)\n",
" raw_beans_received_this_day = order_delivered_today.amount\n",
" if self.verbose:\n",
" print(f\"Raw beans received today: {raw_beans_received_this_day:.0f}\")\n",
" \n",
" order_to_make = self._config.purchaser(\n",
" day, \n",
" current_stock_raw_beans, \n",
" ongoing_orders,\n",
" self._config.lead_time_generator_raw_beans\n",
" )\n",
" if order_to_make:\n",
" if self.verbose:\n",
" print(f\"Placing a new order: {order_to_make}\")\n",
" opened_orders.append(order_to_make)\n",
" ongoing_orders[order_to_make.delivery_date] = order_to_make\n",
"\n",
"\n",
" # Decide on production today and produce whatever gets produced or wait during the changeover\n",
"\n",
" print(f\"Product on the line: {production_line.on_the_line}\")\n",
"\n",
" production_line_switcher(\n",
" production_line,\n",
" {\n",
" \"raw_beans_stock\": current_stock_raw_beans,\n",
" \"roasted_beans_stock\": current_stock_roasted_beans,\n",
" \"decaff_beans_stock\": current_stock_decaff_beans\n",
" }\n",
" )\n",
"\n",
" if production_line.on_the_line == \"roasted_beans\":\n",
" roasted_beans_produced_this_day = production_line.tick()\n",
" decaff_beans_produced_this_day = 0\n",
" if production_line.on_the_line == \"decaff_beans\":\n",
" roasted_beans_produced_this_day = 0\n",
" decaff_beans_produced_this_day = production_line.tick()\n",
" if production_line.on_the_line not in (\"roasted_beans\", \"decaff_beans\"):\n",
" production_line.tick()\n",
" roasted_beans_produced_this_day = 0\n",
" decaff_beans_produced_this_day = 0\n",
"\n",
" raw_beans_consumed_in_production = roasted_beans_produced_this_day + decaff_beans_produced_this_day\n",
" \n",
" if self.verbose:\n",
" print(f\"Roasted beans produced today: {roasted_beans_produced_this_day}\")\n",
" print(f\"Decaff beans produced today: {decaff_beans_produced_this_day}\")\n",
" print(f\"Product {production_line.on_the_line} has been on the line for {production_line.days_on_current_batch} days.\")\n",
"\n",
" # Update stocks with the changes of the day\n",
"\n",
" current_stock_raw_beans = (\n",
" current_stock_raw_beans + \n",
" raw_beans_received_this_day - \n",
" demand_for_this_day_raw_beans -\n",
" raw_beans_consumed_in_production\n",
" )\n",
" stock_raw_beans = np.append(stock_raw_beans, [current_stock_raw_beans])\n",
" current_stock_roasted_beans = current_stock_roasted_beans + roasted_beans_produced_this_day - demand_for_this_day_roasted_beans\n",
" stock_roasted_beans = np.append(stock_roasted_beans, [current_stock_roasted_beans])\n",
" current_stock_decaff_beans = current_stock_decaff_beans + decaff_beans_produced_this_day - demand_for_this_day_decaff_beans\n",
" stock_decaff_beans = np.append(stock_decaff_beans, [current_stock_decaff_beans])\n",
" \n",
" # Remove starting stock\n",
" stock_raw_beans = np.delete(stock_raw_beans, 0) \n",
" stock_roasted_beans = np.delete(stock_roasted_beans, 0)\n",
" stock_decaff_beans = np.delete(stock_decaff_beans, 0)\n",
" \n",
" raw_beans_results = SimulationResult(\n",
" stock_states=stock_raw_beans, \n",
" demand_by_day=demand_by_day_raw_beans, \n",
" sourcing_events=opened_orders\n",
" )\n",
" roasted_beans_results = SimulationResult(\n",
" stock_states=stock_roasted_beans, \n",
" demand_by_day=demand_by_day_roasted_beans, \n",
" sourcing_events=None\n",
" )\n",
" decaff_beans_results = SimulationResult(\n",
" stock_states=stock_decaff_beans, \n",
" demand_by_day=demand_by_day_decaff_beans, \n",
" sourcing_events=opened_orders\n",
" )\n",
"\n",
"\n",
" return raw_beans_results, roasted_beans_results, decaff_beans_results\n",
" \n",
"\n",
"\n"
],
"metadata": {
"id": "cGcEzAIDfa8s"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"## Usage Example"
],
"metadata": {
"id": "Xul3y3LpYKiY"
}
},
{
"cell_type": "code",
"source": [
"# Read this block carefully to understand how to prepare parameters,\n",
"# run simulations and fetch the results.\n",
"\n",
"# These are the steps we will follow:\n",
"# 1. Prepare a purchaser function\n",
"# 2. Prepare a production line management function\n",
"# 3. Assemble a simulation configuration with your parameters and assumptions\n",
"# 4. Run a simulation\n",
"# 5. Fetch results\n",
"\n",
"# AN IMPORTANT NOTE: all numbers in this example are random. You will most \n",
"# surely have to modify them and fit them to the info and data that has been\n",
"# provided to you.\n",
"\n",
"###\n",
"# 1. Prepare a purchaser function\n",
"###\n",
"\n",
"# The purchase function handles the decisions of whether to buy more raw coffee\n",
"# beans to send to Diemen, and how much to buy. It gets called once per simulated\n",
"# day, so you have an oportunity to place orders each day.\n",
"\n",
"\n",
"# You can name your function whatever you like, but the arguments should have\n",
"# the same names and order as show below.\n",
"def a_simple_purchaser(\n",
" day, # The current day\n",
" current_stock, # The level of raw beans stock on that day\n",
" ongoing_orders, # A dictionary with the open purchase orders\n",
" lead_time_generator # The same lead time generator you pass to the Simulation Config\n",
" ):\n",
" # Your code goes here. You can make any logic you want. Just make sure to return\n",
" # None if you don't want to place an order and to return a PurchaseOrder when\n",
" # you want to buy. The policies below are a simple example to inspire you: you\n",
" # definitely want to modify the numbers and/or followed logic.\n",
" \n",
" if ongoing_orders or current_stock > 15_000_000:\n",
" # If we are already waiting for an order to arrive or we have enough stock\n",
" # we don't request more goods.\n",
" return None\n",
"\n",
" if current_stock <= 15_000_000:\n",
" # If the stock is going low, we request more.\n",
" return PurchaseOrder(\n",
" amount=15_000_000, # The amount to order. This is the only bit you change.\n",
" request_date=day, # Always copy paste this.\n",
" delivery_date=day + timedelta(days=lead_time_generator()) # Always copy paste this.\n",
" )\n",
"\n",
"\n",
"###\n",
"# 2. Prepare a production line management function\n",
"### \n",
"\n",
"# The line manager function handles the decision of whether the production line\n",
"# should change to a different product (or no product at all). It gets called \n",
"# once per day.\n",
"\n",
"# You can name your function whatever you like, but the arguments should have\n",
"# the same names and order as show below.\n",
"def a_simple_line_manager(\n",
" production_line, # Details about the production line\n",
" stock_by_product # A summary of the stock that updates each day\n",
"):\n",
" # Your code goes here. You can make any logic you want. Just make sure to \n",
" # switch to None if you don't want to change the product on the line. If you want\n",
" # to switch the product on the line, call production_line.switch_to_product(\"product name\"). \n",
" # The policies below are a simple example to inspire you: you definitely want \n",
" # to modify the numbers and/or followed logic.\n",
" \n",
" # If the current product has been less than 21 days on the line, we don't \n",
" # change anything.\n",
" if production_line.days_on_current_batch < 14:\n",
" return\n",
" \n",
" if (\n",
" stock_by_product[\"roasted_beans_stock\"] > 2_000_000 and \n",
" stock_by_product[\"decaff_beans_stock\"] > 2_000_000 and\n",
" production_line.on_the_line is not None\n",
" ):\n",
" # If we have plenty of stock and we are still producing, we stop the line\n",
" # by switching to None.\n",
" production_line.switch_to_product(None)\n",
" print(\"Too much inventory. I'm switching to None!\")\n",
" return\n",
"\n",
" if (\n",
" stock_by_product[\"roasted_beans_stock\"] > 2_000_000 and \n",
" stock_by_product[\"decaff_beans_stock\"] > 2_000_000 and\n",
" production_line.on_the_line is None\n",
" ):\n",
" # If we have plenty of stock and we are stopped, we remain stopped.\n",
" print(\"Too much inventory. Staying in None!\")\n",
" return\n",
"\n",
" \n",
" if (\n",
" (\n",
" max(stock_by_product[\"roasted_beans_stock\"], 1) / max(stock_by_product[\"decaff_beans_stock\"], 1) < 2\n",
" ) and (\n",
" production_line.on_the_line != \"roasted_beans\"\n",
" )\n",
" ):\n",
" # If we are not producing roasted beans, and there is less than 2kg of roasted beans\n",
" # for each kg of decaff beans in stock, we switch to roasted beans.\n",
" production_line.switch_to_product(\"roasted_beans\")\n",
" print(\"I'm switching to roasted!\")\n",
" return\n",
"\n",
" if (\n",
" (\n",
" max(stock_by_product[\"roasted_beans_stock\"], 1) / max(stock_by_product[\"decaff_beans_stock\"], 1) > 2\n",
" ) and (\n",
" production_line.on_the_line != \"decaff_beans\"\n",
" )\n",
" ):\n",
" # If we are not producing decaff beans, and there is more than 2kg of roasted beans\n",
" # for each kg of decaff beans in stock, we switch to decaff beans.\n",
" production_line.switch_to_product(\"decaff_beans\")\n",
" print(\"I'm switching to decaff!\")\n",
" return\n",
"\n",
"\n",
"###\n",
"# 3. Assemble a simulation configuration\n",
"### \n",
"\n",
"# In order to run as Simulation, you must prepare a config. The config allows \n",
"# you to pass in your policies as well as to modify different parts of the \n",
"# simulation so you can recreate reality accurately. You can find each argument\n",
"# explained below.\n",
"\n",
"an_example_config = SimulationConfig(\n",
" starting_stock_raw_beans=20_000, \n",
" # ^ How many kgs of raw coffee beans does the warehouse start with.\n",
" starting_stock_roasted_beans=1_000,\n",
" # ^ How many kgs of roasted coffee beans does the warehouse start with. \n",
" starting_stock_decaff_beans=500,\n",
" # ^ How many kgs of decaff coffee beans does the warehouse start with.\n",
" demand_generator_raw_beans=lambda: np.random.poisson(12/7) * np.random.normal(300, 50),\n",
" # ^ A function that generates demand for raw beans. This gets called daily.\n",
" # The return units should be kilograms.\n",
" demand_generator_roasted_beans=lambda: np.random.poisson(2/7) * np.random.triangular(200, 250, 300),\n",
" # ^ Same as above but for roasted beans.\n",
" demand_generator_decaff_beans=lambda: np.random.poisson(2/7) * np.random.triangular(200, 250, 300),\n",
" # ^ Same as above but for decaff beans.\n",
" lead_time_generator_raw_beans=lambda: int(np.random.normal(10, 1)), \n",
" # ^ A function that generates the lead times for ships going from Latin America\n",
" # to Diemen. This gets called everytime you place an order to get more raw\n",
" # beans. Should return an integer number of days. \n",
" purchaser=a_simple_purchaser,\n",
" # ^ Here you pass your purchasing policy function.\n",
" production_line_switcher=a_simple_line_manager,\n",
" # ^ Here you pass your production line management policy.\n",
" roasted_beans_daily_production=400,\n",
" # ^ The capacity of normal bean roasting, in kgs per day.\n",
" decaff_beans_daily_production=400\n",
" # ^ The capacity of decaff bean roasting, in kgs per day.\n",
")\n",
"\n",
"###\n",
"# 4. Run a simulation\n",
"###\n",
"\n",
"# The Simulation class is the code that actually runs a simulation. It takes a \n",
"# SimulationConfig as an input, and returns a SimulationResult as an output.\n",
"\n",
"example_simulation = Simulation(\n",
" config=an_example_config, # The config you build goes here\n",
" verbose=True # This shows daily details. Turn to False if you don't want to see them.\n",
")\n",
"\n",
"# Let's run the simulation and store the results\n",
"raw_results, roasted_results, decaff_results = example_simulation.run()\n",
"\n",
"\n",
"###\n",
"# 5. Fetch results\n",
"###\n",
"\n",
"# The simulation will provide you back with three SimulationResult variables, \n",
"# one for each type of coffee bean. You can use this objects to make some quick\n",
"# plots, and also to access the raw data about the stock and demand throughout\n",
"# the simulation for each product.\n",
"\n",
"# In the next cells, you will find a few examples on how to explore these \n",
"# results\n"
],
"metadata": {
"id": "cYHFWRrm3-ns"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"source": [
"raw_results.plot_stock_history()\n",
"roasted_results.plot_stock_history()\n",
"decaff_results.plot_stock_history()"
],
"metadata": {
"id": "iXeAFhIp7848"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"source": [
"raw_results.plot_stock_distribution()\n",
"roasted_results.plot_stock_distribution()\n",
"decaff_results.plot_stock_distribution()"
],
"metadata": {
"id": "RLRHLyVOiysH"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"source": [
"for product, result in ((\"raw\", raw_results), (\"roasted\", roasted_results), (\"decaff\", decaff_results)):\n",
" print(f\"{product} beans service level: {result.service_level()}\")\n",
" print(f\"{product} beans mean stock: {result.mean_stock_level()}\")\n",
" print(f\"{product} beans median stock: {result.median_stock_level()}\")\n",
" print(f\"{product} beans stdev stock: {result.stdev_stock_level()}\")\n",
" print(f\"{product} beans mean demand: {result.mean_demand()}\")"
],
"metadata": {
"id": "Cr-Txnk9jFJK"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"source": [
"for product, result in ((\"raw\", raw_results), (\"roasted\", roasted_results), (\"decaff\", decaff_results)):\n",
" print(f\"Daily stock distribution summary for {product} beans:\")\n",
" print(result.stock_level_summary())"
],
"metadata": {
"id": "z1Ixv0bHqD65"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"source": [
"# Finally, you can access the raw data with the following attributes\n",
"print(roasted_results.stock_states)\n",
"print(roasted_results.demand_by_day)"
],
"metadata": {
"id": "-pw38uGpm-Aa"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"# Your turn\n",
"\n",
"Run the previous cells in order to load the required packages and code. Once you\n",
"have done that, you can start building your own code below."
],
"metadata": {
"id": "qSOiFi9OmgUR"
}
},
{
"cell_type": "code",
"source": [],
"metadata": {
"id": "fOZ7KhC5rgYc"
},
"execution_count": null,
"outputs": []
}
]
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,508 @@
date,amount
2021-01-01,326717
2021-01-02,275529
2021-01-03,389027
2021-01-04,362822
2021-01-05,347345
2021-01-06,345372
2021-01-08,275213
2021-01-09,241112
2021-01-11,299890
2021-01-12,302046
2021-01-13,337515
2021-01-14,300098
2021-01-15,316018
2021-01-16,287948
2021-01-17,274375
2021-01-19,286417
2021-01-20,348557
2021-01-23,301051
2021-01-24,311603
2021-01-25,236287
2021-01-26,210067
2021-01-27,228554
2021-01-28,211761
2021-01-29,243355
2021-01-30,319297
2021-01-31,307556
2021-02-01,361433
2021-02-02,156577
2021-02-03,366125
2021-02-04,250933
2021-02-06,295484
2021-02-07,348856
2021-02-08,309115
2021-02-09,330372
2021-02-11,244318
2021-02-13,306637
2021-02-15,288287
2021-02-17,235796
2021-02-19,277708
2021-02-20,261709
2021-02-21,282469
2021-02-22,308186
2021-02-23,262888
2021-02-24,328054
2021-02-25,270879
2021-02-26,274060
2021-02-27,336799
2021-02-28,405675
2021-03-01,304497
2021-03-02,404511
2021-03-03,306753
2021-03-05,280310
2021-03-07,316546
2021-03-08,353061
2021-03-10,302688
2021-03-11,335184
2021-03-13,329087
2021-03-14,342549
2021-03-15,274529
2021-03-17,301187
2021-03-18,301054
2021-03-19,298410
2021-03-20,360782
2021-03-21,238877
2021-03-22,448062
2021-03-23,325327
2021-03-24,274940
2021-03-25,348203
2021-03-28,341127
2021-03-29,239224
2021-03-31,210169
2021-04-03,401641
2021-04-04,307472
2021-04-07,287169
2021-04-08,261859
2021-04-10,262021
2021-04-11,280504
2021-04-13,267181
2021-04-14,271747
2021-04-15,280964
2021-04-16,374444
2021-04-17,308427
2021-04-18,297192
2021-04-19,245591
2021-04-20,257421
2021-04-21,215640
2021-04-22,351177
2021-04-24,283042
2021-04-25,274782
2021-04-26,293927
2021-04-27,208290
2021-04-28,350390
2021-04-30,247523
2021-05-01,235533
2021-05-04,340665
2021-05-05,309549
2021-05-06,273190
2021-05-08,351836
2021-05-10,262566
2021-05-11,306779
2021-05-13,294170
2021-05-15,320050
2021-05-16,282865
2021-05-18,290425
2021-05-19,350460
2021-05-20,344342
2021-05-21,342156
2021-05-22,323964
2021-05-23,267025
2021-05-24,277235
2021-05-25,296506
2021-05-26,237995
2021-05-27,263647
2021-05-28,270969
2021-05-30,225607
2021-06-01,335635
2021-06-02,283243
2021-06-03,286625
2021-06-04,277667
2021-06-05,295206
2021-06-07,294017
2021-06-08,293425
2021-06-09,235027
2021-06-10,198216
2021-06-13,310663
2021-06-17,331789
2021-06-20,265635
2021-06-21,224847
2021-06-22,263459
2021-06-24,288901
2021-06-25,285640
2021-06-26,377020
2021-06-27,264235
2021-06-29,255274
2021-06-30,334023
2021-07-03,239532
2021-07-04,332379
2021-07-06,304824
2021-07-07,233457
2021-07-08,270742
2021-07-09,275372
2021-07-10,290695
2021-07-11,354016
2021-07-14,308333
2021-07-16,330349
2021-07-17,279489
2021-07-18,278827
2021-07-19,246737
2021-07-21,350309
2021-07-22,253080
2021-07-23,318424
2021-07-24,283229
2021-07-27,387702
2021-07-28,359999
2021-07-29,262529
2021-07-31,329220
2021-08-02,245560
2021-08-03,371978
2021-08-04,316774
2021-08-05,269694
2021-08-06,321235
2021-08-07,337118
2021-08-08,336951
2021-08-09,332485
2021-08-10,340650
2021-08-13,292215
2021-08-15,228006
2021-08-16,261552
2021-08-18,289464
2021-08-19,331391
2021-08-20,335894
2021-08-22,345662
2021-08-25,250506
2021-08-26,324127
2021-08-29,300704
2021-08-30,216277
2021-09-01,390319
2021-09-02,302408
2021-09-04,236882
2021-09-05,328498
2021-09-06,373802
2021-09-07,257518
2021-09-08,315544
2021-09-09,331703
2021-09-10,295623
2021-09-11,262576
2021-09-12,330202
2021-09-16,334117
2021-09-20,318918
2021-09-21,333662
2021-09-22,303395
2021-09-23,343089
2021-09-25,246715
2021-09-26,365227
2021-09-30,279069
2021-10-01,274862
2021-10-03,351246
2021-10-04,224027
2021-10-06,315712
2021-10-08,207710
2021-10-10,313050
2021-10-11,320580
2021-10-12,317107
2021-10-13,255801
2021-10-14,336348
2021-10-15,326889
2021-10-17,388537
2021-10-18,358864
2021-10-20,334452
2021-10-21,315859
2021-10-22,277687
2021-10-23,275894
2021-10-24,348859
2021-10-25,345117
2021-10-27,297398
2021-10-28,279729
2021-10-30,255008
2021-11-01,284791
2021-11-02,366217
2021-11-03,273734
2021-11-04,305492
2021-11-05,317993
2021-11-06,271900
2021-11-07,305045
2021-11-09,253836
2021-11-10,350490
2021-11-12,219134
2021-11-13,294765
2021-11-14,343896
2021-11-15,289186
2021-11-16,180454
2021-11-17,311418
2021-11-18,282468
2021-11-19,287422
2021-11-20,257198
2021-11-21,263091
2021-11-22,300996
2021-11-23,264009
2021-11-24,329625
2021-11-25,324373
2021-11-26,280376
2021-11-27,288525
2021-11-28,363799
2021-11-29,282045
2021-12-01,341980
2021-12-02,312170
2021-12-03,261025
2021-12-04,328802
2021-12-07,302062
2021-12-09,262532
2021-12-10,332557
2021-12-11,299194
2021-12-12,303596
2021-12-13,370443
2021-12-15,315328
2021-12-16,388561
2021-12-18,370054
2021-12-19,382960
2021-12-23,225437
2021-12-24,292496
2021-12-25,325016
2021-12-26,362916
2021-12-27,257472
2021-12-28,311747
2021-12-30,279110
2021-12-31,196848
2022-01-02,298355
2022-01-04,368999
2022-01-06,300327
2022-01-08,265559
2022-01-09,314257
2022-01-12,252150
2022-01-13,351088
2022-01-14,274378
2022-01-15,271547
2022-01-18,262993
2022-01-19,372874
2022-01-20,318061
2022-01-21,383376
2022-01-22,311890
2022-01-23,333323
2022-01-25,332426
2022-01-26,275171
2022-01-27,303853
2022-01-29,289020
2022-01-30,327096
2022-01-31,281682
2022-02-02,275490
2022-02-03,383535
2022-02-04,296316
2022-02-05,351592
2022-02-06,325564
2022-02-07,307185
2022-02-08,314678
2022-02-10,259845
2022-02-11,338493
2022-02-12,226951
2022-02-13,320409
2022-02-15,278782
2022-02-16,260904
2022-02-17,327639
2022-02-18,281008
2022-02-20,312974
2022-02-21,283881
2022-02-22,282495
2022-02-23,333772
2022-02-24,245480
2022-02-25,287406
2022-02-26,331523
2022-02-28,319978
2022-03-01,283928
2022-03-04,282503
2022-03-05,355399
2022-03-06,236853
2022-03-08,247027
2022-03-11,310629
2022-03-12,354012
2022-03-13,307891
2022-03-14,350998
2022-03-15,234012
2022-03-16,283284
2022-03-17,332824
2022-03-21,219997
2022-03-22,359558
2022-03-23,247878
2022-03-25,269752
2022-03-26,373325
2022-03-31,366303
2022-04-03,222577
2022-04-05,336306
2022-04-06,232151
2022-04-07,329687
2022-04-13,319182
2022-04-14,287659
2022-04-16,272320
2022-04-17,321487
2022-04-18,340860
2022-04-19,236354
2022-04-20,320432
2022-04-21,400641
2022-04-22,267561
2022-04-24,299550
2022-04-26,362361
2022-04-29,292377
2022-04-30,289468
2022-05-01,248577
2022-05-02,241467
2022-05-04,266245
2022-05-05,333287
2022-05-06,303953
2022-05-09,309336
2022-05-11,381499
2022-05-12,348890
2022-05-13,406413
2022-05-14,360551
2022-05-16,380784
2022-05-17,291891
2022-05-21,290653
2022-05-22,295487
2022-05-23,253226
2022-05-27,329659
2022-05-28,414924
2022-05-29,302741
2022-05-30,264788
2022-05-31,217593
2022-06-02,261180
2022-06-03,295243
2022-06-07,282270
2022-06-08,332795
2022-06-14,400955
2022-06-15,366259
2022-06-16,319197
2022-06-17,300354
2022-06-20,267838
2022-06-22,299957
2022-06-23,299123
2022-06-24,258038
2022-06-25,267308
2022-06-27,230123
2022-06-28,285768
2022-06-29,278569
2022-06-30,304007
2022-07-01,352665
2022-07-02,277065
2022-07-05,281774
2022-07-06,345073
2022-07-08,317068
2022-07-09,374064
2022-07-11,243048
2022-07-12,381713
2022-07-13,308419
2022-07-15,266591
2022-07-16,343427
2022-07-17,267296
2022-07-18,301904
2022-07-19,268985
2022-07-20,269354
2022-07-21,242209
2022-07-23,304538
2022-07-24,321849
2022-07-25,320558
2022-07-27,283398
2022-07-28,308368
2022-07-29,321836
2022-07-30,253049
2022-07-31,402333
2022-08-03,357724
2022-08-04,226488
2022-08-05,247356
2022-08-06,234899
2022-08-08,317464
2022-08-10,262772
2022-08-11,313256
2022-08-13,275101
2022-08-14,344720
2022-08-15,313842
2022-08-16,325843
2022-08-17,324050
2022-08-18,390448
2022-08-19,354854
2022-08-20,340872
2022-08-22,275545
2022-08-25,277279
2022-08-27,349831
2022-08-28,231753
2022-08-29,266744
2022-09-02,266383
2022-09-04,269079
2022-09-05,274862
2022-09-07,308898
2022-09-08,274465
2022-09-09,258048
2022-09-12,339535
2022-09-14,352963
2022-09-15,354002
2022-09-16,258307
2022-09-21,305506
2022-09-23,294024
2022-09-25,327077
2022-09-26,288372
2022-09-28,280282
2022-10-01,280759
2022-10-02,264145
2022-10-05,342762
2022-10-06,299775
2022-10-09,279429
2022-10-10,335244
2022-10-11,306739
2022-10-12,335628
2022-10-13,275652
2022-10-14,309627
2022-10-20,214583
2022-10-22,321149
2022-10-23,290458
2022-10-24,295040
2022-10-25,252892
2022-10-26,378766
2022-10-28,296984
2022-10-29,246649
2022-10-31,247637
2022-11-02,391106
2022-11-03,234747
2022-11-04,248504
2022-11-05,285295
2022-11-06,294848
2022-11-07,372159
2022-11-08,281897
2022-11-09,287656
2022-11-10,173868
2022-11-11,341964
2022-11-12,358992
2022-11-13,335488
2022-11-15,298316
2022-11-16,316291
2022-11-17,338106
2022-11-18,280470
2022-11-20,380544
2022-11-21,272462
2022-11-22,263061
2022-11-23,341689
2022-11-24,393098
2022-11-25,281410
2022-11-26,315452
2022-11-29,393891
2022-12-03,261234
2022-12-04,206973
2022-12-05,296886
2022-12-06,325265
2022-12-07,272677
2022-12-09,276582
2022-12-10,297452
2022-12-11,267674
2022-12-13,310775
2022-12-14,325403
2022-12-17,366387
2022-12-19,271004
2022-12-20,274431
2022-12-21,321314
2022-12-22,255268
2022-12-23,348741
2022-12-24,158015
2022-12-25,296246
2022-12-26,303504
2022-12-28,262996
2022-12-29,317554
2022-12-30,274724
2022-12-31,319566
1 date amount
2 2021-01-01 326717
3 2021-01-02 275529
4 2021-01-03 389027
5 2021-01-04 362822
6 2021-01-05 347345
7 2021-01-06 345372
8 2021-01-08 275213
9 2021-01-09 241112
10 2021-01-11 299890
11 2021-01-12 302046
12 2021-01-13 337515
13 2021-01-14 300098
14 2021-01-15 316018
15 2021-01-16 287948
16 2021-01-17 274375
17 2021-01-19 286417
18 2021-01-20 348557
19 2021-01-23 301051
20 2021-01-24 311603
21 2021-01-25 236287
22 2021-01-26 210067
23 2021-01-27 228554
24 2021-01-28 211761
25 2021-01-29 243355
26 2021-01-30 319297
27 2021-01-31 307556
28 2021-02-01 361433
29 2021-02-02 156577
30 2021-02-03 366125
31 2021-02-04 250933
32 2021-02-06 295484
33 2021-02-07 348856
34 2021-02-08 309115
35 2021-02-09 330372
36 2021-02-11 244318
37 2021-02-13 306637
38 2021-02-15 288287
39 2021-02-17 235796
40 2021-02-19 277708
41 2021-02-20 261709
42 2021-02-21 282469
43 2021-02-22 308186
44 2021-02-23 262888
45 2021-02-24 328054
46 2021-02-25 270879
47 2021-02-26 274060
48 2021-02-27 336799
49 2021-02-28 405675
50 2021-03-01 304497
51 2021-03-02 404511
52 2021-03-03 306753
53 2021-03-05 280310
54 2021-03-07 316546
55 2021-03-08 353061
56 2021-03-10 302688
57 2021-03-11 335184
58 2021-03-13 329087
59 2021-03-14 342549
60 2021-03-15 274529
61 2021-03-17 301187
62 2021-03-18 301054
63 2021-03-19 298410
64 2021-03-20 360782
65 2021-03-21 238877
66 2021-03-22 448062
67 2021-03-23 325327
68 2021-03-24 274940
69 2021-03-25 348203
70 2021-03-28 341127
71 2021-03-29 239224
72 2021-03-31 210169
73 2021-04-03 401641
74 2021-04-04 307472
75 2021-04-07 287169
76 2021-04-08 261859
77 2021-04-10 262021
78 2021-04-11 280504
79 2021-04-13 267181
80 2021-04-14 271747
81 2021-04-15 280964
82 2021-04-16 374444
83 2021-04-17 308427
84 2021-04-18 297192
85 2021-04-19 245591
86 2021-04-20 257421
87 2021-04-21 215640
88 2021-04-22 351177
89 2021-04-24 283042
90 2021-04-25 274782
91 2021-04-26 293927
92 2021-04-27 208290
93 2021-04-28 350390
94 2021-04-30 247523
95 2021-05-01 235533
96 2021-05-04 340665
97 2021-05-05 309549
98 2021-05-06 273190
99 2021-05-08 351836
100 2021-05-10 262566
101 2021-05-11 306779
102 2021-05-13 294170
103 2021-05-15 320050
104 2021-05-16 282865
105 2021-05-18 290425
106 2021-05-19 350460
107 2021-05-20 344342
108 2021-05-21 342156
109 2021-05-22 323964
110 2021-05-23 267025
111 2021-05-24 277235
112 2021-05-25 296506
113 2021-05-26 237995
114 2021-05-27 263647
115 2021-05-28 270969
116 2021-05-30 225607
117 2021-06-01 335635
118 2021-06-02 283243
119 2021-06-03 286625
120 2021-06-04 277667
121 2021-06-05 295206
122 2021-06-07 294017
123 2021-06-08 293425
124 2021-06-09 235027
125 2021-06-10 198216
126 2021-06-13 310663
127 2021-06-17 331789
128 2021-06-20 265635
129 2021-06-21 224847
130 2021-06-22 263459
131 2021-06-24 288901
132 2021-06-25 285640
133 2021-06-26 377020
134 2021-06-27 264235
135 2021-06-29 255274
136 2021-06-30 334023
137 2021-07-03 239532
138 2021-07-04 332379
139 2021-07-06 304824
140 2021-07-07 233457
141 2021-07-08 270742
142 2021-07-09 275372
143 2021-07-10 290695
144 2021-07-11 354016
145 2021-07-14 308333
146 2021-07-16 330349
147 2021-07-17 279489
148 2021-07-18 278827
149 2021-07-19 246737
150 2021-07-21 350309
151 2021-07-22 253080
152 2021-07-23 318424
153 2021-07-24 283229
154 2021-07-27 387702
155 2021-07-28 359999
156 2021-07-29 262529
157 2021-07-31 329220
158 2021-08-02 245560
159 2021-08-03 371978
160 2021-08-04 316774
161 2021-08-05 269694
162 2021-08-06 321235
163 2021-08-07 337118
164 2021-08-08 336951
165 2021-08-09 332485
166 2021-08-10 340650
167 2021-08-13 292215
168 2021-08-15 228006
169 2021-08-16 261552
170 2021-08-18 289464
171 2021-08-19 331391
172 2021-08-20 335894
173 2021-08-22 345662
174 2021-08-25 250506
175 2021-08-26 324127
176 2021-08-29 300704
177 2021-08-30 216277
178 2021-09-01 390319
179 2021-09-02 302408
180 2021-09-04 236882
181 2021-09-05 328498
182 2021-09-06 373802
183 2021-09-07 257518
184 2021-09-08 315544
185 2021-09-09 331703
186 2021-09-10 295623
187 2021-09-11 262576
188 2021-09-12 330202
189 2021-09-16 334117
190 2021-09-20 318918
191 2021-09-21 333662
192 2021-09-22 303395
193 2021-09-23 343089
194 2021-09-25 246715
195 2021-09-26 365227
196 2021-09-30 279069
197 2021-10-01 274862
198 2021-10-03 351246
199 2021-10-04 224027
200 2021-10-06 315712
201 2021-10-08 207710
202 2021-10-10 313050
203 2021-10-11 320580
204 2021-10-12 317107
205 2021-10-13 255801
206 2021-10-14 336348
207 2021-10-15 326889
208 2021-10-17 388537
209 2021-10-18 358864
210 2021-10-20 334452
211 2021-10-21 315859
212 2021-10-22 277687
213 2021-10-23 275894
214 2021-10-24 348859
215 2021-10-25 345117
216 2021-10-27 297398
217 2021-10-28 279729
218 2021-10-30 255008
219 2021-11-01 284791
220 2021-11-02 366217
221 2021-11-03 273734
222 2021-11-04 305492
223 2021-11-05 317993
224 2021-11-06 271900
225 2021-11-07 305045
226 2021-11-09 253836
227 2021-11-10 350490
228 2021-11-12 219134
229 2021-11-13 294765
230 2021-11-14 343896
231 2021-11-15 289186
232 2021-11-16 180454
233 2021-11-17 311418
234 2021-11-18 282468
235 2021-11-19 287422
236 2021-11-20 257198
237 2021-11-21 263091
238 2021-11-22 300996
239 2021-11-23 264009
240 2021-11-24 329625
241 2021-11-25 324373
242 2021-11-26 280376
243 2021-11-27 288525
244 2021-11-28 363799
245 2021-11-29 282045
246 2021-12-01 341980
247 2021-12-02 312170
248 2021-12-03 261025
249 2021-12-04 328802
250 2021-12-07 302062
251 2021-12-09 262532
252 2021-12-10 332557
253 2021-12-11 299194
254 2021-12-12 303596
255 2021-12-13 370443
256 2021-12-15 315328
257 2021-12-16 388561
258 2021-12-18 370054
259 2021-12-19 382960
260 2021-12-23 225437
261 2021-12-24 292496
262 2021-12-25 325016
263 2021-12-26 362916
264 2021-12-27 257472
265 2021-12-28 311747
266 2021-12-30 279110
267 2021-12-31 196848
268 2022-01-02 298355
269 2022-01-04 368999
270 2022-01-06 300327
271 2022-01-08 265559
272 2022-01-09 314257
273 2022-01-12 252150
274 2022-01-13 351088
275 2022-01-14 274378
276 2022-01-15 271547
277 2022-01-18 262993
278 2022-01-19 372874
279 2022-01-20 318061
280 2022-01-21 383376
281 2022-01-22 311890
282 2022-01-23 333323
283 2022-01-25 332426
284 2022-01-26 275171
285 2022-01-27 303853
286 2022-01-29 289020
287 2022-01-30 327096
288 2022-01-31 281682
289 2022-02-02 275490
290 2022-02-03 383535
291 2022-02-04 296316
292 2022-02-05 351592
293 2022-02-06 325564
294 2022-02-07 307185
295 2022-02-08 314678
296 2022-02-10 259845
297 2022-02-11 338493
298 2022-02-12 226951
299 2022-02-13 320409
300 2022-02-15 278782
301 2022-02-16 260904
302 2022-02-17 327639
303 2022-02-18 281008
304 2022-02-20 312974
305 2022-02-21 283881
306 2022-02-22 282495
307 2022-02-23 333772
308 2022-02-24 245480
309 2022-02-25 287406
310 2022-02-26 331523
311 2022-02-28 319978
312 2022-03-01 283928
313 2022-03-04 282503
314 2022-03-05 355399
315 2022-03-06 236853
316 2022-03-08 247027
317 2022-03-11 310629
318 2022-03-12 354012
319 2022-03-13 307891
320 2022-03-14 350998
321 2022-03-15 234012
322 2022-03-16 283284
323 2022-03-17 332824
324 2022-03-21 219997
325 2022-03-22 359558
326 2022-03-23 247878
327 2022-03-25 269752
328 2022-03-26 373325
329 2022-03-31 366303
330 2022-04-03 222577
331 2022-04-05 336306
332 2022-04-06 232151
333 2022-04-07 329687
334 2022-04-13 319182
335 2022-04-14 287659
336 2022-04-16 272320
337 2022-04-17 321487
338 2022-04-18 340860
339 2022-04-19 236354
340 2022-04-20 320432
341 2022-04-21 400641
342 2022-04-22 267561
343 2022-04-24 299550
344 2022-04-26 362361
345 2022-04-29 292377
346 2022-04-30 289468
347 2022-05-01 248577
348 2022-05-02 241467
349 2022-05-04 266245
350 2022-05-05 333287
351 2022-05-06 303953
352 2022-05-09 309336
353 2022-05-11 381499
354 2022-05-12 348890
355 2022-05-13 406413
356 2022-05-14 360551
357 2022-05-16 380784
358 2022-05-17 291891
359 2022-05-21 290653
360 2022-05-22 295487
361 2022-05-23 253226
362 2022-05-27 329659
363 2022-05-28 414924
364 2022-05-29 302741
365 2022-05-30 264788
366 2022-05-31 217593
367 2022-06-02 261180
368 2022-06-03 295243
369 2022-06-07 282270
370 2022-06-08 332795
371 2022-06-14 400955
372 2022-06-15 366259
373 2022-06-16 319197
374 2022-06-17 300354
375 2022-06-20 267838
376 2022-06-22 299957
377 2022-06-23 299123
378 2022-06-24 258038
379 2022-06-25 267308
380 2022-06-27 230123
381 2022-06-28 285768
382 2022-06-29 278569
383 2022-06-30 304007
384 2022-07-01 352665
385 2022-07-02 277065
386 2022-07-05 281774
387 2022-07-06 345073
388 2022-07-08 317068
389 2022-07-09 374064
390 2022-07-11 243048
391 2022-07-12 381713
392 2022-07-13 308419
393 2022-07-15 266591
394 2022-07-16 343427
395 2022-07-17 267296
396 2022-07-18 301904
397 2022-07-19 268985
398 2022-07-20 269354
399 2022-07-21 242209
400 2022-07-23 304538
401 2022-07-24 321849
402 2022-07-25 320558
403 2022-07-27 283398
404 2022-07-28 308368
405 2022-07-29 321836
406 2022-07-30 253049
407 2022-07-31 402333
408 2022-08-03 357724
409 2022-08-04 226488
410 2022-08-05 247356
411 2022-08-06 234899
412 2022-08-08 317464
413 2022-08-10 262772
414 2022-08-11 313256
415 2022-08-13 275101
416 2022-08-14 344720
417 2022-08-15 313842
418 2022-08-16 325843
419 2022-08-17 324050
420 2022-08-18 390448
421 2022-08-19 354854
422 2022-08-20 340872
423 2022-08-22 275545
424 2022-08-25 277279
425 2022-08-27 349831
426 2022-08-28 231753
427 2022-08-29 266744
428 2022-09-02 266383
429 2022-09-04 269079
430 2022-09-05 274862
431 2022-09-07 308898
432 2022-09-08 274465
433 2022-09-09 258048
434 2022-09-12 339535
435 2022-09-14 352963
436 2022-09-15 354002
437 2022-09-16 258307
438 2022-09-21 305506
439 2022-09-23 294024
440 2022-09-25 327077
441 2022-09-26 288372
442 2022-09-28 280282
443 2022-10-01 280759
444 2022-10-02 264145
445 2022-10-05 342762
446 2022-10-06 299775
447 2022-10-09 279429
448 2022-10-10 335244
449 2022-10-11 306739
450 2022-10-12 335628
451 2022-10-13 275652
452 2022-10-14 309627
453 2022-10-20 214583
454 2022-10-22 321149
455 2022-10-23 290458
456 2022-10-24 295040
457 2022-10-25 252892
458 2022-10-26 378766
459 2022-10-28 296984
460 2022-10-29 246649
461 2022-10-31 247637
462 2022-11-02 391106
463 2022-11-03 234747
464 2022-11-04 248504
465 2022-11-05 285295
466 2022-11-06 294848
467 2022-11-07 372159
468 2022-11-08 281897
469 2022-11-09 287656
470 2022-11-10 173868
471 2022-11-11 341964
472 2022-11-12 358992
473 2022-11-13 335488
474 2022-11-15 298316
475 2022-11-16 316291
476 2022-11-17 338106
477 2022-11-18 280470
478 2022-11-20 380544
479 2022-11-21 272462
480 2022-11-22 263061
481 2022-11-23 341689
482 2022-11-24 393098
483 2022-11-25 281410
484 2022-11-26 315452
485 2022-11-29 393891
486 2022-12-03 261234
487 2022-12-04 206973
488 2022-12-05 296886
489 2022-12-06 325265
490 2022-12-07 272677
491 2022-12-09 276582
492 2022-12-10 297452
493 2022-12-11 267674
494 2022-12-13 310775
495 2022-12-14 325403
496 2022-12-17 366387
497 2022-12-19 271004
498 2022-12-20 274431
499 2022-12-21 321314
500 2022-12-22 255268
501 2022-12-23 348741
502 2022-12-24 158015
503 2022-12-25 296246
504 2022-12-26 303504
505 2022-12-28 262996
506 2022-12-29 317554
507 2022-12-30 274724
508 2022-12-31 319566

View file

@ -0,0 +1,33 @@
date_ordered,date_received,amount
2021-01-01,2021-01-20,16000000
2021-01-24,2021-02-11,14000000
2021-02-18,2021-03-10,9000000
2021-03-16,2021-03-31,11000000
2021-04-02,2021-04-19,16000000
2021-04-20,2021-05-10,17000000
2021-05-17,2021-06-10,15000000
2021-06-15,2021-07-07,13000000
2021-07-11,2021-08-01,10000000
2021-08-05,2021-08-18,17000000
2021-08-21,2021-09-08,12000000
2021-09-13,2021-09-29,13000000
2021-09-30,2021-10-11,11000000
2021-10-15,2021-11-04,15000000
2021-11-08,2021-11-28,14000000
2021-12-03,2021-12-21,13000000
2021-12-24,2022-01-10,10000000
2022-01-16,2022-01-27,14000000
2022-01-29,2022-02-18,17000000
2022-02-22,2022-03-13,11000000
2022-03-17,2022-04-02,12000000
2022-04-07,2022-05-03,13000000
2022-05-05,2022-05-30,14000000
2022-06-05,2022-06-17,17000000
2022-06-21,2022-07-13,16000000
2022-07-20,2022-08-13,12000000
2022-08-20,2022-09-09,15000000
2022-09-11,2022-09-22,14000000
2022-09-23,2022-10-17,13000000
2022-10-24,2022-11-14,20000000
2022-11-20,2022-12-14,16000000
2022-12-17,2022-12-23,12000000
1 date_ordered date_received amount
2 2021-01-01 2021-01-20 16000000
3 2021-01-24 2021-02-11 14000000
4 2021-02-18 2021-03-10 9000000
5 2021-03-16 2021-03-31 11000000
6 2021-04-02 2021-04-19 16000000
7 2021-04-20 2021-05-10 17000000
8 2021-05-17 2021-06-10 15000000
9 2021-06-15 2021-07-07 13000000
10 2021-07-11 2021-08-01 10000000
11 2021-08-05 2021-08-18 17000000
12 2021-08-21 2021-09-08 12000000
13 2021-09-13 2021-09-29 13000000
14 2021-09-30 2021-10-11 11000000
15 2021-10-15 2021-11-04 15000000
16 2021-11-08 2021-11-28 14000000
17 2021-12-03 2021-12-21 13000000
18 2021-12-24 2022-01-10 10000000
19 2022-01-16 2022-01-27 14000000
20 2022-01-29 2022-02-18 17000000
21 2022-02-22 2022-03-13 11000000
22 2022-03-17 2022-04-02 12000000
23 2022-04-07 2022-05-03 13000000
24 2022-05-05 2022-05-30 14000000
25 2022-06-05 2022-06-17 17000000
26 2022-06-21 2022-07-13 16000000
27 2022-07-20 2022-08-13 12000000
28 2022-08-20 2022-09-09 15000000
29 2022-09-11 2022-09-22 14000000
30 2022-09-23 2022-10-17 13000000
31 2022-10-24 2022-11-14 20000000
32 2022-11-20 2022-12-14 16000000
33 2022-12-17 2022-12-23 12000000