From 0222ece90a4caa554b1dd09aa37b95ad394bf893 Mon Sep 17 00:00:00 2001
From: chenluhua1980 <Chenluhua@qq.com>
Date: 星期五, 26 十二月 2025 15:53:51 +0800
Subject: [PATCH] 1.显示生产数据和tt

---
 SourceCode/Bond/Servo/CPageProdOverview.cpp |   35 ++++++++++-
 SourceCode/Bond/Servo/ProductionStats.cpp   |   63 ++++++++++++++++++++
 SourceCode/Bond/Servo/ProductionStats.h     |   10 +++
 SourceCode/Bond/Servo/CPanelProduction.cpp  |   20 ++----
 SourceCode/Bond/Servo/CPanelProduction.h    |    9 +-
 5 files changed, 114 insertions(+), 23 deletions(-)

diff --git a/SourceCode/Bond/Servo/CPageProdOverview.cpp b/SourceCode/Bond/Servo/CPageProdOverview.cpp
index 6ca851e..11813f4 100644
--- a/SourceCode/Bond/Servo/CPageProdOverview.cpp
+++ b/SourceCode/Bond/Servo/CPageProdOverview.cpp
@@ -5,11 +5,12 @@
 #include "Servo.h"
 #include "CPageProdOverview.h"
 #include "afxdialogex.h"
+#include "CPanelProduction.h"
 
 namespace
 {
 	constexpr UINT_PTR kTimerRefreshId = 2001;
-	constexpr UINT kTimerRefreshIntervalMs = 5000;
+	constexpr UINT kTimerRefreshIntervalMs = 10000;
 }
 
 IMPLEMENT_DYNAMIC(CPageProdOverview, CDialogEx)
@@ -129,12 +130,36 @@
 
 void CPageProdOverview::RefreshData()
 {
+	auto* pPanel = dynamic_cast<CPanelProduction*>(GetParent());
+	if (pPanel == nullptr) {
+		pPanel = dynamic_cast<CPanelProduction*>(GetParent() ? GetParent()->GetParent() : nullptr);
+	}
+	if (pPanel == nullptr) {
+		m_labelDayOut.setText(_T("--"));
+		m_labelNightOut.setText(_T("--"));
+		m_labelDayTakt.setText(_T("--"));
+		m_labelNightTakt.setText(_T("--"));
+		return;
+	}
+
+	ProductionShiftSummary day;
+	ProductionShiftSummary night;
+	if (!pPanel->TryGetDayNightSummaries(day, night)) {
+		m_labelDayOut.setText(_T("--"));
+		m_labelNightOut.setText(_T("--"));
+		m_labelDayTakt.setText(_T("--"));
+		m_labelNightTakt.setText(_T("--"));
+		return;
+	}
+
 	CString text;
-	text.Format(_T("%d"), 123);
+	text.Format(_T("%lld"), day.output.pairsTotal);
 	m_labelDayOut.setText(text);
-	text.Format(_T("%d"), 1235);
+	text.Format(_T("%lld"), night.output.pairsTotal);
 	m_labelNightOut.setText(text);
 
-	m_labelDayTakt.setText(_T("1236"));
-	m_labelNightTakt.setText(_T("1238"));
+	text.Format(_T("%.1fs"), day.output.avgTaktSeconds);
+	m_labelDayTakt.setText(text);
+	text.Format(_T("%.1fs"), night.output.avgTaktSeconds);
+	m_labelNightTakt.setText(text);
 }
diff --git a/SourceCode/Bond/Servo/CPanelProduction.cpp b/SourceCode/Bond/Servo/CPanelProduction.cpp
index 814a631..d8c9e2c 100644
--- a/SourceCode/Bond/Servo/CPanelProduction.cpp
+++ b/SourceCode/Bond/Servo/CPanelProduction.cpp
@@ -156,11 +156,12 @@
 	}
 }
 
-BOOL CPanelProduction::TryGetShiftSummary(ProductionShiftSummary& outSummary)
+BOOL CPanelProduction::TryGetDayNightSummaries(ProductionShiftSummary& outDay, ProductionShiftSummary& outNight)
 {
 	CSingleLock lock(&m_csShiftSummary, TRUE);
 	if (!m_bShiftSummaryValid) return FALSE;
-	outSummary = m_shiftSummary;
+	outDay = m_daySummary;
+	outNight = m_nightSummary;
 	return TRUE;
 }
 
