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

View File

@@ -112,6 +112,24 @@
Response.Redirect("default.asp")
Response.End
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)%>">
@@ -143,6 +161,12 @@
<h5 class="card-text"><%=Server.HTMLEncode(rs("machinetype") & "")%></h5>
<%' machinedescription column doesn't exist in Phase 2 schema %>
<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>
@@ -168,6 +192,11 @@
<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>
</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 %>
<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>
@@ -408,6 +437,7 @@ End If
<tr>
<th>PC Hostname</th>
<th>IP Address</th>
<th>Location</th>
<th>Relationship</th>
</tr>
</thead>
@@ -416,7 +446,8 @@ End If
' Query PCs that control this machine (directly or via dualpath)
' Check both directions - the PC is identified by pctypeid IS NOT NULL
' 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 " & _
"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 " & _
@@ -425,11 +456,11 @@ End If
"GROUP BY m.machineid, m.machinenumber, m.hostname"
Set rs2 = ExecuteParameterizedQuery(objConn, strSQL2, Array(machineid, machineid, machineid))
Dim pcHostname, pcIP, pcMachineID
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
Do While Not rs2.EOF
Dim pcHostname, pcIP, pcMachineID
pcHostname = rs2("hostname") & ""
pcIP = rs2("address") & ""
pcMachineID = rs2("machineid")
@@ -440,6 +471,7 @@ End If
Response.Write("<tr>")
Response.Write("<td><a href='./displaypc.asp?machineid=" & pcMachineID & "'>" & Server.HTMLEncode(pcHostname) & "</a></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("</tr>")
rs2.MoveNext
@@ -787,6 +819,338 @@ End If
</table>
</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 %>
</div>
</div>
@@ -1024,7 +1388,7 @@ End If
$('.location-link').on('mouseenter', function(e) {
var $link = $(this);
var machineId = $link.data('machineid');
var locationName = $link.text().trim();
var locationName = $link.data('name') || $link.text().trim();
var mouseEvent = e;
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('mouseleave', function() {
hideLocationPopup();