Преглед на файлове

feat(house-fault): AppData.StartupFaults+快照透出Faults+ReportStartupFaults(走reportAlarm闭环);MonitorSnapshot.Faults(TDD)

ReportStartupFaults 经 SerialBinController.ReportAlarmController 走现有报警闭环:
相机坏photoState=1/串口坏comState=1/其余维度-1跳过 → alarm表+双端列表+短信电话+可消警。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
huangjie преди 1 ден
родител
ревизия
4b60fd9162

+ 34 - 0
ivf_tl_operate_2.0/control/IvfTl.ControlHost.Tests/MonitorSnapshotFaultTests.cs

@@ -0,0 +1,34 @@
+using System;
+using ivf_tl_Control;
+using Xunit;
+
+namespace IvfTl.ControlHost.Tests
+{
+    public class MonitorSnapshotFaultTests
+    {
+        [Fact]
+        public void Snapshot_Faults_Defaults_Empty_Not_Null()
+        {
+            var s = new MonitorSnapshot();
+            Assert.NotNull(s.Faults);
+            Assert.Empty(s.Faults);
+        }
+
+        [Fact]
+        public void HouseFaultRow_Holds_Fields()
+        {
+            var row = new HouseFaultRow
+            {
+                HouseSn = 6,
+                FaultType = "CcdSnMissing",
+                Reason = "相机列表中不存在仓室的CCDSN12345",
+                Stage = "扫口握手",
+                At = new DateTime(2026, 6, 23),
+                Isolated = true
+            };
+            Assert.Equal(6, row.HouseSn);
+            Assert.Equal("CcdSnMissing", row.FaultType);
+            Assert.True(row.Isolated);
+        }
+    }
+}

+ 49 - 0
ivf_tl_operate_2.0/control/ivf_tl_Control/AppData.cs

@@ -8,6 +8,7 @@ using IvfTl.Control.Entity.DTO.ApiRequestDTO;
 using IvfTl.Control.Entity.DTO.ApiResultDTO;
 using IvfTl.Control.Entity.GlobalEntitys;
 using IvfTl.Control.Entity.GlobalEnums;
+using IvfTl.Control.Entity.InitEntitys;
 using IvfTl.Control.Services;
 using IvfTl.Control.Services.HttpServices;
 using ivf_tl_ServicesImpl.DBServices;
@@ -194,6 +195,9 @@ namespace ivf_tl_Control
 
         public UserInfo CurrentUser { get; set; }
 
+        /// <summary>启动期坏舱清单(InitTL 写入;GetMonitorSnapshot 透出 /status;ReportStartupFaults 经 reportAlarm 闭环上报)。</summary>
+        public List<HouseFault> StartupFaults { get; set; } = new List<HouseFault>();
+
         public TLSetting TLSetting;
         public HouseBin HouseBin1;
         public HouseBin HouseBin2;
@@ -288,6 +292,21 @@ namespace ivf_tl_Control
             {
                 ExLog(ex, "GetMonitorSnapshot");
             }
+            // 舱故障透出(StartupFaults → HouseFaultRow,字符串化枚举跨端);独立 try,不影响其余快照。
+            try
+            {
+                snap.Faults = (StartupFaults ?? new List<HouseFault>())
+                    .Select(f => new HouseFaultRow
+                    {
+                        HouseSn = f.HouseSn,
+                        FaultType = f.Type.ToString(),
+                        Reason = f.Reason,
+                        Stage = f.Stage,
+                        At = f.At,
+                        Isolated = f.Isolated
+                    }).ToList();
+            }
+            catch { }
             return snap;
         }
 
@@ -1360,6 +1379,36 @@ namespace ivf_tl_Control
             }
         }
 
+        /// <summary>
+        /// 上报启动期坏舱告警(每坏舱一条),复用现有 reportAlarm 报警闭环:
+        /// SerialBinController.ReportAlarmController → /reportAlarm → 报警责任链 → alarm 表
+        /// → front/operate 报警列表 + 短信/电话通知 + 可静音/恢复自动消警(与运行期 HouseStateEvent 同一入口)。
+        /// 维度码 0正常/1异常/-1跳过:相机类故障 → photoState=1;串口/编号/Init 类 → comState=1;其余维度 -1(不误清别的告警)。
+        /// 全 try 兜底,失败不影响启动。不走 reportCloudAlarm(群消息),无新增 alarmTypeKey、无 Java 改动。
+        /// </summary>
+        public void ReportStartupFaults()
+        {
+            if (StartupFaults == null || StartupFaults.Count == 0) return;
+            string tlSn = "";
+            try { tlSn = TLSetting?.tlSn ?? ""; } catch { }
+            foreach (var f in StartupFaults)
+            {
+                // 舱号未知(相机/串口级)定位不到具体舱,不进 reportAlarm(仍在 /status Faults 里可见)。
+                if (f.HouseSn <= 0) continue;
+                try
+                {
+                    bool cameraFault = f.Type == HouseFaultType.CcdSnMissing
+                                    || f.Type == HouseFaultType.CcdSnDuplicate
+                                    || f.Type == HouseFaultType.CameraDuplicateSn
+                                    || f.Type == HouseFaultType.CameraReadFailed;
+                    int comState = cameraFault ? -1 : 1;   // 串口/编号/Init 异常
+                    int photoState = cameraFault ? 1 : -1; // 相机异常
+                    SerialBinController.ReportAlarmController(tlSn, f.HouseSn, -1, comState, photoState, -1, -1);
+                }
+                catch (Exception ex) { ExLog(ex, "ReportStartupFaults"); }
+            }
+        }
+
         public void KafkaAlarm(string tlsn, int housesn, ulong dishId, ulong embryoId, string imageName, int wellSn)
         {
             try

+ 14 - 0
ivf_tl_operate_2.0/control/ivf_tl_Control/MonitorSnapshot.cs

@@ -59,6 +59,20 @@ namespace ivf_tl_Control
         // —— 各舱室温度/气体/CCD 只读态 ——
         /// <summary>各舱室只读状态行(温度/压力/CCD/运行态)。</summary>
         public List<HouseMonitorRow> Houses { get; set; } = new List<HouseMonitorRow>();
+
+        /// <summary>舱故障列表(启动排除 + 运行期突发),供 operate 监控页红色高亮展示。</summary>
+        public List<HouseFaultRow> Faults { get; set; } = new List<HouseFaultRow>();
+    }
+
+    /// <summary>单条舱故障只读行(operate/front 展示用,字符串化枚举便于跨端)。</summary>
+    public class HouseFaultRow
+    {
+        public int HouseSn { get; set; }
+        public string FaultType { get; set; }
+        public string Reason { get; set; }
+        public string Stage { get; set; }
+        public DateTime At { get; set; }
+        public bool Isolated { get; set; }
     }
 
     /// <summary>单个舱室只读监控行(取 HouseBin 已有只读字段聚合,不触发任何动作)。</summary>