Add UDC Performance Dashboard and Tool Health features

- Add displayudc.asp with Dashboard tab containing:
  - Production Trend chart (daily parts)
  - OOT Rate Trend chart (daily OOT %)
  - Machine Utilization chart (top 10 by runtime hours)
  - Top Operators chart (top 10 by parts produced)
- Add tabs for drill-down: Live Activity, Operators, Machines, Parts,
  Quality/OOT, Timing, Activity Log, Tool Health, Uptime, IT Diagnostics

- Add Tool Health section to displaymachine.asp UDC tab:
  - Summary cards (tools monitored, measurements, OOT count)
  - Tool status table with health indicators
  - Recent OOT events display

- Add UDC API endpoints in api.asp:
  - getUDCPartRuns, getUDCOperatorStats, getUDCMachineStats, getUDCManualTiming

- Add sql/udctables.sql schema for UDC data storage

- Update docs/API.md with UDC endpoint documentation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
cproudlock
2025-12-16 07:54:13 -05:00
parent 95072d96fc
commit 91fe5a6c66
6 changed files with 3298 additions and 105 deletions

360
api.asp
View File

@@ -49,10 +49,14 @@ Select Case action
GetRecordedIP() GetRecordedIP()
Case "updateMachinePositions" Case "updateMachinePositions"
UpdateMachinePositions() UpdateMachinePositions()
Case "logDNCEvent" Case "getUDCPartRuns"
LogDNCEvent() GetUDCPartRuns()
Case "getDNCStats" Case "getUDCOperatorStats"
GetDNCStats() GetUDCOperatorStats()
Case "getUDCMachineStats"
GetUDCMachineStats()
Case "getUDCManualTiming"
GetUDCManualTiming()
Case Else Case Else
SendError "Invalid action: " & action SendError "Invalid action: " & action
End Select End Select
@@ -2546,99 +2550,47 @@ Sub UpdateMachinePositions()
End Sub End Sub
' ============================================================================ ' ============================================================================
' eDNC SPECIAL CHARACTER FIX - LOGGING ' UDC LOG DATA ENDPOINTS
' ============================================================================ ' ============================================================================
Sub LogDNCEvent() Sub GetUDCPartRuns()
On Error Resume Next On Error Resume Next
' Get parameters ' Get optional filters
Dim hostname, filename, eventAction, bytesRemoved, version, message Dim machinenumber, startdate, enddate, badgenumber
hostname = Trim(Request.Form("hostname") & "") machinenumber = Trim(Request.QueryString("machinenumber") & "")
filename = Trim(Request.Form("filename") & "") startdate = Trim(Request.QueryString("startdate") & "")
eventAction = Trim(Request.Form("eventType") & "") enddate = Trim(Request.QueryString("enddate") & "")
bytesRemoved = Request.Form("bytesRemoved") badgenumber = Trim(Request.QueryString("badgenumber") & "")
version = Trim(Request.Form("version") & "")
message = Trim(Request.Form("message") & "")
' Validate required fields ' Build query
If hostname = "" Or eventAction = "" Then Dim sql, conditions
SendError "hostname and eventType are required" sql = "SELECT p.partrunid, s.machinenumber, p.partnumber, p.opernumber, p.serialnumber, " & _
Exit Sub "p.programname, p.jobnumber, p.badgenumber, p.programstart, p.programend, " & _
"p.cycletime, p.changeover, p.measurementcount, p.manualcount, p.probecount, p.ootcount " & _
"FROM udcparts p " & _
"JOIN udcsessions s ON p.sessionid = s.sessionid "
conditions = ""
If machinenumber <> "" Then
conditions = conditions & " AND s.machinenumber = '" & Replace(machinenumber, "'", "''") & "'"
End If
If startdate <> "" Then
conditions = conditions & " AND p.programstart >= '" & Replace(startdate, "'", "''") & "'"
End If
If enddate <> "" Then
conditions = conditions & " AND p.programstart <= '" & Replace(enddate, "'", "''") & " 23:59:59'"
End If
If badgenumber <> "" Then
conditions = conditions & " AND p.badgenumber = '" & Replace(badgenumber, "'", "''") & "'"
End If End If
' Default bytesRemoved to 0 if not numeric If conditions <> "" Then
If Not IsNumeric(bytesRemoved) Or bytesRemoved = "" Then bytesRemoved = 0 sql = sql & " WHERE 1=1 " & conditions
' Get machineid from hostname (required for logging)
Dim safeHostname, machineid, rsLookup
safeHostname = Replace(hostname, "'", "''")
Set rsLookup = objConn.Execute("SELECT machineid FROM machines WHERE UPPER(hostname) = UPPER('" & safeHostname & "') AND pctypeid IS NOT NULL LIMIT 1")
If rsLookup.EOF Then
rsLookup.Close
Set rsLookup = Nothing
SendError "Unknown hostname: " & hostname
Exit Sub
End If End If
sql = sql & " ORDER BY p.programstart DESC LIMIT 1000"
machineid = CLng(rsLookup("machineid")) Dim rs
rsLookup.Close
Set rsLookup = Nothing
' Sanitize remaining inputs
Dim safeFilename, safeAction, safeVersion, safeMessage
safeFilename = Replace(filename, "'", "''")
safeAction = Replace(eventAction, "'", "''")
safeVersion = Replace(version, "'", "''")
safeMessage = Replace(message, "'", "''")
' Insert log entry using machineid
Dim insertSQL
insertSQL = "INSERT INTO ednclogs (machineid, filename, action, bytes_removed, version, message) " & _
"VALUES (" & machineid & ", '" & safeFilename & "', '" & safeAction & "', " & _
CLng(bytesRemoved) & ", '" & safeVersion & "', '" & safeMessage & "')"
objConn.Execute insertSQL
If Err.Number <> 0 Then
SendError "Failed to log event: " & Err.Description
Exit Sub
End If
' Track in installedapps (appid 79 = eDNC Special Character Fix)
Dim edncAppId, rsApp
edncAppId = 79
' Check if already in installedapps
Set rsApp = objConn.Execute("SELECT installedappid FROM installedapps WHERE machineid = " & machineid & " AND appid = " & edncAppId)
If rsApp.EOF Then
' Insert new record
objConn.Execute "INSERT INTO installedapps (appid, machineid, isactive) VALUES (" & edncAppId & ", " & machineid & ", 1)"
End If
rsApp.Close
Set rsApp = Nothing
' Send success response
Response.Write "{""success"":true,""message"":""Event logged""}"
End Sub
Sub GetDNCStats()
On Error Resume Next
' Get stats derived from ednclogs, joined to machines for hostname
Dim sql, rs
sql = "SELECT m.hostname, " & _
"(SELECT version FROM ednclogs WHERE machineid = l.machineid ORDER BY created DESC LIMIT 1) AS version, " & _
"MIN(l.created) AS first_seen, " & _
"MAX(l.created) AS last_seen, " & _
"SUM(CASE WHEN l.action = 'cleaned' THEN 1 ELSE 0 END) AS total_cleaned, " & _
"SUM(CASE WHEN l.action = 'failed' THEN 1 ELSE 0 END) AS total_failed, " & _
"(SELECT COUNT(*) FROM ednclogs WHERE machineid = l.machineid AND created > DATE_SUB(NOW(), INTERVAL 24 HOUR)) AS events_24h " & _
"FROM ednclogs l " & _
"INNER JOIN machines m ON l.machineid = m.machineid " & _
"GROUP BY l.machineid, m.hostname " & _
"ORDER BY last_seen DESC"
Set rs = objConn.Execute(sql) Set rs = objConn.Execute(sql)
If Err.Number <> 0 Then If Err.Number <> 0 Then
@@ -2648,7 +2600,7 @@ Sub GetDNCStats()
' Build JSON response ' Build JSON response
Dim json, first Dim json, first
json = "{""success"":true,""installations"":[" json = "{""success"":true,""partruns"":["
first = True first = True
Do While Not rs.EOF Do While Not rs.EOF
@@ -2656,22 +2608,236 @@ Sub GetDNCStats()
first = False first = False
json = json & "{" & _ json = json & "{" & _
"""hostname"":""" & (rs("hostname") & "") & """," & _ """partrunid"":" & CLng(rs("partrunid") & "0") & "," & _
"""version"":""" & (rs("version") & "") & """," & _ """machinenumber"":""" & (rs("machinenumber") & "") & """," & _
"""firstSeen"":""" & (rs("first_seen") & "") & """," & _ """partnumber"":""" & (rs("partnumber") & "") & """," & _
"""lastSeen"":""" & (rs("last_seen") & "") & """," & _ """opernumber"":""" & (rs("opernumber") & "") & """," & _
"""totalCleaned"":" & (rs("total_cleaned") + 0) & "," & _ """serialnumber"":""" & (rs("serialnumber") & "") & """," & _
"""totalFailed"":" & (rs("total_failed") + 0) & "," & _ """programname"":""" & (rs("programname") & "") & """," & _
"""events24h"":" & (rs("events_24h") + 0) & _ """jobnumber"":""" & (rs("jobnumber") & "") & """," & _
"""badgenumber"":""" & (rs("badgenumber") & "") & """," & _
"""programstart"":""" & (rs("programstart") & "") & """," & _
"""programend"":""" & (rs("programend") & "") & """," & _
"""cycletime"":" & CLng(rs("cycletime") & "0") & "," & _
"""changeover"":" & CLng(rs("changeover") & "0") & "," & _
"""measurementcount"":" & CLng(rs("measurementcount") & "0") & "," & _
"""manualcount"":" & CLng(rs("manualcount") & "0") & "," & _
"""probecount"":" & CLng(rs("probecount") & "0") & "," & _
"""ootcount"":" & CLng(rs("ootcount") & "0") & _
"}" "}"
rs.MoveNext rs.MoveNext
Loop Loop
json = json & "]}" json = json & "]}"
rs.Close rs.Close
Set rs = Nothing Set rs = Nothing
Response.ContentType = "application/json"
Response.Write json
End Sub
Sub GetUDCOperatorStats()
On Error Resume Next
Dim startdate, enddate
startdate = Trim(Request.QueryString("startdate") & "")
enddate = Trim(Request.QueryString("enddate") & "")
Dim sql, conditions
sql = "SELECT p.badgenumber, COUNT(*) AS partsrun, " & _
"AVG(p.cycletime) AS avgcycletime, AVG(p.changeover) AS avgchangeover, " & _
"SUM(p.measurementcount) AS totalmeasurements, SUM(p.manualcount) AS totalmanual, " & _
"SUM(p.ootcount) AS totaloot, MIN(p.programstart) AS firstrun, MAX(p.programend) AS lastrun, " & _
"(SELECT AVG(mr.responseseconds) FROM udcmanualrequests mr " & _
" JOIN udcparts p2 ON mr.partrunid = p2.partrunid WHERE p2.badgenumber = p.badgenumber) AS avgmanualtime " & _
"FROM udcparts p " & _
"WHERE p.badgenumber IS NOT NULL AND p.badgenumber != '' "
If startdate <> "" Then
sql = sql & " AND p.programstart >= '" & Replace(startdate, "'", "''") & "'"
End If
If enddate <> "" Then
sql = sql & " AND p.programstart <= '" & Replace(enddate, "'", "''") & " 23:59:59'"
End If
sql = sql & " GROUP BY p.badgenumber ORDER BY partsrun DESC"
Dim rs
Set rs = objConn.Execute(sql)
If Err.Number <> 0 Then
SendError "Database error: " & Err.Description
Exit Sub
End If
Dim json, first
Dim avgCycle, avgChange, avgManual
json = "{""success"":true,""operators"":["
first = True
Do While Not rs.EOF
If Not first Then json = json & ","
first = False
If IsNull(rs("avgcycletime")) Then avgCycle = 0 Else avgCycle = Round(CDbl(rs("avgcycletime")), 0)
If IsNull(rs("avgchangeover")) Then avgChange = 0 Else avgChange = Round(CDbl(rs("avgchangeover")), 0)
If IsNull(rs("avgmanualtime")) Then avgManual = 0 Else avgManual = Round(CDbl(rs("avgmanualtime")), 0)
json = json & "{" & _
"""badgenumber"":""" & (rs("badgenumber") & "") & """," & _
"""partsrun"":" & CLng(rs("partsrun") & "0") & "," & _
"""avgcycletime"":" & avgCycle & "," & _
"""avgchangeover"":" & avgChange & "," & _
"""avgmanualtime"":" & avgManual & "," & _
"""totalmeasurements"":" & CLng(rs("totalmeasurements") & "0") & "," & _
"""totalmanual"":" & CLng(rs("totalmanual") & "0") & "," & _
"""totaloot"":" & CLng(rs("totaloot") & "0") & "," & _
"""firstrun"":""" & (rs("firstrun") & "") & """," & _
"""lastrun"":""" & (rs("lastrun") & "") & """" & _
"}"
rs.MoveNext
Loop
json = json & "]}"
rs.Close
Set rs = Nothing
Response.ContentType = "application/json"
Response.Write json
End Sub
Sub GetUDCMachineStats()
On Error Resume Next
Dim startdate, enddate
startdate = Trim(Request.QueryString("startdate") & "")
enddate = Trim(Request.QueryString("enddate") & "")
Dim sql
sql = "SELECT s.machinenumber, COUNT(*) AS partsrun, " & _
"AVG(p.cycletime) AS avgcycletime, AVG(p.changeover) AS avgchangeover, " & _
"SUM(p.measurementcount) AS totalmeasurements, SUM(p.ootcount) AS totaloot, " & _
"MIN(p.programstart) AS firstrun, MAX(p.programend) AS lastrun " & _
"FROM udcparts p " & _
"JOIN udcsessions s ON p.sessionid = s.sessionid "
If startdate <> "" Or enddate <> "" Then
sql = sql & " WHERE 1=1 "
If startdate <> "" Then
sql = sql & " AND p.programstart >= '" & Replace(startdate, "'", "''") & "'"
End If
If enddate <> "" Then
sql = sql & " AND p.programstart <= '" & Replace(enddate, "'", "''") & " 23:59:59'"
End If
End If
sql = sql & " GROUP BY s.machinenumber ORDER BY partsrun DESC"
Dim rs
Set rs = objConn.Execute(sql)
If Err.Number <> 0 Then
SendError "Database error: " & Err.Description
Exit Sub
End If
Dim json, first
Dim avgCycle, avgChange
json = "{""success"":true,""machines"":["
first = True
Do While Not rs.EOF
If Not first Then json = json & ","
first = False
If IsNull(rs("avgcycletime")) Then avgCycle = 0 Else avgCycle = Round(CDbl(rs("avgcycletime")), 0)
If IsNull(rs("avgchangeover")) Then avgChange = 0 Else avgChange = Round(CDbl(rs("avgchangeover")), 0)
json = json & "{" & _
"""machinenumber"":""" & (rs("machinenumber") & "") & """," & _
"""partsrun"":" & CLng(rs("partsrun") & "0") & "," & _
"""avgcycletime"":" & avgCycle & "," & _
"""avgchangeover"":" & avgChange & "," & _
"""totalmeasurements"":" & CLng(rs("totalmeasurements") & "0") & "," & _
"""totaloot"":" & CLng(rs("totaloot") & "0") & "," & _
"""firstrun"":""" & (rs("firstrun") & "") & """," & _
"""lastrun"":""" & (rs("lastrun") & "") & """" & _
"}"
rs.MoveNext
Loop
json = json & "]}"
rs.Close
Set rs = Nothing
Response.ContentType = "application/json"
Response.Write json
End Sub
Sub GetUDCManualTiming()
On Error Resume Next
Dim machinenumber, startdate, enddate
machinenumber = Trim(Request.QueryString("machinenumber") & "")
startdate = Trim(Request.QueryString("startdate") & "")
enddate = Trim(Request.QueryString("enddate") & "")
Dim sql, conditions
sql = "SELECT mr.requestid, p.badgenumber, s.machinenumber, " & _
"mr.requesttime, mr.responsetime, mr.responseseconds, mr.description " & _
"FROM udcmanualrequests mr " & _
"JOIN udcparts p ON mr.partrunid = p.partrunid " & _
"JOIN udcsessions s ON p.sessionid = s.sessionid "
conditions = ""
If machinenumber <> "" Then
conditions = conditions & " AND s.machinenumber = '" & Replace(machinenumber, "'", "''") & "'"
End If
If startdate <> "" Then
conditions = conditions & " AND mr.requesttime >= '" & Replace(startdate, "'", "''") & "'"
End If
If enddate <> "" Then
conditions = conditions & " AND mr.requesttime <= '" & Replace(enddate, "'", "''") & " 23:59:59'"
End If
If conditions <> "" Then
sql = sql & " WHERE 1=1 " & conditions
End If
sql = sql & " ORDER BY mr.requesttime DESC LIMIT 1000"
Dim rs
Set rs = objConn.Execute(sql)
If Err.Number <> 0 Then
SendError "Database error: " & Err.Description
Exit Sub
End If
Dim json, first
json = "{""success"":true,""manualrequests"":["
first = True
Do While Not rs.EOF
If Not first Then json = json & ","
first = False
json = json & "{" & _
"""requestid"":" & CLng(rs("requestid") & "0") & "," & _
"""badgenumber"":""" & (rs("badgenumber") & "") & """," & _
"""machinenumber"":""" & (rs("machinenumber") & "") & """," & _
"""requesttime"":""" & (rs("requesttime") & "") & """," & _
"""responsetime"":""" & (rs("responsetime") & "") & """," & _
"""responseseconds"":" & CLng(rs("responseseconds") & "0") & "," & _
"""description"":""" & Replace(rs("description") & "", """", "\""") & """" & _
"}"
rs.MoveNext
Loop
json = json & "]}"
rs.Close
Set rs = Nothing
Response.ContentType = "application/json"
Response.Write json Response.Write json
End Sub End Sub

