# Inventory Prediction Manager

**URL:** <https://forum.inductiveautomation.com/t/inventory-prediction-manager/93439>\
**Category:** Ignition\
**Tags:** perspective, scripting\
**Created:** [July 19, 2024, 7:45am UTC](https://forum.inductiveautomation.com/t/inventory-prediction-manager/93439 "2024-07-19T07:45:38Z")\
**Posts on this page:** 10\
**Page:** 1

<div class="post-metadata">

**Author:** ![Fadillah\_Ariadinata](https://avatars.discourse-cdn.com/v4/letter/f/4af34b/32.png) [@Fadillah\_Ariadinata](https://forum.inductiveautomation.com/u/Fadillah_Ariadinata)\
**Post date:** [July 19, 2024, 7:45am UTC](https://forum.inductiveautomation.com/t/inventory-prediction-manager/93439/1 "2024-07-19T07:45:38Z")

</div>

I just tried importing the Inventory Prediction Manager project. I've followed the steps from the README file, but I'm currently confused by the errors and how to configure everything to be able to use the tags I have. Can someone please help?[https://inductiveautomation.com/exchange/2476/installation](https://inductiveautomation.com/exchange/2476/installation)

 ![Screenshot 2024-07-19 133223](https://us1.discourse-cdn.com/inductiveautomation/original/3X/e/3/e3bf38d7fb30f877dfeafc567c09250de054e722.png)

---

<div class="post-metadata">

**Author:** ![pascal.fragnoud](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pascal.fragnoud/32/24728_2.png) [@pascal.fragnoud](https://forum.inductiveautomation.com/u/pascal.fragnoud)\
**Post date:** [July 19, 2024, 8:25am UTC](https://forum.inductiveautomation.com/t/inventory-prediction-manager/93439/2 "2024-07-19T08:25:23Z")

</div>

We'll need more details to help you.

Show us :

- what the error is (put your mouse cursor over the red message on the bottom of the popup and wait for the tooltip to show),
- what that function called looks like (the `...createFullDataSet` one),
- and what parameters you're passing to your binding.

---

<div class="post-metadata">

**Author:** ![Fadillah\_Ariadinata](https://avatars.discourse-cdn.com/v4/letter/f/4af34b/32.png) [@Fadillah\_Ariadinata](https://forum.inductiveautomation.com/u/Fadillah_Ariadinata)\
**Post date:** [July 19, 2024, 8:36am UTC](https://forum.inductiveautomation.com/t/inventory-prediction-manager/93439/3 "2024-07-19T08:36:53Z")

</div>

![Screenshot 2024-07-19 153213](https://us1.discourse-cdn.com/inductiveautomation/original/3X/5/2/5286e7e67fd120ddf7291b80a5c65cb50e09e4b7.png)  
 ![Untitled](https://us1.discourse-cdn.com/inductiveautomation/original/3X/0/9/092854eb4df6aa37c48666f723048ae0794aeb97.png)  
Basically, I've just imported this "Resource Exchange" resource due to company needs. I've only configured the Gateway web page according to the installation steps provided by the creator.

---

<div class="post-metadata">

**Author:** ![pascal.fragnoud](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pascal.fragnoud/32/24728_2.png) [@pascal.fragnoud](https://forum.inductiveautomation.com/u/pascal.fragnoud)\
**Post date:** [July 19, 2024, 8:43am UTC](https://forum.inductiveautomation.com/t/inventory-prediction-manager/93439/4 "2024-07-19T08:43:31Z")

</div>

You have a division by 0 on line 148.

Take a look at this:

> [@Wiki - how to post code on this forum](https://forum.inductiveautomation.com/t/wiki-how-to-post-code-on-this-forum/77831):
>
> This thread is intended as a short help file for all those struggling to post formatted code on the forum. Code block formatting Posting formatted code on the forum (and most others) is as simple as selecting the code and pressing the \</\> button. It works the same as applying Bold or Italic styles to your document. It preserves indentation and applies syntax highlighting. [How to post code] Note that all the \</\> button does is add three backticks ``` on the line before the code block and anot…

---

<div class="post-metadata">

**Author:** ![Fadillah\_Ariadinata](https://avatars.discourse-cdn.com/v4/letter/f/4af34b/32.png) [@Fadillah\_Ariadinata](https://forum.inductiveautomation.com/u/Fadillah_Ariadinata)\
**Post date:** [July 19, 2024, 8:46am UTC](https://forum.inductiveautomation.com/t/inventory-prediction-manager/93439/5 "2024-07-19T08:46:41Z")

</div>

```python
def randomInventoryDecrementation(tagPath):#for simulation
	#called from Simulator Trigger tag in UDT
	value = system.tag.readBlocking([tagPath])[0].value
	hour = system.date.getHour24(system.date.now())
	if hour<12:#prevent "running out" every morning
		hour = 24-hour
	randomNum = 0.500/float(hour)

	newValue = value-randomNum

	if newValue<0:
		newValue = 100.000
	
	res = system.tag.writeBlocking([tagPath],[newValue])
	return

def predictionDataset(tagPath = '[Inventory Management]Exchange/Inventory Management/Tank 1'):
	
	fullDS = createFullDataset(tagPath)

	#update tags	
	notificationTime = system.tag.readBlocking([tagPath+'/Order Lead Time'])[0].value
	runoutDate = fullDS[-1]['time']
	notificationDate = system.date.addDays(runoutDate, -notificationTime)

	tagPaths = [
		tagPath+'/Predicted Runout Date',
		tagPath+'/Notification Date'
	]
	values = [
		runoutDate,
		notificationDate
	]
	#send notification
	notificationSent = system.tag.readBlocking([tagPath+'/Notification Sent'])[0].value
	if not notificationSent and system.date.now()>notificationTime:
		sendNotification(tagPath)
		tagPaths.append(tagPath+'/Notification Sent')
		values.append(True)
	
	writeQuality = system.tag.writeBlocking(tagPaths,values)
	print writeQuality
	return fullDS
	
def sendNotification(tagPath):
	tagPaths = [
			tagPath+'/Asset Name',
			tagPath+'/Product Name',
			tagPath+'/Predicted Runout Date',
			tagPath+'/Order Lead Time',
			tagPath+'/Notification Roster'
		]
	values = system.tag.readBlocking(tagPaths)
	body = 'The predicted runout date for ' +str(values[0].value)+ '('+str(values[1].value)+') is '+ str(values[2].value)+'. With a lead time of '+str(values[3].value)+' days it is recommended that you re-order soon.'
	recipients = values[4].value
	smtp_server = "mySmtpServer"#configured in gateway
	
	#uncomment to send notification
#	system.net.sendEmail(smtpProfile=smtp_server, fromAddr="myemail@mycompany.com", subject="Inventory Alert", body=body, html=1, to=recipients)
	return

def createFullDataset(tagPath = '[Inventory Management]Exchange/Inventory Management/Tank 1'):
	#query tag history
	typicalPeriod = system.tag.readBlocking([tagPath+'/Typical Period'])[0].value
	tagPaths = [tagPath+'/Raw Value']
	columnNames = ['value']
	endDate = system.date.now()
	startDate = system.date.addDays(endDate, int(-typicalPeriod))
	aggregationMode = 'LastValue'
	returnSize = -1
	dataset = system.tag.queryTagHistory(tagPaths, startDate, endDate, columnNames = columnNames, aggregationMode = aggregationMode, returnSize = returnSize)
	headers = system.dataset.getColumnHeaders(dataset)
	pyds = system.dataset.toPyDataSet(dataset)
	data = []
	for row in pyds:
		item = {
			'time': row['t_stamp'],
			'value': row['value']
		}

		data.append(item)
		
	#find, if any, material refill points and remove previous data
	threshold = system.tag.readBlocking([tagPath+'/Analysis Buffer Value'])[0].value
	
	stopDict = data[0]
	startIndex = 0
	for startIndex in xrange(len(data)-1,0,-1):
		if data[startIndex]['value']>threshold:
			stopDict = data[startIndex-1]
			break
#	print 'stopDict: ',stopDict
#	print
	
	#initialize the return DS to the data before the analysis begins
	fullDS = data[0:startIndex+1]
	for row in fullDS:#prep full ds for calculated value and set to none
		row['calculated'] = None
	#get calculatedDS
	
	#get the data that for the analysis to be performed on, data since last material fill
	calculationDS = data[startIndex+1:len(data)]
	calculationData = []
	for item in calculationDS:
		calculationData.append([item['time'],item['value']])
	
	#get calculated dataset
	datasetToCalculate = system.dataset.toDataSet(headers, calculationData)
	calculatedDataset = calculateLinearRegressionOnHistoryDataset(datasetToCalculate)
	calculatedpyds = system.dataset.toPyDataSet(calculatedDataset[0])
	
	#append calcuated dataset to fullDS that was iniitialized above
	for row in calculatedpyds:
		fullDS.append({
			'time': system.date.fromMillis(row['t_stamp']),
			'value': row['value'],
			'calculated': row['calculated']
		})
	
	#add y inercept data point for predition line
	fullDS.append({
		'time': system.date.fromMillis(int(calculatedDataset[2])),#y intercept
		'value': None,
		'calculated': 0
	})
	
	finalHeaders = ['t_stamp','value','calculated']
	finalData = []
	#view fully combined dataset
	for row in fullDS:
		print row
		finalData.append([row['time'],row['value'],row['calculated']])
		
	return fullDS
	
def linreg(X, Y):
    if len(X) != len(Y): raise ValueError, 'unequal length'
    N = len(X)
    Sx = Sy = Sxx = Syy = Sxy = 0.0
    for x, y in map(None, X, Y):
        Sx = Sx + x
        Sy = Sy + y
        Sxx = Sxx + x*x
        Syy = Syy + y*y
        Sxy = Sxy + x*y
    det = Sxx * N - Sx * Sx
    a, b = (Sxy * N - Sy * Sx)/det, (Sxx * Sy - Sx * Sxy)/det
    meanerror = residual = float(0.0)
    for x, y in map(None, X, Y):
        meanerror = meanerror + (y - Sy/N)**2
        residual = residual + (y - a * x - b)**2
    RR = 1 - residual/meanerror
    ss = residual / (N-2)
    Var_a, Var_b = ss * N / det, ss * Sxx / det
    return a, b, RR	#y intercept, slope, risk ratio

#dataset is expected to be a single tag historical query
#Use this function to perform linear regression on any historical dataset!
def calculateLinearRegressionOnHistoryDataset(dataset):
	x = []
	y=[]
	for i in range(dataset.getRowCount()):
		t_stamp = system.date.toMillis(dataset.getValueAt(i,0))
		value = dataset.getValueAt(i,1)
# print t_stamp,value
		y.append(t_stamp)#t_stamp, convert to millis
		x.append(value)#tag history value
	
	results = linreg(x,y)
	slope = results[0]
	yIntercept = results[1]
	riskRatio = results[2]
	print
#	print results
	print 'y intercept: ', yIntercept, system.date.fromMillis(int(yIntercept))
	print 'slope: ',slope
	print 'risk ratio: ',riskRatio
	print 
	data = []
	for i in range(dataset.getRowCount()):
		t_stamp = system.date.toMillis(dataset.getValueAt(i,0))
		value = dataset.getValueAt(i,1)
# calculated = slope*t_stamp+yIntercept
		calculated = (t_stamp-yIntercept)/slope
		
# print t_stamp,value,calculated
		data.append([t_stamp,value,calculated])
	
	headers = system.dataset.getColumnHeaders(dataset)
	headers.append('calculated')
	
	ret = system.dataset.toDataSet(headers, data)
	
	return ret,slope,yIntercept

```

are you succes with this module? i mean maybe will be some vid i can watch about this module

---

<div class="post-metadata">

**Author:** ![pascal.fragnoud](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pascal.fragnoud/32/24728_2.png) [@pascal.fragnoud](https://forum.inductiveautomation.com/u/pascal.fragnoud)\
**Post date:** [July 19, 2024, 9:04am UTC](https://forum.inductiveautomation.com/t/inventory-prediction-manager/93439/6 "2024-07-19T09:04:41Z")

</div>

I don't use it.

I checked the code a little bit, and frankly I don't have time to debug it.

Here's a first step:  
check the length of the data structures the `linreg` function uses.  
add some logging between lines 139 and 140 to log/print `N`. This seems to me like the most likely culprit.

---

<div class="post-metadata">

**Author:** ![Fadillah\_Ariadinata](https://avatars.discourse-cdn.com/v4/letter/f/4af34b/32.png) [@Fadillah\_Ariadinata](https://forum.inductiveautomation.com/u/Fadillah_Ariadinata)\
**Post date:** [July 19, 2024, 9:13am UTC](https://forum.inductiveautomation.com/t/inventory-prediction-manager/93439/7 "2024-07-19T09:13:08Z")

</div>

```python
def linreg(X, Y):
    if len(X) != len(Y):
        raise ValueError('unequal length')

    N = len(X)
    logging.debug(f'Length of X and Y (N): {N}')
    
    Sx = Sy = Sxx = Syy = Sxy = 0.0
    
    for x, y in zip(X, Y):
        Sx += x
        Sy += y
        Sxx += x * x
        Syy += y * y
        Sxy += x * y
    
    det = Sxx * N - Sx * Sx
    a = (Sxy * N - Sy * Sx) / det
    b = (Sxx * Sy - Sx * Sxy) / det
    
    meanerror = residual = 0.0
    
    for x, y in zip(X, Y):
        meanerror += (y - Sy / N) ** 2
        residual += (y - a * x - b) ** 2
    
    RR = 1 - residual / meanerror
    ss = residual / (N - 2)
    Var_a = ss * N / det
    Var_b = ss * Sxx / det
    
    return a, b, RR 

```

this right?

---

<div class="post-metadata">

**Author:** ![pascal.fragnoud](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pascal.fragnoud/32/24728_2.png) [@pascal.fragnoud](https://forum.inductiveautomation.com/u/pascal.fragnoud)\
**Post date:** [July 19, 2024, 9:20am UTC](https://forum.inductiveautomation.com/t/inventory-prediction-manager/93439/8 "2024-07-19T09:20:29Z")

</div>

f-strings don't exist in python2, you'll need to either

- use one of python2 formatting syntaxes
- use the built-in formatted logging functions (see [system.util.getLogger | Ignition User Manual](https://www.docs.inductiveautomation.com/docs/8.1/appendix/scripting-functions/system-util/system-util-getLogger))

Note also that to use `logging.debug()`, you'll first need to declare and define `logging`.

Again, use `system.util.getLogger` as explained in the page linked above.

---

<div class="post-metadata">

**Author:** ![pascal.fragnoud](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pascal.fragnoud/32/24728_2.png) [@pascal.fragnoud](https://forum.inductiveautomation.com/u/pascal.fragnoud)\
**Post date:** [July 19, 2024, 9:37am UTC](https://forum.inductiveautomation.com/t/inventory-prediction-manager/93439/10 "2024-07-19T09:37:04Z")

</div>

This was not supposed to fix anything, only give you clues about what might be going wrong.

---

<div class="post-metadata">

**Author:** ![Herbie](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/herbie/32/65288_2.png) [@Herbie](https://forum.inductiveautomation.com/u/Herbie)\
**Post date:** [July 19, 2024, 10:39am UTC](https://forum.inductiveautomation.com/t/inventory-prediction-manager/93439/11 "2024-07-19T10:39:57Z")

</div>

> [@Fadillah\_Ariadinata](#):
>
> ![Untitled](https://us1.discourse-cdn.com/inductiveautomation/original/3X/0/9/092854eb4df6aa37c48666f723048ae0794aeb97.png)

If you read the error message, it is giving you the clues you need to start debugging. What I read is on line 148 in the function linreg(), you have a divide by zero error. I see several places this could be an issue, but there is clearly nothing checking for having a 0 in the denominator of several lines of code. I also cannot pin point the issue as we don't have line numbers, so you should be able to go right to it. So the focus should be on debugging those, or adding code to handle these situations. Look at the "###### Check this line" in the code below.

You can see a situation where `det=0` or `meanerror=0` or `N=2` that would all throw this error.

```python
def linreg(X, Y):
    if len(X) != len(Y): raise ValueError, 'unequal length'
    N = len(X)
    Sx = Sy = Sxx = Syy = Sxy = 0.0
    for x, y in map(None, X, Y):
        Sx = Sx + x
        Sy = Sy + y
        Sxx = Sxx + x*x
        Syy = Syy + y*y
        Sxy = Sxy + x*y
    det = Sxx * N - Sx * Sx
    a, b = (Sxy * N - Sy * Sx)/det, (Sxx * Sy - Sx * Sxy)/det ###### Check this line
    meanerror = residual = float(0.0)
    for x, y in map(None, X, Y):
        meanerror = meanerror + (y - Sy/N)**2
        residual = residual + (y - a * x - b)**2
    RR = 1 - residual/meanerror ###### Check this line
    ss = residual / (N-2) ###### Check this line
    Var_a, Var_b = ss * N / det, ss * Sxx / det ###### Check this line
    return a, b, RR	#y intercept, slope, risk ratio
    

```

While you are at it, I also see a possible error coming if you run this at a certain time (midnight) as well that needs handled appriopriately.

```python
def randomInventoryDecrementation(tagPath):#for simulation
	#called from Simulator Trigger tag in UDT
	value = system.tag.readBlocking([tagPath])[0].value
	hour = system.date.getHour24(system.date.now())
	if hour<12:#prevent "running out" every morning
		hour = 24-hour
	randomNum = 0.500/float(hour) ###### Check this line

	newValue = value-randomNum

	if newValue<0:
		newValue = 100.000
	
	res = system.tag.writeBlocking([tagPath],[newValue])
	return

```

Keep in mind, reusing of code is fine, but the original developer made some assumptions about it's use (when it would be run, like not at midnight, or that certain values would always be present) that you are running into at this point. This would be a good improvement on the code if the original poster could update, but this is what we as developers get to deal with. You can try to detect the 0 before you do the division, or use a try/except here to handle it.