@@ -197,10 +198,12 @@
 	for (;;) {
 		if (self->m_evStopStats.Lock(intervalMs)) break;
 
-		ProductionShiftSummary summary;
-		if (ProductionStats::ComputeCurrentShiftSummary(theApp.m_model.m_configuration, summary)) {
+		ProductionShiftSummary daySummary;
+		ProductionShiftSummary nightSummary;
+		if (ProductionStats::ComputeDayNightSummaries(theApp.m_model.m_configuration, daySummary, nightSummary)) {
 			CSingleLock lock(&self->m_csShiftSummary, TRUE);
-			self->m_shiftSummary = std::move(summary);
+			self->m_daySummary = std::move(daySummary);
+			self->m_nightSummary = std::move(nightSummary);
 			self->m_bShiftSummaryValid = TRUE;
 		}
 	}
@@ -211,12 +214,5 @@
 void CPanelProduction::OnTimer(UINT_PTR nIDEvent)
 {
 	// TODO: 鍦ㄦ娣诲姞娑堟伅澶勭悊绋嬪簭浠g爜鍜�/鎴栬皟鐢ㄩ粯璁ゅ��
-	if (nIDEvent == 1) {
-		ProductionShiftSummary outSummary;
-		if (TryGetShiftSummary(outSummary)) {
-			TRACE("OnTimer outSummary.output.pairsPass:%d\n", outSummary.output.pairsPass);
-		}
-	}
-
 	CDialogEx::OnTimer(nIDEvent);
 }
diff --git a/SourceCode/Bond/Servo/CPanelProduction.h b/SourceCode/Bond/Servo/CPanelProduction.h
index cbf285f..66f5d06 100644
--- a/SourceCode/Bond/Servo/CPanelProduction.h
+++ b/SourceCode/Bond/Servo/CPanelProduction.h
@@ -24,8 +24,9 @@
 	HWND m_hPlaceholder;
 	CAccordionWnd* m_pAccordionWnd;
 
-	// Production shift summary (updated by background thread)
-	ProductionShiftSummary m_shiftSummary;
+	// Production shift summaries (updated by background thread)
+	ProductionShiftSummary m_daySummary;
+	ProductionShiftSummary m_nightSummary;
 	BOOL m_bShiftSummaryValid;
 	CCriticalSection m_csShiftSummary;
 	CWinThread* m_pStatsThread;
@@ -49,8 +50,8 @@
 	afx_msg void OnVLineMoveX(NMHDR* nmhdr, LRESULT* result);
 	afx_msg void OnBnClickedButtonClose();
 
-	// Thread-safe snapshot for UI timer display
-	BOOL TryGetShiftSummary(ProductionShiftSummary& outSummary);
+	// Thread-safe snapshots for UI timer display
+	BOOL TryGetDayNightSummaries(ProductionShiftSummary& outDay, ProductionShiftSummary& outNight);
 
 private:
 	static UINT AFX_CDECL StatsThreadProc(LPVOID pParam);
diff --git a/SourceCode/Bond/Servo/ProductionStats.cpp b/SourceCode/Bond/Servo/ProductionStats.cpp
index aff2a74..edf6272 100644
--- a/SourceCode/Bond/Servo/ProductionStats.cpp
+++ b/SourceCode/Bond/Servo/ProductionStats.cpp
@@ -88,7 +88,9 @@
 	}
 
 	const char* sql =
-		"SELECT class_id, buddy_id, aoi_result "
+		"SELECT class_id, buddy_id, aoi_result, "
+		"IFNULL(strftime('%Y-%m-%d %H:%M:%S', t_start, 'localtime'), ''),"
+		"IFNULL(strftime('%Y-%m-%d %H:%M:%S', t_end,   'localtime'), '') "
 		"FROM glass_log "
 		"WHERE t_end IS NOT NULL AND t_end >= ? AND t_end < ?;";
 
@@ -105,6 +107,7 @@
 		bool hasPass = false;
 		bool hasFail = false;
 		bool hasNo = false;
+		long long maxTaktSeconds = -1;
 	};
 	std::unordered_map<std::string, PairAgg> pairs;
 
@@ -114,6 +117,8 @@
 			const char* classId = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0));
 			const char* buddyId = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 1));
 			const int aoi = sqlite3_column_int(stmt, 2);
+			const char* sStart = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 3));
+			const char* sEnd = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 4));
 
 			const std::string a = classId ? classId : "";
 			const std::string b = buddyId ? buddyId : "";
@@ -130,6 +135,12 @@
 			if (aoi == 1) agg.hasPass = true;
 			else if (aoi == 2) agg.hasFail = true;
 			else agg.hasNo = true;
+
+			std::chrono::system_clock::time_point tpStart{}, tpEnd{};
+			if (TryParseLocalTime(sStart ? sStart : "", tpStart) && TryParseLocalTime(sEnd ? sEnd : "", tpEnd) && tpEnd > tpStart) {
+				const auto secs = std::chrono::duration_cast<std::chrono::seconds>(tpEnd - tpStart).count();
+				if (secs > agg.maxTaktSeconds) agg.maxTaktSeconds = secs;
+			}
 		}
 		else if (rc == SQLITE_DONE) {
 			break;
@@ -143,14 +154,23 @@
 	sqlite3_close(db);
 
 	out.pairsTotal = static_cast<long long>(pairs.size());
+	long long sumTakt = 0;
+	long long cntTakt = 0;
 	for (const auto& kv : pairs) {
 		const auto& agg = kv.second;
 		if (agg.hasFail) out.pairsFail++;
 		else if (agg.hasPass) out.pairsPass++;
 		else out.pairsNoResult++;
+
+		if (agg.maxTaktSeconds >= 0) {
+			sumTakt += agg.maxTaktSeconds;
+			cntTakt += 1;
+		}
 	}
 	const long long denom = out.pairsPass + out.pairsFail;
 	out.yield = (denom > 0) ? (static_cast<double>(out.pairsPass) / static_cast<double>(denom)) : 0.0;
+	out.taktSamplePairs = cntTakt;
+	out.avgTaktSeconds = (cntTakt > 0) ? (static_cast<double>(sumTakt) / static_cast<double>(cntTakt)) : 0.0;
 }
 
 static void ComputeAlarmSummaryFromDb(
@@ -359,6 +379,7 @@
 	LOGI("<ProductionStats>Shift=%s, [%s ~ %s]", shiftName, s.window.startLocal.c_str(), s.window.endLocal.c_str());
 	LOGI("<ProductionStats>Output(pairs): total=%lld, pass=%lld, fail=%lld, no_result=%lld, yield=%.2f%%",
 		s.output.pairsTotal, s.output.pairsPass, s.output.pairsFail, s.output.pairsNoResult, s.output.yield * 100.0);
+	LOGI("<ProductionStats>Takt: avg=%.1fs, samples=%lld", s.output.avgTaktSeconds, s.output.taktSamplePairs);
 	LOGI("<ProductionStats>Alarms: triggered=%d, overlapping=%d, downtime=%.1f min",
 		s.alarms.alarmsTriggered, s.alarms.alarmsOverlapping, s.alarms.downtimeMinutes);
 	if (!s.alarms.bySeverity.empty()) {
@@ -375,3 +396,43 @@
 		LOGI("%s", oss.str().c_str());
 	}
 }
+
+bool ProductionStats::ComputeDayNightSummaries(CConfiguration& config, ProductionShiftSummary& outDay, ProductionShiftSummary& outNight)
+{
+	ProductionShiftSummary cur;
+	if (!ComputeCurrentShiftSummary(config, cur)) return false;
+
+	// Determine previous adjacent window for the other shift.
+	ProductionShiftSummary other = cur;
+	if (cur.window.type == ProductionShiftType::Day) {
+		other.window.type = ProductionShiftType::Night;
+		other.window.end = cur.window.start;
+		other.window.start = cur.window.start - (cur.window.end - cur.window.start);
+	}
+	else {
+		other.window.type = ProductionShiftType::Day;
+		other.window.end = cur.window.start;
+		other.window.start = cur.window.start - (cur.window.end - cur.window.start);
+	}
+	other.window.startLocal = FormatLocal(other.window.start);
+	other.window.endLocal = FormatLocal(other.window.end);
+	other.window.startUtcIso = FormatUtcIso(other.window.start);
+	other.window.endUtcIso = FormatUtcIso(other.window.end);
+
+	other.output = ProductionOutputSummary{};
+	other.alarms = ProductionAlarmSummary{};
+	other.transfers = ProductionTransferSummary{};
+	ComputeOutputFromProcessDb(other.window, other.output);
+	ComputeAlarmSummaryFromDb(other.window, other.alarms);
+	ComputeTransferSummaryFromDb(other.window, other.transfers);
+
+	if (cur.window.type == ProductionShiftType::Day) {
+		outDay = std::move(cur);
+		outNight = std::move(other);
+	}
+	else {
+		outNight = std::move(cur);
+		outDay = std::move(other);
+	}
+	return true;
+}
diff --git a/SourceCode/Bond/Servo/ProductionStats.h b/SourceCode/Bond/Servo/ProductionStats.h
index 84c8cb5..c884228 100644
--- a/SourceCode/Bond/Servo/ProductionStats.h
+++ b/SourceCode/Bond/Servo/ProductionStats.h
@@ -27,6 +27,10 @@
 	long long pairsFail = 0;
 	long long pairsNoResult = 0;
 	double yield = 0.0;              // pairsPass / (pairsPass + pairsFail), 0 if denom==0
+
+	// Average takt time derived from glass_log.t_start/t_end (per pair, seconds)
+	double avgTaktSeconds = 0.0;
+	long long taktSamplePairs = 0;
 };
 
 struct ProductionAlarmSummary {
@@ -54,5 +58,9 @@
 	static bool GetCurrentShiftWindow(CConfiguration& config, ProductionShiftWindow& outWindow);
 	static bool ComputeCurrentShiftSummary(CConfiguration& config, ProductionShiftSummary& outSummary);
 	static void LogCurrentShiftSummary(CConfiguration& config);
-};
 
+	// Computes "current shift" and its adjacent other shift, so UI can always show Day+Night numbers.
+	// - If current is Day: day=current day shift, night=previous night shift.
+	// - If current is Night: night=current night shift, day=previous day shift.
+	static bool ComputeDayNightSummaries(CConfiguration& config, ProductionShiftSummary& outDay, ProductionShiftSummary& outNight);
+};

--
Gitblit v1.9.3