fix: gate DTW comparisons on wafer type + recording category; surface load errors

This commit is contained in:
jack
2026-07-06 15:41:29 -07:00
parent 1989ba8e5a
commit 0014ccc184
5 changed files with 272 additions and 23 deletions
+53 -4
View File
@@ -1,5 +1,6 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Controls.impl
import QtQuick.Dialogs
import QtQuick.Layouts
import ISC
@@ -30,11 +31,23 @@ Item {
// into the trend chart's data property. The slot parseJsonToData() is defined
// on the Python TrendChartItem; we use it so malformed payloads are logged
// there instead of crashing the QML binding.
property string loadErrorMessage: ""
Connections {
target: streamController
function onTrendData(avgsJson) {
trendChart.setDataFromJson(avgsJson);
}
// A file was picked from the rail (any tab) — loadFile() switches the
// backend to review mode; mirror that in the toolbar toggle so a
// successful load is never hidden behind a stale "Live" selection.
function onLoadedFileChanged() {
root.loadErrorMessage = "";
if (streamController.mode === "review")
modeBar.currentIndex = 0;
}
function onLoadFileError(message) {
root.loadErrorMessage = message;
}
}
ColumnLayout {
@@ -118,12 +131,13 @@ Item {
RowLayout {
visible: streamController.mode === "live"
spacing: 5
// WiFi icon (Unicode approximation; swap for SVG if available)
Label {
// Live status dot (matches the connection-flow indicator dot elsewhere)
Rectangle {
id: liveIndicator
text: "◉"
width: 8; height: 8
radius: 4
Layout.alignment: Qt.AlignVCenter
color: (deviceController.connectionStatus === "Connected" && streamController.state !== "idle") ? Theme.liveColor : Theme.bodyColor
font.pixelSize: Theme.fontSm
SequentialAnimation on opacity {
running: deviceController.connectionStatus === "Connected" && streamController.state !== "idle"
@@ -250,6 +264,41 @@ Item {
}
}
// ── Load error banner ───────────────────────────────────────────
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: errorRow.implicitHeight + 16
visible: root.loadErrorMessage !== ""
color: Theme.errorSurface
border.color: Theme.statusErrorColor
border.width: 1
radius: Theme.radiusSm
RowLayout {
id: errorRow
anchors.fill: parent
anchors.margins: 8
spacing: 8
IconImage {
source: "icons/triangle-alert.svg"
width: 16; height: 16
sourceSize.width: 16
sourceSize.height: 16
color: Theme.errorTextSoft
Layout.alignment: Qt.AlignTop
}
Label {
text: root.loadErrorMessage
color: Theme.errorTextSoft
font.pixelSize: Theme.fontSm
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
}
}
// ── Body: 2 columns (wafer + trend + transport | readout) ──────────
RowLayout {
Layout.fillWidth: true
+62 -19
View File
@@ -122,10 +122,12 @@ ColumnLayout {
}
contentItem: RowLayout {
spacing: 6
Label {
text: "▤"
IconImage {
source: "../icons/folder.svg"
width: 14; height: 14
sourceSize.width: 14
sourceSize.height: 14
color: Theme.bodyColor
font.pixelSize: Theme.fontSm
}
Label {
text: file_browser.currentDirectory.split("/").pop() || file_browser.currentDirectory
@@ -204,7 +206,7 @@ ColumnLayout {
onClicked: streamController.loadFile(modelData.fileName)
background: Rectangle {
color: fileItem.isActive ? Qt.rgba(1, 1, 1, 0.06) : fileItem.hovered ? Qt.rgba(1, 1, 1, 0.04) : "transparent"
color: fileItem.isActive ? Theme.listActiveBackground : fileItem.hovered ? Theme.listHoverBackground : "transparent"
radius: Theme.radiusSm
Behavior on color {
ColorAnimation { duration: Theme.durationFast }
@@ -225,11 +227,11 @@ ColumnLayout {
color: {
var t = modelData.waferType;
if (t === "A" || t === "E" || t === "P")
return "#3B82F6";
return Theme.familyBlueAccent;
if (t === "B" || t === "C" || t === "D")
return "#10B981";
return Theme.familyGreenAccent;
if (t === "Z")
return "#8B5CF6";
return Theme.familyVioletAccent;
return Theme.headingColor;
}
}
@@ -249,19 +251,36 @@ ColumnLayout {
color: {
var t = modelData.waferType;
if (t === "A" || t === "E" || t === "P")
return "#1D4ED8";
return Theme.familyBlueFill;
if (t === "B" || t === "C" || t === "D")
return "#065F46";
return Theme.familyGreenFill;
if (t === "Z")
return "#7C3AED";
return "#374151";
return Theme.familyVioletFill;
return Theme.familyNeutralFill;
}
Label {
anchors.centerIn: parent
text: modelData.waferType || "?"
font.pixelSize: Theme.fontMd
font.weight: Font.Bold
color: "#FFFFFF"
color: Theme.textOnColor
}
// Recording indicator dot — distinguishes a live-recorded CSV
// (SessionController.startRecording) from a read-memory dump
// (DeviceController.parseAndSaveData).
Rectangle {
visible: modelData.isRecording === true
width: 10
height: 10
radius: 5
color: Theme.recordColor
border.color: Theme.subtleSectionBackground
border.width: 1.5
anchors.top: parent.top
anchors.right: parent.right
anchors.topMargin: -2
anchors.rightMargin: -2
}
}
@@ -270,14 +289,38 @@ ColumnLayout {
spacing: 1
Layout.fillWidth: true
// Primary: wafer type + serial number
Label {
text: modelData.serialNumber ? (modelData.waferType + modelData.serialNumber) : modelData.baseName
color: Theme.headingColor
font.pixelSize: Theme.fontMd
font.weight: Font.Bold
elide: Text.ElideRight
// Primary: wafer type + serial number, plus a REC badge for recordings
RowLayout {
Layout.fillWidth: true
spacing: 6
Label {
text: modelData.serialNumber ? (modelData.waferType + modelData.serialNumber) : modelData.baseName
color: Theme.headingColor
font.pixelSize: Theme.fontMd
font.weight: Font.Bold
elide: Text.ElideRight
Layout.fillWidth: true
}
Rectangle {
visible: modelData.isRecording === true
radius: 4
color: Qt.alpha(Theme.recordColor, 0.18)
implicitWidth: recLabel.implicitWidth + 10
implicitHeight: recLabel.implicitHeight + 4
Layout.alignment: Qt.AlignVCenter
Label {
id: recLabel
anchors.centerIn: parent
text: "REC"
color: Theme.recordColor
font.pixelSize: Theme.font2xs
font.bold: true
font.letterSpacing: 0.6
}
}
}
// Secondary: date · time
+7
View File
@@ -119,6 +119,11 @@ class FileBrowser(QObject):
return
for csv_path in sorted(self._current_directory.glob("*.csv")):
# Recorded-live CSVs are named "live_<serial>_<timestamp>.csv" by
# SessionController.startRecording (see WaferMapTab's Record button),
# distinct from the "<serial>-<timestamp>.csv" convention used by
# DeviceController.parseAndSaveData for read-memory dumps.
is_recording = csv_path.stem.lower().startswith("live_")
try:
metadata = self._load_metadata(csv_path)
wafer_type = metadata.get_wafer_type().upper() or (csv_path.stem[0].upper() if csv_path.stem else "")
@@ -146,6 +151,7 @@ class FileBrowser(QObject):
"masterType": master_state.get(str(csv_path), "") or metadata.master_type,
"fileName": str(csv_path),
"highlight": wafer_type in {"A", "B", "C"},
"isRecording": is_recording,
}
)
except Exception:
@@ -169,6 +175,7 @@ class FileBrowser(QObject):
"masterType": master_state.get(str(csv_path), ""),
"fileName": str(csv_path),
"highlight": False,
"isRecording": is_recording,
}
)