Release 0.0.1 Deep Dive

by Kevin Fasusi |

Deep dives will be a recurring post, for every release with a substantial feature update. The purpose of these deep dives, is to assist developers seeking to contribute. The hope is that by explaining the library internals in detail, warts and all, a contributor can better focus their contribution.

The core api can be found in the namespaces:

  • demand
  • production
  • inventory
  • simulation
  • distribution

The core codebase is non-public. The auxiliary functions and API can be found in the modules like model_inventory which contains the analyse_orders_from_file_col, analyse_orders etc. Please see the official documentation for further information.

├── __init__.py
├── __init__.pyc
├── data.csv
├── data_cleansing.py
├── data_col.csv
├── demand
│   ├── __init__.py
│   ├── __init__.pyc
│   ├── __pycache__
│   │   ├── __init__.cpython-35.pyc
│   │   ├── abc_xyz.cpython-35.pyc
│   │   ├── analyse_uncertain_demand.cpython-35.pyc
│   │   ├── economic_order_quantity.cpython-35.pyc
│   │   ├── forecast_demand.cpython-35.pyc
│   │   ├── summarise_analysis.cpython-35.pyc
│   │   └── summarise_demand.cpython-35.pyc
│   ├── abc_xyz.py
│   ├── abc_xyz.pyc
│   ├── analyse_uncertain_demand.py
│   ├── economic_order_quantity.py
│   ├── economic_order_quantity.pyc
│   ├── eoq.c
│   ├── eoq.cp35-win_amd64.pyd
│   ├── eoq.cpython-35m-darwin.so
│   ├── eoq.cpython-35m-x86_64-linux-gnu.so
│   ├── eoq.pyx
│   ├── forecast_demand.py
│   ├── summarise_analysis.py
│   └── summarise_demand.py
├── model_demand.py
├── model_distribution.py
├── model_inventory.py
├── model_production.py
├── model_warehouse.py
├── orders_analysis.txt

Analyse Orders

Inventory analysis requires a .csv file, an example of the format is here. In this example the .csv file contains the monthly demand for 32 stock keeping units (SKUs), over a 12 month period. The function analyse_orders_abcxyz_from_file in the model_inventory module is used to analyse the inventory profile.

from supplychainpy.model_inventory import analyse_orders_abcxyz_from_file

orders_transactions = analyse_orders_abcxyz_from_file(file_path="data.csv",
z_value=Decimal(1.28),
reorder_cost=Decimal(5000),
file_type="csv")

The function returns an ABCXYZ object which also contains an orders attribute which is a list of UncertainDemand objects. These objects contain the summary for each individual sku, while the ABCXYZ object contains the summary analysis for the whole inventory profile. The attributes for ABCXYZ can be seen in the Pycharm debugger.

orders analysis variables

Retrieving the summary from this method is not consistent with the rest of the library. There are currently two ways to retrieve the summary analysis for each SKU (there can only be one :-p). The first involves using the objects public instance variables:

from supplychainpy.model_inventory import analyse_orders_abcxyz_from_file

abc = analyse_orders_abcxyz_from_file(file_path="data.csv",
z_value=Decimal(1.28),
reorder_cost=Decimal(5000),
file_type="csv")

for sku in abc.orders:
print("Sku: {} "
"Economic Order Quantity: {:.0f}"
" Sku Revenue: {:.0f} "
"ABCXYZ Classification: {}".format(sku.sku_id,
sku.economic_order_qty,
sku.revenue,
sku.abcxyz_classification))

resulting in:

    Sku: KR202-209 Economic Order Quantity: 1311 Sku Revenue: 6942800 ABCXYZ Classification: CZ
    Sku: KR202-210 Economic Order Quantity: 1405 Sku Revenue: 7900000 ABCXYZ Classification: CY
    Sku: KR202-211 Economic Order Quantity: 1224 Sku Revenue: 6900000 ABCXYZ Classification: CZ
    Sku: KR202-212 Economic Order Quantity: 1317 Sku Revenue: 10000000 ABCXYZ Classification: BY
    Sku: KR202-213 Economic Order Quantity: 981 Sku Revenue: 6700000 ABCXYZ Classification: CY
    Sku: KR202-214 Economic Order Quantity: 1170 Sku Revenue: 10000000 ABCXYZ Classification: BY

