Skip to main content

Operator Login and Analysis

Operator login is not the same as signing in to Kanoa. It records an operator badging in to the line they're working, and badging out when they go on break, finish their shift, or move to another line. These sessions are the raw presence data OLE is built from.

Badging into a new line automatically ends the operator's session on their previous line, so nobody appears to be working two lines because they forgot to badge out. A lead who really does cover two lines can be allowed multiple sessions.

Badge in during the scheduled window

Login time only counts inside the operator's shift assignment from the previous lesson. If you badge in outside it, for example at 8:00 PM against a 7:00 AM to 3:00 PM Day Shift, none of the time is counted and the operator leaderboard comes back empty. Before you start, check on Ops → Scheduling → Crews that Day Crew is assigned to a shift that covers now.

Badge operators in and out
  1. Make sure Machine 1 is running in production, so there is runtime to measure against.
  2. Navigate to Ops → Operations → Operator Login.
  3. Badge johnDoe in to Machine 1.
  4. Badge marcoDiaz in to Machine 1 as well.
  5. After about 10 minutes, badge johnDoe out.
  6. Leave marcoDiaz logged in for another 20 minutes or so. Machine 1's simulator drops into downtime states at random, so some of that time the operator may be present while the machine isn't running.
  7. Review the sessions table and confirm the login and logout times.

Attendance compares these sessions with the crew's shift assignments from the previous lesson. Login time outside an operator's assigned window isn't counted, which is why the assignment should cover the time you're actually working. An operator with no assignment at all is measured against the whole report window instead.

You now have two people sharing one line for part of the time, one leaving early, and probably a stretch where someone was present but the machine wasn't running. Those are the situations OLE is designed to separate.

Read the Operators dashboard
  1. Navigate to Ops → Analysis → Operators.
  2. Select Machine 1, today, and the EvenSplit attribution mode.
  3. Read the KPI tiles for the whole line. Hover each one to see what it measures:
    • Attendance is well below 100%, because both operators were logged in for only part of their scheduled time, and marcoDiaz left early.
    • Labor Gap compares OLE and POLE for the line. The tiles divide POLE by the report window rather than by scheduled labor time, so if Machine 1 ran most of the day while your operators were logged in for only half an hour, Labor Gap goes negative and is labeled Understaffed (see the tip below).
  4. Review the Utilization breakdown bars. Asset time and labor time are shown separately, because a line's runtime and the people's presence are independent.
  5. In the Operator leaderboard, compare the two operators, ranked by POLE. For each operator, POLE is never higher than OLE. If Machine 1 went down while johnDoe was still logged in, their POLE is lower than their OLE: that difference is present-but-not-producing time.
  6. Change the asset to the Philadelphia site. The same numbers roll up across every line beneath it, without scheduling anything again.
  7. Click operator in the leaderboard to see their numbers broken down by each line they worked.
Attribution modeHow a shared line's output is creditedUse it for
EvenSplit (default)Shared between everyone logged in at the same timeNormal crew work
FullCreditEveryone logged in gets full creditTraining and shadowing
PrimaryOnlyOnly the first person logged in gets performance and quality creditOne operator running the line with helpers
Reading the Labor Gap

At the line or site level, Labor Gap can go negative. That means the line kept running near its normal rate even though fewer scheduled people showed up: understaffed, but holding for now. It's an early warning, and the dashboard labels it Understaffed rather than Present, not producing.

Check your work

Run this in the Script Console. It lists today's sessions on Machine 1, checks each operator's login time against their shift assignment, then prints the leaderboard. If the leaderboard is empty, the FAIL lines above it say why.

Show the check script
operatorLeaderboard.py
# Kanoa Ops Training - OLE / POLE from a script (Operator effectiveness, lesson 2)
#
# Shows today's operator sessions on Machine 1, checks each one against that
# operator's shift assignments, then prints the operator leaderboard. Login time
# outside an operator's assigned window isn't counted, so the leaderboard can come
# back empty even though the sessions are there. It only reads; it changes nothing.

PATH = 'CoreForge\\Philadelphia\\Machining\\Machine 1'
MODE = 'EvenSplit' # or 'FullCredit', 'PrimaryOnly'

results = []

def check(ok, what, fix=None):
results.append(bool(ok))
if ok:
print 'PASS %s' % what
else:
print 'FAIL %s' % what
if fix:
print ' -> %s' % fix

def note(what):
print 'NOTE %s' % what

def text(v):
return '' if v is None else unicode(v).strip()

def rows(data):
return system.kanoa.utilities.convertDatasetToJSON(data) if data is not None else []

def fmt(d):
return system.date.format(d, 'HH:mm') if d is not None else 'open'

def overlapSecs(s1, e1, s2, e2):
"""Seconds two periods overlap. An open end counts as now."""
e1, e2 = e1 or now, e2 or now
s = s1 if system.date.isAfter(s1, s2) else s2
e = e1 if system.date.isBefore(e1, e2) else e2
return max(0, system.date.secondsBetween(s, e))

now = system.date.now()
start = system.date.midnight(now)
assetId = system.kanoa.utilities.getFieldValue('assetId', system.kanoa.asset.getAssets({'assetPath': PATH}))

# Assignments on Machine 1 or anything above it (a crew scheduled at the site covers every line beneath it)
parts = PATH.split('\\')
coverIds = set()
for i in range(1, len(parts) + 1):
aid = system.kanoa.utilities.getFieldValue('assetId', system.kanoa.asset.getAssets({'assetPath': '\\'.join(parts[:i])}))
if aid is not None:
coverIds.add(aid)

print '=' * 70
print 'Operators on %s today (%s to %s)' % (parts[-1], fmt(start), fmt(now))
print '=' * 70

# 1. Sessions
print '\n-- Operator sessions'
sessions = rows(system.kanoa.operators.getOperators({'assetId': assetId, 'startDate': start, 'endDate': now}))
check(sessions, 'Operators badged in to %s today (%d sessions)' % (parts[-1], len(sessions)),
'In Ops > Operations > Operator Login, badge johnDoe and marcoDiaz in to Machine 1.')
for s in sessions:
print ' %-12s %s to %s' % (text(s.get('userName')), fmt(s.get('startDate')), fmt(s.get('endDate')))

# 2. Sessions vs shift assignments
assignments = [a for a in rows(system.kanoa.operators.getOperatorShiftAssignments({'startDate': start, 'endDate': now}))
if a.get('assetId') in coverIds]
if sessions:
print '\n-- Logins inside the scheduled window'
for userId in sorted(set(s.get('userId') for s in sessions)):
mine = [s for s in sessions if s.get('userId') == userId]
name = text(mine[0].get('userName'))
sched = [a for a in assignments if a.get('userId') == userId]
windows = ', '.join('%s to %s' % (fmt(a.get('startDate')), fmt(a.get('endDate'))) for a in sched)
if not sched:
note('%s has no shift assignment today, so they are measured against the whole report window. '
'Their attendance will look low; that is expected.' % name)
continue
counted = sum(overlapSecs(s.get('startDate'), s.get('endDate'), a.get('startDate'), a.get('endDate'))
for s in mine for a in sched)
check(counted > 0, '%s logged in during their scheduled window (%d min counted, scheduled %s)' % (name, counted // 60, windows),
'None of their login time falls inside %s, so none of it is counted and they drop off the leaderboard. '
'On Ops > Scheduling > Crews, assign Day Crew to the shift you are working now, then run this again.' % windows)

# 3. The leaderboard
print '\n-- Operator leaderboard (%s)' % MODE
board = rows(system.kanoa.operators.getOLEOperatorLeaderboard(assetId, start, now, MODE))
check(board, 'The leaderboard has %d operator(s)' % len(board),
'No counted login time yet. Fix any FAIL above; with no FAIL, badge an operator in and wait a few minutes.')
for r in board:
print ' ' + ', '.join('%s=%s' % (k, text(r[k])) for k in sorted(r.keys()))
if board and all(not r.get('ole') and not r.get('pole') for r in board):
note('OLE and POLE are 0. Check Machine 1 has a standard rate for the item it is running, and that it is in production.')

passed = results.count(True)
print '\n' + '=' * 70
print 'Result: %d of %d checks passed' % (passed, len(results))
print '=' * 70

If OLE and POLE come back as 0 while attendance looks right, check that Machine 1 has a standard rate for the item it's running. Performance, and so OLE and POLE, can't be calculated without one.