View File

@@ -112,6 +112,24 @@
Response.Redirect("default.asp") Response.Redirect("default.asp")
Response.End Response.End
End If End If
' Check if machine has UDC data (only for equipment with machinenumber)
Dim rsUDCCheck, hasUDCData, strSQL2, machineNum
hasUDCData = False
machineNum = rs("machinenumber") & ""
If machineNum <> "" Then
strSQL2 = "SELECT COUNT(*) as cnt FROM udcparts p " & _
"JOIN udcsessions s ON p.sessionid = s.sessionid " & _
"WHERE s.machinenumber = ?"
Set rsUDCCheck = ExecuteParameterizedQuery(objConn, strSQL2, Array(machineNum))
If Not rsUDCCheck Is Nothing Then
If Not rsUDCCheck.EOF Then
If CLng(rsUDCCheck("cnt") & "0") > 0 Then hasUDCData = True
End If
rsUDCCheck.Close
Set rsUDCCheck = Nothing
End If
End If
%> %>
<body class="bg-theme <%=Server.HTMLEncode(theme)%>"> <body class="bg-theme <%=Server.HTMLEncode(theme)%>">
@@ -143,6 +161,12 @@
<h5 class="card-text"><%=Server.HTMLEncode(rs("machinetype") & "")%></h5> <h5 class="card-text"><%=Server.HTMLEncode(rs("machinetype") & "")%></h5>
<%' machinedescription column doesn't exist in Phase 2 schema %> <%' machinedescription column doesn't exist in Phase 2 schema %>
<p class="card-text"><%=Server.HTMLEncode(rs("machinenotes") & "")%></p> <p class="card-text"><%=Server.HTMLEncode(rs("machinenotes") & "")%></p>
<%
' Only show Print Badge for equipment (has machinenumber), not servers/network devices
If Trim(rs("machinenumber") & "") <> "" Then
%>
<a href="./printbadge.asp?machineid=<%=Server.HTMLEncode(machineid)%>" target="_blank" class="btn btn-primary btn-sm mt-3">Print Badge</a>
<% End If %>
</div> </div>
</div> </div>
@@ -168,6 +192,11 @@
<li class="nav-item"> <li class="nav-item">
<a href="javascript:void();" data-target="#applications" data-toggle="pill" class="nav-link"><i class="zmdi zmdi-apps"></i> <span class="hidden-xs">Applications</span></a> <a href="javascript:void();" data-target="#applications" data-toggle="pill" class="nav-link"><i class="zmdi zmdi-apps"></i> <span class="hidden-xs">Applications</span></a>
</li> </li>
<% End If %>
<% If hasUDCData Then %>
<li class="nav-item">
<a href="javascript:void();" data-target="#udc" data-toggle="pill" class="nav-link"><i class="zmdi zmdi-chart"></i> <span class="hidden-xs">UDC</span></a>
</li>
<% End If %> <% End If %>
<li class="nav-item"> <li class="nav-item">
<a href="./machineedit.asp?machineid=<%=Server.HTMLEncode(machineid)%>" class="nav-link" style="background: linear-gradient(45deg, #667eea 0%, #764ba2 100%); color: white;"><i class="zmdi zmdi-edit"></i> <span class="hidden-xs">Edit Machine</span></a> <a href="./machineedit.asp?machineid=<%=Server.HTMLEncode(machineid)%>" class="nav-link" style="background: linear-gradient(45deg, #667eea 0%, #764ba2 100%); color: white;"><i class="zmdi zmdi-edit"></i> <span class="hidden-xs">Edit Machine</span></a>
@@ -408,6 +437,7 @@ End If
<tr> <tr>
<th>PC Hostname</th> <th>PC Hostname</th>
<th>IP Address</th> <th>IP Address</th>
<th>Location</th>
<th>Relationship</th> <th>Relationship</th>
</tr> </tr>
</thead> </thead>
@@ -416,7 +446,8 @@ End If
' Query PCs that control this machine (directly or via dualpath) ' Query PCs that control this machine (directly or via dualpath)
' Check both directions - the PC is identified by pctypeid IS NOT NULL ' Check both directions - the PC is identified by pctypeid IS NOT NULL
' Use GROUP_CONCAT to combine multiple IPs into one row per PC ' Use GROUP_CONCAT to combine multiple IPs into one row per PC
strSQL2 = "SELECT m.machineid, m.machinenumber, m.hostname, GROUP_CONCAT(DISTINCT c.address ORDER BY c.address SEPARATOR ', ') as address, 'Controls' as relationshiptype " & _ strSQL2 = "SELECT m.machineid, m.machinenumber, m.hostname, " & _
"GROUP_CONCAT(DISTINCT c.address ORDER BY c.address SEPARATOR ', ') as address, 'Controls' as relationshiptype " & _
"FROM machinerelationships mr " & _ "FROM machinerelationships mr " & _
"JOIN machines m ON (mr.machineid = m.machineid OR mr.related_machineid = m.machineid) " & _ "JOIN machines m ON (mr.machineid = m.machineid OR mr.related_machineid = m.machineid) " & _
"LEFT JOIN communications c ON m.machineid = c.machineid AND c.comstypeid IN (1, 3) AND c.isactive = 1 " & _ "LEFT JOIN communications c ON m.machineid = c.machineid AND c.comstypeid IN (1, 3) AND c.isactive = 1 " & _
@@ -425,11 +456,11 @@ End If
"GROUP BY m.machineid, m.machinenumber, m.hostname" "GROUP BY m.machineid, m.machinenumber, m.hostname"
Set rs2 = ExecuteParameterizedQuery(objConn, strSQL2, Array(machineid, machineid, machineid)) Set rs2 = ExecuteParameterizedQuery(objConn, strSQL2, Array(machineid, machineid, machineid))
Dim pcHostname, pcIP, pcMachineID
If rs2.EOF Then If rs2.EOF Then
Response.Write("<tr><td colspan='3' class='text-muted text-center'>No controlling PC assigned</td></tr>") Response.Write("<tr><td colspan='4' class='text-muted text-center'>No controlling PC assigned</td></tr>")
Else Else
Do While Not rs2.EOF Do While Not rs2.EOF
Dim pcHostname, pcIP, pcMachineID
pcHostname = rs2("hostname") & "" pcHostname = rs2("hostname") & ""
pcIP = rs2("address") & "" pcIP = rs2("address") & ""
pcMachineID = rs2("machineid") pcMachineID = rs2("machineid")
@@ -440,6 +471,7 @@ End If
Response.Write("<tr>") Response.Write("<tr>")
Response.Write("<td><a href='./displaypc.asp?machineid=" & pcMachineID & "'>" & Server.HTMLEncode(pcHostname) & "</a></td>") Response.Write("<td><a href='./displaypc.asp?machineid=" & pcMachineID & "'>" & Server.HTMLEncode(pcHostname) & "</a></td>")
Response.Write("<td>" & pcIP & "</td>") Response.Write("<td>" & pcIP & "</td>")
Response.Write("<td class='text-center'><a href='#' class='location-link text-info' data-machineid='" & pcMachineID & "' data-name='" & Server.HTMLEncode(pcHostname) & "'><i class='zmdi zmdi-pin' style='font-size:1.2rem;'></i></a></td>")
Response.Write("<td><span class='badge badge-primary'>" & Server.HTMLEncode(rs2("relationshiptype") & "") & "</span></td>") Response.Write("<td><span class='badge badge-primary'>" & Server.HTMLEncode(rs2("relationshiptype") & "") & "</span></td>")
Response.Write("</tr>") Response.Write("</tr>")
rs2.MoveNext rs2.MoveNext
@@ -787,6 +819,338 @@ End If
</table> </table>
</div> </div>
</div> </div>
<% End If %>
<% If hasUDCData Then %>
<div class="tab-pane" id="udc">
<h5 class="mb-3">UDC Performance Data</h5>
<!-- Today's Stats Summary Cards -->
<div class="row mb-4">
<%
' Get today's UDC stats for this machine
Dim rsUDCToday, todayParts, todayOOT, todayAvgCycle, todayLastBadge
strSQL2 = "SELECT COUNT(*) as partstoday, " & _
"SUM(ootcount) as oottoday, " & _
"AVG(cycletime) as avgcycle, " & _
"(SELECT badgenumber FROM udcparts p2 JOIN udcsessions s2 ON p2.sessionid = s2.sessionid " & _
" WHERE s2.machinenumber = ? ORDER BY p2.programend DESC LIMIT 1) as lastbadge " & _
"FROM udcparts p " & _
"JOIN udcsessions s ON p.sessionid = s.sessionid " & _
"WHERE s.machinenumber = ? AND DATE(p.programstart) = CURDATE()"
Set rsUDCToday = ExecuteParameterizedQuery(objConn, strSQL2, Array(rs("machinenumber") & "", rs("machinenumber") & ""))
If Not rsUDCToday.EOF Then
todayParts = CLng(rsUDCToday("partstoday") & "0")
todayOOT = CLng(rsUDCToday("oottoday") & "0")
If Not IsNull(rsUDCToday("avgcycle")) Then
todayAvgCycle = FormatNumber(CDbl(rsUDCToday("avgcycle")) / 60, 1)
Else
todayAvgCycle = "0"
End If
todayLastBadge = rsUDCToday("lastbadge") & ""
Else
todayParts = 0
todayOOT = 0
todayAvgCycle = "0"
todayLastBadge = ""
End If
rsUDCToday.Close
Set rsUDCToday = Nothing
%>
<div class="col-md-3">
<div class="card bg-success text-white">
<div class="card-body text-center">
<h3 class="mb-0"><%=todayParts%></h3>
<small>Parts Today</small>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-info text-white">
<div class="card-body text-center">
<h3 class="mb-0"><%=todayAvgCycle%>m</h3>
<small>Avg Cycle Time</small>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card <% If todayOOT > 0 Then Response.Write("bg-danger") Else Response.Write("bg-secondary") End If %> text-white">
<div class="card-body text-center">
<h3 class="mb-0"><%=todayOOT%></h3>
<small>OOT Today</small>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-primary text-white">
<div class="card-body text-center">
<h3 class="mb-0"><%If todayLastBadge <> "" Then Response.Write(Server.HTMLEncode(todayLastBadge)) Else Response.Write("-")%></h3>
<small>Current Operator</small>
</div>
</div>
</div>
</div>
<!-- Recent Activity Log -->
<h6 class="mb-2"><i class="zmdi zmdi-time-restore"></i> Recent Activity</h6>
<div class="table-responsive mb-4">
<table class="table table-hover table-striped table-sm">
<thead>
<tr>
<th>Time</th>
<th>Type</th>
<th>Badge</th>
<th>Details</th>
</tr>
</thead>
<tbody>
<%
' Get recent activity (violations + badge changes) for this machine
Dim rsActivity, actBadge
strSQL2 = "SELECT * FROM (" & _
"SELECT eventtime, 'Violation' as acttype, badgenumber, " & _
"CONCAT(crossingdesc, ': ', previousval, ' -> ', currentval) as details " & _
"FROM udcviolations WHERE machinenumber = ? " & _
"UNION ALL " & _
"SELECT eventtime, 'Badge Change' as acttype, badgenumber, details " & _
"FROM udcheaderupdates WHERE machinenumber = ? " & _
") combined ORDER BY eventtime DESC LIMIT 15"
Set rsActivity = ExecuteParameterizedQuery(objConn, strSQL2, Array(rs("machinenumber") & "", rs("machinenumber") & ""))
If rsActivity.EOF Then
Response.Write("<tr><td colspan='4' class='text-muted text-center'>No recent activity</td></tr>")
Else
Do While Not rsActivity.EOF
If rsActivity("acttype") = "Violation" Then
actBadge = "<span class='badge badge-light'>Setting Change</span>"
Else
actBadge = "<span class='badge badge-info'>Badge</span>"
End If
Response.Write("<tr>")
Response.Write("<td class='small'>" & Server.HTMLEncode(rsActivity("eventtime") & "") & "</td>")
Response.Write("<td>" & actBadge & "</td>")
Response.Write("<td>" & Server.HTMLEncode(rsActivity("badgenumber") & "") & "</td>")
Response.Write("<td class='small'>" & Server.HTMLEncode(rsActivity("details") & "") & "</td>")
Response.Write("</tr>")
rsActivity.MoveNext
Loop
End If
rsActivity.Close
Set rsActivity = Nothing
%>
</tbody>
</table>
</div>
<!-- Tool Health Section -->
<h6 class="mb-2 mt-4"><i class="zmdi zmdi-settings"></i> Tool Health</h6>
<%
' Get tool health summary for this machine (last 30 days)
Dim rsToolSummary, toolCount, toolMeasurements, toolOOT, toolLastCheck
strSQL2 = "SELECT COUNT(DISTINCT t.toolnumber) as unique_tools, " & _
"COUNT(*) as total_measurements, " & _
"SUM(t.oot) as oot_count, " & _
"MAX(t.eventtime) as last_check " & _
"FROM udctooldata t " & _
"JOIN udcsessions s ON t.sessionid = s.sessionid " & _
"WHERE s.machinenumber = ? AND t.eventtime >= DATE_SUB(NOW(), INTERVAL 30 DAY)"
Set rsToolSummary = ExecuteParameterizedQuery(objConn, strSQL2, Array(rs("machinenumber") & ""))
If Not rsToolSummary.EOF Then
toolCount = CLng(rsToolSummary("unique_tools") & "0")
toolMeasurements = CLng(rsToolSummary("total_measurements") & "0")
toolOOT = CLng(rsToolSummary("oot_count") & "0")
toolLastCheck = rsToolSummary("last_check") & ""
Else
toolCount = 0
toolMeasurements = 0
toolOOT = 0
toolLastCheck = ""
End If
rsToolSummary.Close
Set rsToolSummary = Nothing
If toolMeasurements > 0 Then
%>
<!-- Tool Health Summary Cards -->
<div class="row mb-3">
<div class="col-md-3">
<div class="card bg-secondary text-white">
<div class="card-body text-center py-2">
<h4 class="mb-0"><%=toolCount%></h4>
<small>Tools Monitored</small>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-info text-white">
<div class="card-body text-center py-2">
<h4 class="mb-0"><%=toolMeasurements%></h4>
<small>Measurements (30d)</small>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card <% If toolOOT > 0 Then Response.Write("bg-danger") Else Response.Write("bg-success") End If %> text-white">
<div class="card-body text-center py-2">
<h4 class="mb-0"><%=toolOOT%></h4>
<small>Out of Tolerance</small>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-primary text-white">
<div class="card-body text-center py-2">
<h6 class="mb-0"><%If toolLastCheck <> "" Then Response.Write(Server.HTMLEncode(Left(toolLastCheck, 16))) Else Response.Write("-")%></h6>
<small>Last Tool Check</small>
</div>
</div>
</div>
</div>
<!-- Tool Status Table -->
<div class="table-responsive mb-3">
<table class="table table-hover table-striped table-sm">
<thead>
<tr>
<th>Tool #</th>
<th>Description</th>
<th>Checks</th>
<th>Avg Dev</th>
<th>Max Dev</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<%
' Get tool status by tool number
Dim rsTools, toolStatus, toolStatusClass, toolAvgDev, toolMaxDev, toolDevPct
strSQL2 = "SELECT t.toolnumber, " & _
"MAX(t.description) as description, " & _
"COUNT(*) as measurements, " & _
"ROUND(AVG(t.deviation), 4) as avg_deviation, " & _
"ROUND(MAX(ABS(t.deviation)), 4) as max_deviation, " & _
"MAX(ABS(t.minval)) as tolerance_ref, " & _
"SUM(t.oot) as oot_count " & _
"FROM udctooldata t " & _
"JOIN udcsessions s ON t.sessionid = s.sessionid " & _
"WHERE s.machinenumber = ? AND t.eventtime >= DATE_SUB(NOW(), INTERVAL 30 DAY) " & _
"GROUP BY t.toolnumber " & _
"ORDER BY oot_count DESC, measurements DESC " & _
"LIMIT 10"
Set rsTools = ExecuteParameterizedQuery(objConn, strSQL2, Array(rs("machinenumber") & ""))
If rsTools.EOF Then
Response.Write("<tr><td colspan='6' class='text-muted text-center'>No tool data available</td></tr>")
Else
Do While Not rsTools.EOF
' Calculate status based on OOT and deviation
If CLng(rsTools("oot_count") & "0") > 0 Then
toolStatus = "<i class='zmdi zmdi-alert-circle'></i> <strong>OOT</strong>"
toolStatusClass = "bg-danger text-white"
Else
toolStatus = "<i class='zmdi zmdi-check-circle text-success'></i> OK"
toolStatusClass = ""
End If
If Not IsNull(rsTools("avg_deviation")) Then
toolAvgDev = FormatNumber(CDbl(rsTools("avg_deviation")), 4)
Else
toolAvgDev = "-"
End If
If Not IsNull(rsTools("max_deviation")) Then
toolMaxDev = FormatNumber(CDbl(rsTools("max_deviation")), 4)
Else
toolMaxDev = "-"
End If
Response.Write("<tr class='" & toolStatusClass & "'>")
Response.Write("<td><strong>" & Server.HTMLEncode(rsTools("toolnumber") & "") & "</strong></td>")
Response.Write("<td class='small'>" & Server.HTMLEncode(Left(rsTools("description") & "", 30)) & "</td>")
Response.Write("<td>" & rsTools("measurements") & "</td>")
Response.Write("<td>" & toolAvgDev & "</td>")
Response.Write("<td>" & toolMaxDev & "</td>")
Response.Write("<td>" & toolStatus & "</td>")
Response.Write("</tr>")
rsTools.MoveNext
Loop
End If
rsTools.Close
Set rsTools = Nothing
%>
</tbody>
</table>
</div>
<%
' Check for recent OOT events
Dim rsOOT, ootEventCount
strSQL2 = "SELECT COUNT(*) as cnt FROM udctooldata t " & _
"JOIN udcsessions s ON t.sessionid = s.sessionid " & _
"WHERE s.machinenumber = ? AND t.oot = 1 AND t.eventtime >= DATE_SUB(NOW(), INTERVAL 7 DAY)"
Set rsOOT = ExecuteParameterizedQuery(objConn, strSQL2, Array(rs("machinenumber") & ""))
ootEventCount = 0
If Not rsOOT.EOF Then ootEventCount = CLng(rsOOT("cnt") & "0")
rsOOT.Close
Set rsOOT = Nothing
If ootEventCount > 0 Then
%>
<!-- Recent OOT Events -->
<h6 class="mb-2"><i class="zmdi zmdi-alert-triangle text-warning"></i> Recent Out-of-Tolerance Events (7 days)</h6>
<div class="table-responsive mb-3">
<table class="table table-hover table-sm">
<thead class="thead-light">
<tr>
<th>Time</th>
<th>Tool #</th>
<th>Description</th>
<th>Actual</th>
<th>Min/Max</th>
<th>Deviation</th>
</tr>
</thead>
<tbody>
<%
Dim rsOOTEvents
strSQL2 = "SELECT t.eventtime, t.toolnumber, t.description, " & _
"t.actualval, t.minval, t.maxval, t.deviation " & _
"FROM udctooldata t " & _
"JOIN udcsessions s ON t.sessionid = s.sessionid " & _
"WHERE s.machinenumber = ? AND t.oot = 1 AND t.eventtime >= DATE_SUB(NOW(), INTERVAL 7 DAY) " & _
"ORDER BY t.eventtime DESC LIMIT 10"
Set rsOOTEvents = ExecuteParameterizedQuery(objConn, strSQL2, Array(rs("machinenumber") & ""))
Do While Not rsOOTEvents.EOF
Response.Write("<tr class='bg-warning'>")
Response.Write("<td class='small'>" & Server.HTMLEncode(rsOOTEvents("eventtime") & "") & "</td>")
Response.Write("<td><strong>" & Server.HTMLEncode(rsOOTEvents("toolnumber") & "") & "</strong></td>")
Response.Write("<td class='small'>" & Server.HTMLEncode(Left(rsOOTEvents("description") & "", 25)) & "</td>")
Response.Write("<td>" & FormatNumber(CDbl(rsOOTEvents("actualval") & "0"), 4) & "</td>")
Response.Write("<td class='small'>" & FormatNumber(CDbl(rsOOTEvents("minval") & "0"), 4) & " / " & FormatNumber(CDbl(rsOOTEvents("maxval") & "0"), 4) & "</td>")
Response.Write("<td class='text-danger'><strong>" & FormatNumber(CDbl(rsOOTEvents("deviation") & "0"), 4) & "</strong></td>")
Response.Write("</tr>")
rsOOTEvents.MoveNext
Loop
rsOOTEvents.Close
Set rsOOTEvents = Nothing
%>
</tbody>
</table>
</div>
<%
End If ' ootEventCount > 0
Else ' toolMeasurements = 0
%>
<div class="alert alert-secondary">
<i class="zmdi zmdi-info-outline"></i> No tool measurement data available for this machine.
</div>
<%
End If ' toolMeasurements > 0
%>
<!-- Link to Full Dashboard -->
<div class="text-center">
<a href="./displayudc.asp?machine=<%=Server.URLEncode(rs("machinenumber") & "")%>" class="btn btn-primary">
<i class="zmdi zmdi-chart"></i> View Full UDC Dashboard
</a>
</div>
</div>
<% End If %> <% End If %>
</div> </div>
</div> </div>
@@ -1024,7 +1388,7 @@ End If
$('.location-link').on('mouseenter', function(e) { $('.location-link').on('mouseenter', function(e) {
var $link = $(this); var $link = $(this);
var machineId = $link.data('machineid'); var machineId = $link.data('machineid');
var locationName = $link.text().trim(); var locationName = $link.data('name') || $link.text().trim();
var mouseEvent = e; var mouseEvent = e;
if (hoverTimer) { if (hoverTimer) {
@@ -1043,6 +1407,15 @@ End If
} }
}); });
// Also handle click for location links (useful for touch devices)
$('.location-link').on('click', function(e) {
e.preventDefault();
var $link = $(this);
var machineId = $link.data('machineid');
var locationName = $link.data('name') || $link.text().trim();
showLocationPopup(machineId, locationName, e);
});
$popup.on('mouseenter', function() {}); $popup.on('mouseenter', function() {});
$popup.on('mouseleave', function() { $popup.on('mouseleave', function() {
hideLocationPopup(); hideLocationPopup();

2173
displayudc.asp Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -906,6 +906,191 @@ Monitor these metrics:
--- ---
## UDC Log Data Endpoints
These endpoints provide access to parsed UDC (Universal Data Collector) log data for management reporting.
### 5. `getUDCPartRuns`
**Purpose:** Get part run data with cycle times and measurement counts
**Method:** GET
**Parameters (all optional):**
- `machinenumber` - Filter by machine number (e.g., "3110")
- `startdate` - Start date filter (YYYY-MM-DD)
- `enddate` - End date filter (YYYY-MM-DD)
- `badgenumber` - Filter by operator badge
**Response:**
```json
{
"success": true,
"partruns": [
{
"partrunid": 123,
"machinenumber": "3110",
"partnumber": "4096525-725G01",
"opernumber": "00602",
"serialnumber": "FGB0T7LH",
"programname": "03100042",
"jobnumber": "8J2R4",
"badgenumber": "020620BZ",
"programstart": "11/19/2025 10:02:53 AM",
"programend": "11/20/2025 2:05:16 AM",
"cycletime": 57743,
"changeover": 720,
"measurementcount": 205,
"manualcount": 25,
"probecount": 180,
"ootcount": 1
}
]
}
```
**Example:**
```bash
curl "http://server/api.asp?action=getUDCPartRuns&machinenumber=3110&startdate=2025-11-01"
```
---
### 6. `getUDCOperatorStats`
**Purpose:** Get aggregated operator statistics including average cycle times and manual response times
**Method:** GET
**Parameters (all optional):**
- `startdate` - Start date filter (YYYY-MM-DD)
- `enddate` - End date filter (YYYY-MM-DD)
**Response:**
```json
{
"success": true,
"operators": [
{
"badgenumber": "020620BZ",
"partsrun": 156,
"avgcycletime": 19347,
"avgchangeover": 5000,
"avgmanualtime": 245,
"totalmeasurements": 27920,
"totalmanual": 3500,
"totaloot": 10,
"firstrun": "11/1/2025 9:15:51 AM",
"lastrun": "12/10/2025 6:54:28 AM"
}
]
}
```
**Example:**
```bash
curl "http://server/api.asp?action=getUDCOperatorStats&startdate=2025-11-01&enddate=2025-12-31"
```
---
### 7. `getUDCMachineStats`
**Purpose:** Get aggregated machine statistics including parts produced and OOT rates
**Method:** GET
**Parameters (all optional):**
- `startdate` - Start date filter (YYYY-MM-DD)
- `enddate` - End date filter (YYYY-MM-DD)
**Response:**
```json
{
"success": true,
"machines": [
{
"machinenumber": "4003",
"partsrun": 156,
"avgcycletime": 19347,
"avgchangeover": 5000,
"totalmeasurements": 27920,
"totaloot": 10,
"firstrun": "5/4/2025 9:15:51 AM",
"lastrun": "6/17/2025 6:54:28 AM"
}
]
}
```
**Example:**
```bash
curl "http://server/api.asp?action=getUDCMachineStats"
```
---
### 8. `getUDCManualTiming`
**Purpose:** Get manual data entry response times (time from request prompt to operator entry)
**Method:** GET
**Parameters (all optional):**
- `machinenumber` - Filter by machine number
- `startdate` - Start date filter (YYYY-MM-DD)
- `enddate` - End date filter (YYYY-MM-DD)
**Response:**
```json
{
"success": true,
"manualrequests": [
{
"requestid": 123,
"badgenumber": "020620BZ",
"machinenumber": "3110",
"requesttime": "11/19/2025 10:00:27 AM",
"responsetime": "11/19/2025 10:05:31 AM",
"responseseconds": 304,
"description": "GAGE CUT STG4 BLADE DROP"
}
]
}
```
**Example:**
```bash
curl "http://server/api.asp?action=getUDCManualTiming&machinenumber=3110"
```
---
## UDC Data Import
UDC log files are imported using the Python parser at `/home/camp/projects/UDC/parser/udcparser.py`.
**Usage:**
```bash
# Import all log files from default directory
python3 udcparser.py
# Import specific file
python3 udcparser.py --file /path/to/UDC_Log_3110.log
# Specify custom directory
python3 udcparser.py --dir /path/to/logs
```
**Database Tables:**
- `udcsessions` - Log file sessions
- `udcparts` - Part runs with cycle times
- `udcmeasurements` - Individual measurements (PROCESSDATA, TOOLDATA)
- `udcevents` - Item crossings, messages
- `udcmanualrequests` - Manual data entry timing
---
## Future Enhancements ## Future Enhancements
### Planned Features: ### Planned Features:
@@ -926,7 +1111,7 @@ Monitor these metrics:
--- ---
**Version:** 1.1 **Version:** 1.2
**Last Updated:** 2025-12-11 **Last Updated:** 2025-12-12
**Maintained By:** ShopDB Development Team **Maintained By:** ShopDB Development Team
**Support:** Review `/logs/api.log` for troubleshooting **Support:** Review `/logs/api.log` for troubleshooting

View File

@@ -1,6 +1,7 @@
<% <%
' Calculate fiscal week (GE fiscal year starts first Monday of January) ' Calculate fiscal week (GE fiscal year starts first Monday of January)
Dim fwToday, fwYearStart, fwFirstMonday, fwDayOfWeek, fwDaysFromStart, fiscalWeek Dim fwToday, fwYearStart, fwFirstMonday, fwDayOfWeek, fwDaysFromStart, fiscalWeek
Dim fwPrevYearStart, fwPrevFirstMonday, fwPrevDayOfWeek
fwToday = Date() fwToday = Date()
' Find first Monday of current year ' Find first Monday of current year
@@ -14,7 +15,6 @@ End If
' If we're before the first Monday, use previous year's week count ' If we're before the first Monday, use previous year's week count
If fwToday < fwFirstMonday Then If fwToday < fwFirstMonday Then
Dim fwPrevYearStart, fwPrevFirstMonday, fwPrevDayOfWeek
fwPrevYearStart = DateSerial(Year(fwToday) - 1, 1, 1) fwPrevYearStart = DateSerial(Year(fwToday) - 1, 1, 1)
fwPrevDayOfWeek = Weekday(fwPrevYearStart, vbMonday) fwPrevDayOfWeek = Weekday(fwPrevYearStart, vbMonday)
If fwPrevDayOfWeek = 1 Then If fwPrevDayOfWeek = 1 Then
@@ -82,7 +82,11 @@ End If
<i class="zmdi zmdi-collection-image text-yellow"></i><span>Reports</span> <i class="zmdi zmdi-collection-image text-yellow"></i><span>Reports</span>
</a> </a>
</li> </li>
<li>
<a href="./displayudc.asp">
<i class="zmdi zmdi-chart text-info"></i><span>UDC Reports</span>
</a>
</li>
<li class="sidebar-header">Admin</li> <li class="sidebar-header">Admin</li>
<li><a href="./displaysubnets.asp"><i class="zmdi zmdi-network text-danger"></i><span>Network</span></a></li> <li><a href="./displaysubnets.asp"><i class="zmdi zmdi-network text-danger"></i><span>Network</span></a></li>

292
sql/udctables.sql Normal file
View File

@@ -0,0 +1,292 @@
-- ============================================================================
-- UDC Log Parser Database Schema
-- Stores parsed data from UDC (Universal Data Collector) log files
-- Created: 2025-12-12
-- ============================================================================
-- Sessions table - tracks each log file imported
CREATE TABLE IF NOT EXISTS udcsessions (
sessionid INT AUTO_INCREMENT PRIMARY KEY,
machineid INT,
machinenumber VARCHAR(20),
logfilename VARCHAR(255) NOT NULL,
sessionstart DATETIME,
sessionend DATETIME,
recordcount INT DEFAULT 0,
dateadded DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_machineid (machineid),
INDEX idx_machinenumber (machinenumber),
INDEX idx_sessionstart (sessionstart),
UNIQUE INDEX idx_logfilename (logfilename(191))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Parts table - tracks each part run (serial number cycle)
CREATE TABLE IF NOT EXISTS udcparts (
partrunid INT AUTO_INCREMENT PRIMARY KEY,
sessionid INT NOT NULL,
machineid INT,
partnumber VARCHAR(50),
opernumber VARCHAR(20),
serialnumber VARCHAR(50),
programname VARCHAR(50),
jobnumber VARCHAR(50),
badgenumber VARCHAR(20),
programstart DATETIME,
programend DATETIME,
cycletime INT,
changeover INT,
measurementcount INT DEFAULT 0,
manualcount INT DEFAULT 0,
probecount INT DEFAULT 0,
ootcount INT DEFAULT 0,
dateadded DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_sessionid (sessionid),
INDEX idx_machineid (machineid),
INDEX idx_serialnumber (serialnumber),
INDEX idx_partnumber (partnumber),
INDEX idx_jobnumber (jobnumber),
INDEX idx_badgenumber (badgenumber),
INDEX idx_programstart (programstart),
CONSTRAINT fk_udcparts_session FOREIGN KEY (sessionid) REFERENCES udcsessions(sessionid) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Measurements table - all PROCESSDATA, TOOLDATA, MACHINEDATA entries
CREATE TABLE IF NOT EXISTS udcmeasurements (
measurementid INT AUTO_INCREMENT PRIMARY KEY,
partrunid INT,
sessionid INT NOT NULL,
eventtime DATETIME,
eventtype VARCHAR(20),
method VARCHAR(20),
dimid VARCHAR(20),
description VARCHAR(255),
seqnumber INT,
minval DECIMAL(12,6),
maxval DECIMAL(12,6),
actualval DECIMAL(12,6),
deviation DECIMAL(12,6),
oot TINYINT DEFAULT 0,
INDEX idx_partrunid (partrunid),
INDEX idx_sessionid (sessionid),
INDEX idx_eventtime (eventtime),
INDEX idx_method (method),
INDEX idx_oot (oot),
CONSTRAINT fk_udcmeasurements_partrun FOREIGN KEY (partrunid) REFERENCES udcparts(partrunid) ON DELETE CASCADE,
CONSTRAINT fk_udcmeasurements_session FOREIGN KEY (sessionid) REFERENCES udcsessions(sessionid) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Events table - item crossings, messages, manual requests
CREATE TABLE IF NOT EXISTS udcevents (
eventid INT AUTO_INCREMENT PRIMARY KEY,
partrunid INT,
sessionid INT NOT NULL,
eventtime DATETIME,
eventtype VARCHAR(30),
itemnumber VARCHAR(20),
description TEXT,
INDEX idx_partrunid (partrunid),
INDEX idx_sessionid (sessionid),
INDEX idx_eventtime (eventtime),
INDEX idx_eventtype (eventtype),
CONSTRAINT fk_udcevents_partrun FOREIGN KEY (partrunid) REFERENCES udcparts(partrunid) ON DELETE CASCADE,
CONSTRAINT fk_udcevents_session FOREIGN KEY (sessionid) REFERENCES udcsessions(sessionid) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Manual requests table - tracks timing of manual data entry
CREATE TABLE IF NOT EXISTS udcmanualrequests (
requestid INT AUTO_INCREMENT PRIMARY KEY,
partrunid INT,
measurementid INT,
requesttime DATETIME,
responsetime DATETIME,
responseseconds INT,
description VARCHAR(255),
INDEX idx_partrunid (partrunid),
INDEX idx_requesttime (requesttime),
INDEX idx_responseseconds (responseseconds),
CONSTRAINT fk_udcmanualrequests_partrun FOREIGN KEY (partrunid) REFERENCES udcparts(partrunid) ON DELETE CASCADE,
CONSTRAINT fk_udcmanualrequests_measurement FOREIGN KEY (measurementid) REFERENCES udcmeasurements(measurementid) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Tool data table - TOOLDATA entries (tool offsets, wear, corner radius)
CREATE TABLE IF NOT EXISTS udctooldata (
tooldataid INT AUTO_INCREMENT PRIMARY KEY,
partrunid INT,
sessionid INT NOT NULL,
eventtime DATETIME,
method VARCHAR(20),
dimid VARCHAR(20),
description VARCHAR(255),
toolnumber INT,
minval DECIMAL(12,6),
maxval DECIMAL(12,6),
actualval DECIMAL(12,6),
deviation DECIMAL(12,6),
oot TINYINT DEFAULT 0,
INDEX idx_partrunid (partrunid),
INDEX idx_sessionid (sessionid),
INDEX idx_eventtime (eventtime),
INDEX idx_toolnumber (toolnumber),
INDEX idx_oot (oot),
CONSTRAINT fk_udctooldata_partrun FOREIGN KEY (partrunid) REFERENCES udcparts(partrunid) ON DELETE SET NULL,
CONSTRAINT fk_udctooldata_session FOREIGN KEY (sessionid) REFERENCES udcsessions(sessionid) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Errors table - error events from UDC logs
CREATE TABLE IF NOT EXISTS udcerrors (
errorid INT AUTO_INCREMENT PRIMARY KEY,
sessionid INT,
machinenumber VARCHAR(20),
eventtime DATETIME,
errortype VARCHAR(100),
errormessage TEXT,
sourcemethod VARCHAR(255),
INDEX idx_sessionid (sessionid),
INDEX idx_machinenumber (machinenumber),
INDEX idx_eventtime (eventtime),
INDEX idx_errortype (errortype)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Connections table - serial/network connection events
CREATE TABLE IF NOT EXISTS udcconnections (
connectionid INT AUTO_INCREMENT PRIMARY KEY,
sessionid INT,
machinenumber VARCHAR(20),
eventtime DATETIME,
eventtype VARCHAR(20),
comport VARCHAR(20),
details VARCHAR(255),
INDEX idx_sessionid (sessionid),
INDEX idx_machinenumber (machinenumber),
INDEX idx_eventtime (eventtime),
INDEX idx_eventtype (eventtype)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Active sessions table - tracks currently running machines
CREATE TABLE IF NOT EXISTS udcactivesessions (
activeid INT AUTO_INCREMENT PRIMARY KEY,
machinenumber VARCHAR(20) NOT NULL,
sessionid INT,
partnumber VARCHAR(50),
badgenumber VARCHAR(20),
partsrun INT DEFAULT 0,
sessionstart DATETIME,
lastupdate DATETIME,
UNIQUE INDEX idx_machinenumber (machinenumber),
INDEX idx_sessionid (sessionid),
INDEX idx_lastupdate (lastupdate)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Header updates table - badge changes during part runs
CREATE TABLE IF NOT EXISTS udcheaderupdates (
updateid INT AUTO_INCREMENT PRIMARY KEY,
partrunid INT,
sessionid INT,
machinenumber VARCHAR(20),
eventtime DATETIME,
details VARCHAR(255),
description VARCHAR(255),
badgenumber VARCHAR(20),
INDEX idx_partrunid (partrunid),
INDEX idx_sessionid (sessionid),
INDEX idx_machinenumber (machinenumber),
INDEX idx_eventtime (eventtime)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Violations table - item crossing value changes
CREATE TABLE IF NOT EXISTS udcviolations (
violationid INT AUTO_INCREMENT PRIMARY KEY,
partrunid INT,
sessionid INT,
machinenumber VARCHAR(20),
eventtime DATETIME,
previousval DECIMAL(12,4),
currentval DECIMAL(12,4),
badgenumber VARCHAR(20),
itemno VARCHAR(20),
crossingdesc VARCHAR(255),
INDEX idx_partrunid (partrunid),
INDEX idx_sessionid (sessionid),
INDEX idx_machinenumber (machinenumber),
INDEX idx_eventtime (eventtime)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- ============================================================================
-- Useful Views for Reporting
-- ============================================================================
-- Part runs with machine info
CREATE OR REPLACE VIEW vwudcpartruns AS
SELECT
p.partrunid,
p.sessionid,
p.machineid,
s.machinenumber,
p.partnumber,
p.opernumber,
p.serialnumber,
p.programname,
p.jobnumber,
p.badgenumber,
p.programstart,
p.programend,
p.cycletime,
p.changeover,
p.measurementcount,
p.manualcount,
p.probecount,
p.ootcount
FROM udcparts p
JOIN udcsessions s ON p.sessionid = s.sessionid;
-- Operator stats aggregation
CREATE OR REPLACE VIEW vwudcoperatorstats AS
SELECT
badgenumber,
COUNT(*) AS partsrun,
AVG(cycletime) AS avgcycletime,
AVG(changeover) AS avgchangeover,
SUM(measurementcount) AS totalmeasurements,
SUM(manualcount) AS totalmanual,
SUM(ootcount) AS totaloot,
MIN(programstart) AS firstrun,
MAX(programend) AS lastrun
FROM udcparts
WHERE badgenumber IS NOT NULL AND badgenumber != ''
GROUP BY badgenumber;
-- Machine stats aggregation
CREATE OR REPLACE VIEW vwudcmachinestats AS
SELECT
s.machinenumber,
p.machineid,
COUNT(*) AS partsrun,
AVG(p.cycletime) AS avgcycletime,
AVG(p.changeover) AS avgchangeover,
SUM(p.measurementcount) AS totalmeasurements,
SUM(p.ootcount) AS totaloot,
MIN(p.programstart) AS firstrun,
MAX(p.programend) AS lastrun
FROM udcparts p
JOIN udcsessions s ON p.sessionid = s.sessionid
GROUP BY s.machinenumber, p.machineid;
-- Manual request response times
CREATE OR REPLACE VIEW vwudcmanualtiming AS
SELECT
r.requestid,
p.badgenumber,
s.machinenumber,
r.requesttime,
r.responsetime,
r.responseseconds,
r.description
FROM udcmanualrequests r
JOIN udcparts p ON r.partrunid = p.partrunid
JOIN udcsessions s ON p.sessionid = s.sessionid;
-- ============================================================================
-- Verify
-- ============================================================================
SELECT 'UDC tables created successfully' AS status;
SHOW TABLES LIKE 'udc%';