A glaring oversight is apparent as of this post. The revenue value is generated using the unit_cost and not a value representive of a standard or retail price. This will be changed in release-0.0.4

The second way is by iterating over the orders list and calling the UncertainDemand.orders_summary method.

from supplychainpy.model_inventory import analyse_orders_abcxyz_from_file

abc = analyse_orders_abcxyz_from_file(file_path="data.csv",
z_value=Decimal(1.28),
reorder_cost=Decimal(5000),
file_type="csv")
for sku in abc.orders:
print(sku.orders_summary())

resulting in:

{'ABC_XYZ_Classification': 'AX', 'reorder_quantity': '258', 'revenue': '2090910.44',
'average_order': '539', 'reorder_level': '813', 'economic_order_quantity': '277', 'sku': 'RR381-33',
'demand_variability': '0.052', 'economic_order_variable_cost': '29557.61',
'standard_deviation': '28', 'safety_stock': '51'}              

In release-0.0.4 this will be resolved, as holding on to the object will incur a cost in performance at scale.

The formulas for the calculations are here. The doc string for the class and functions are here. Most of the methods in the UncertainDemand class are non-public, as signified by the leading underscore. The constructor for UncertainDemand does all the work, along with the EconomicOrderQuantity and ABCXYZ class. All three are instantiated and called from within the analyse_orders_abcxyz_from_file function.

def analyse_orders_abcxyz_from_file(file_path: str,
z_value: Decimal,
reorder_cost: Decimal,
file_type: str = FileFormats.text.name,
period: str = "month",
length: int = 12) -> AbcXyz:

analysed_orders_collection = []
item_list = {}

if _check_extension(file_path=file_path, file_type=file_type):
if file_type == FileFormats.text.name:
f = open(file_path, 'r')
item_list = (data_cleansing.clean_orders_data_row(f))
elif file_type == FileFormats.csv.name:
f = open(file_path)
item_list = data_cleansing.clean_orders_data_row_csv(f, length=length)
else:
raise Exception("Incorrect file type specified."
"Please specify 'csv' or 'text' for the file_type parameter.")

for sku in item_list:
orders = {}

sku_id, unit_cost, lead_time = sku.get("sku id"), sku.get("unit cost"), sku.get("lead time")

orders['demand'] = sku.get("demand")
total_orders = 0
for order in orders['demand']:
total_orders += int(order)

analysed_orders = analyse_uncertain_demand.UncertainDemand(orders=orders,
sku=sku_id,
lead_time=lead_time,
unit_cost=unit_cost,
reorder_cost=Decimal(reorder_cost),
z_value=Decimal(z_value))

average_orders = analysed_orders.average_orders

reorder_quantity = analysed_orders.fixed_order_quantity

eoq = economic_order_quantity.EconomicOrderQuantity(total_orders=float(total_orders),
reorder_quantity=float(reorder_quantity),
holding_cost=float(0.25),
reorder_cost=float(reorder_cost),
average_orders=average_orders,
unit_cost=float(unit_cost))

analysed_orders.economic_order_qty = eoq.economic_order_quantity
analysed_orders.economic_order_variable_cost = eoq.minimum_variable_cost

analysed_orders_collection.append(analysed_orders)

del analysed_orders
del eoq
del sku

abc = AbcXyz(analysed_orders_collection)
abc.percentage_revenue()
abc.cumulative_percentage_revenue()
abc.abc_classification()
abc.xyz_classification()
a = summarise_demand.AnalyseOrdersSummary(abc.orders)
abc.abcxyz_summary = a.classification_summary()

f.close()
return abc

The naming conventions used for the analyse methods are cumbersome analyse_orders_abcxyz_from_file, for example, is not beautiful (as is mandated by Pythonistas across the globe). In release 0.0.4 these will be changing to describe_inventory I like the describe convention, it is also found in Julia where the result is a gamut of descriptive statistics.

Read More: