Inventory Prediction Manager

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

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.



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.

You have a division by 0 on line 148.

Take a look at this:

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

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.

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?

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

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.

2 Likes

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

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.

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.

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.

2 Likes