thingies
This commit is contained in:
parent
a67287eeb7
commit
ee8dd41f85
129 changed files with 91712 additions and 0 deletions
BIN
pds/cases/case_2/Case 2 description.pdf
Normal file
BIN
pds/cases/case_2/Case 2 description.pdf
Normal file
Binary file not shown.
80
pds/cases/case_2/Case 2.md
Normal file
80
pds/cases/case_2/Case 2.md
Normal 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?
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
686
pds/cases/case_2/Diemen_case_2_my_notebook.ipynb
Normal file
686
pds/cases/case_2/Diemen_case_2_my_notebook.ipynb
Normal 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
BIN
pds/cases/case_2/case_2.zip
Normal file
Binary file not shown.
689
pds/cases/case_2/case_2_student_notebook.ipynb
Normal file
689
pds/cases/case_2/case_2_student_notebook.ipynb
Normal 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": []
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
pds/cases/case_2/grading.xlsx
Normal file
BIN
pds/cases/case_2/grading.xlsx
Normal file
Binary file not shown.
BIN
pds/cases/case_2/grading_team_1.xlsx
Normal file
BIN
pds/cases/case_2/grading_team_1.xlsx
Normal file
Binary file not shown.
BIN
pds/cases/case_2/grading_team_2.xlsx
Normal file
BIN
pds/cases/case_2/grading_team_2.xlsx
Normal file
Binary file not shown.
BIN
pds/cases/case_2/grading_team_3.xlsx
Normal file
BIN
pds/cases/case_2/grading_team_3.xlsx
Normal file
Binary file not shown.
BIN
pds/cases/case_2/grading_team_4.xlsx
Normal file
BIN
pds/cases/case_2/grading_team_4.xlsx
Normal file
Binary file not shown.
BIN
pds/cases/case_2/grading_team_4_comments_reviewd.xlsx
Normal file
BIN
pds/cases/case_2/grading_team_4_comments_reviewd.xlsx
Normal file
Binary file not shown.
BIN
pds/cases/case_2/grading_team_5.xlsx
Normal file
BIN
pds/cases/case_2/grading_team_5.xlsx
Normal file
Binary file not shown.
508
pds/cases/case_2/served_orders.csv
Normal file
508
pds/cases/case_2/served_orders.csv
Normal 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
|
||||
|
33
pds/cases/case_2/sourcing_events.csv
Normal file
33
pds/cases/case_2/sourcing_events.csv
Normal 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
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue