| | |
| | | } |
| | | |
| | | 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 < ?;"; |
| | | |
| | |
| | | bool hasPass = false; |
| | | bool hasFail = false; |
| | | bool hasNo = false; |
| | | long long maxTaktSeconds = -1; |
| | | }; |
| | | std::unordered_map<std::string, PairAgg> pairs; |
| | | |
| | |
| | | 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 : ""; |
| | |
| | | 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; |
| | |
| | | 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( |
| | |
| | | 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()) { |
| | |
| | | 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; |
| | | } |