diff --git a/tools/RebuildProduction/Program.cs b/tools/RebuildProduction/Program.cs new file mode 100644 index 0000000..0cbfeae --- /dev/null +++ b/tools/RebuildProduction/Program.cs @@ -0,0 +1,243 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using Dapper; +using MySqlConnector; +using Newtonsoft.Json.Linq; + +namespace RebuildProduction +{ + /// + /// 从 log_collect_raw 原始 JSON 重建 2026-05-07 的产量统计数据。 + /// 使用修复后的 ProductionTracker 算法重新生成 cnc_production_segment, + /// 然后执行日终汇总重新聚合。 + /// + class Program + { + const string BusinessConn = "Server=localhost;Database=cnc_business;Uid=root;Pwd=root;Charset=utf8mb4;SslMode=None;"; + const string LogConn = "Server=localhost;Database=cnc_log;Uid=root;Pwd=root;Charset=utf8mb4;SslMode=None;"; + const string ProgramChange = "program_change"; + const string ManualReset = "manual_reset"; + const string EndOfDay = "end_of_day"; + + class Observation + { + public int MachineId; + public DateTime Time; + public string Program; + public decimal? Count; + } + + class Segment + { + public int MachineId; + public string Program; + public DateTime ProdDate; + public DateTime StartTime; + public DateTime? EndTime; + public decimal StartCount; + public decimal? EndCount; + public decimal? Quantity; + public string Reason; + public bool Settled; + } + + class State + { + public string LastProgram; + public decimal? LastCount; + public Segment ActiveSeg; + } + + static void Main() + { + Console.WriteLine("===== 产量重建 v1.0 ====="); + Console.WriteLine($"开始: {DateTime.Now:HH:mm:ss}"); + + // 1. 加载机床映射 + Console.Write("[1/5] 加载机床映射... "); + var devMap = new Dictionary(StringComparer.OrdinalIgnoreCase); + using (var c = new MySqlConnection(BusinessConn)) + { + foreach (var r in c.Query<(int Id, string Dc)>("SELECT id, device_code FROM cnc_machine WHERE is_enabled=1")) + devMap[r.Dc] = r.Id; + } + Console.WriteLine($"{devMap.Count}台"); + + // 2. 提取时间线 + Console.Write("[2/5] 提取时间线... "); + var obs = new List(); + int errs = 0; + using (var c = new MySqlConnection(LogConn)) + { + foreach (var r in c.Query<(long Id, DateTime T, string J)>( + "SELECT id, request_time, raw_json FROM log_collect_raw WHERE DATE(request_time)='2026-05-07' AND is_success=1 ORDER BY request_time, id")) + { + try + { + foreach (var d in JArray.Parse(r.J)) + { + var dev = (JObject)d; + var dc = dev["device"]?.ToString(); + if (dc == null || !devMap.TryGetValue(dc, out int mid)) continue; + var tags = dev["tags"] as JArray; + if (tags == null) continue; + + string prog = null, cntStr = null; + foreach (var t in tags) + { + var tid = t["id"]?.ToString(); + if (tid == "Tag5") prog = t["value"]?.ToString(); + else if (tid == "Tag8") cntStr = t["value"]?.ToString(); + } + decimal? cnt = null; + if (!string.IsNullOrEmpty(cntStr) && decimal.TryParse(cntStr, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out var v)) + cnt = v; + + obs.Add(new Observation { MachineId = mid, Time = r.T, Program = prog ?? "", Count = cnt }); + } + } + catch { errs++; } + } + } + Console.WriteLine($"{obs.Count}条 (解析失败{errs})"); + + // 3. 重放 ProductionTracker + Console.Write("[3/5] 重放算法... "); + var segs = new List(); + var states = new Dictionary(); + + foreach (var o in obs.OrderBy(x => x.MachineId).ThenBy(x => x.Time)) + { + if (string.IsNullOrEmpty(o.Program)) continue; + + if (!states.TryGetValue(o.MachineId, out var st)) + states[o.MachineId] = st = new State(); + + bool hasLast = st.LastProgram != null; + bool close = false; + string reason = ""; + + if (hasLast) + { + if (!string.Equals(st.LastProgram, o.Program, StringComparison.OrdinalIgnoreCase)) + { close = true; reason = ProgramChange; } + else if (o.Count.HasValue && st.LastCount.HasValue && o.Count.Value < st.LastCount.Value) + { close = true; reason = ManualReset; } + } + + if (close && st.ActiveSeg != null) + { + var seg = st.ActiveSeg; + seg.EndTime = o.Time; + seg.EndCount = st.LastCount; // 修复:用结账前最后已知值 + seg.Quantity = seg.EndCount.HasValue ? Math.Max(0, seg.EndCount.Value - seg.StartCount) : null; + seg.Reason = reason; + seg.Settled = true; + segs.Add(seg); + st.ActiveSeg = null; + } + + if (st.ActiveSeg == null) + { + st.ActiveSeg = new Segment + { + MachineId = o.MachineId, Program = o.Program, + ProdDate = o.Time.Date, StartTime = o.Time, + StartCount = o.Count ?? 0, Settled = false + }; + } + + if (o.Count.HasValue) st.ActiveSeg.EndCount = o.Count.Value; + st.LastProgram = o.Program; + st.LastCount = o.Count; + } + + // 活跃段 → end_of_day + foreach (var st in states.Values) + { + if (st.ActiveSeg != null) + { + var seg = st.ActiveSeg; + seg.Reason = EndOfDay; seg.Settled = true; + if (seg.EndCount.HasValue) seg.Quantity = Math.Max(0, seg.EndCount.Value - seg.StartCount); + segs.Add(seg); + } + } + + var eod = segs.Count(s => s.Reason == EndOfDay); + var mr = segs.Count(s => s.Reason == ManualReset); + var pc = segs.Count(s => s.Reason == ProgramChange); + var total = segs.Sum(s => s.Quantity ?? 0); + Console.WriteLine($"{segs.Count}段 (eod={eod}, mr={mr}, pc={pc}) 总产量={total:N0}"); + + // 4. 写入数据库 + Console.Write("[4/5] 写入数据库... "); + using (var c = new MySqlConnection(BusinessConn)) + { + c.Open(); + using var t = c.BeginTransaction(); + c.Execute("DELETE FROM cnc_production_segment WHERE production_date='2026-05-07'", transaction: t); + foreach (var s in segs) + { + c.Execute( + @"INSERT INTO cnc_production_segment (machine_id,program_name,production_date,start_time,end_time,start_part_count,end_part_count,quantity,is_settled,close_reason,created_at,updated_at) + VALUES (@a,@b,@c,@d,@e,@f,@g,@h,@i,@j,NOW(),NOW())", + new { a = s.MachineId, b = s.Program, c = s.ProdDate.ToString("yyyy-MM-dd"), d = s.StartTime, e = (object)s.EndTime ?? DBNull.Value, f = s.StartCount, g = (object)s.EndCount ?? DBNull.Value, h = (object)s.Quantity ?? DBNull.Value, i = s.Settled ? 1 : 0, j = s.Reason }, + transaction: t); + } + t.Commit(); + } + Console.WriteLine("OK"); + + // 5. 重新汇总 + Console.Write("[5/5] 重新汇总... "); + using (var c = new MySqlConnection(BusinessConn)) + { + c.Open(); + using var t = c.BeginTransaction(); + var dt = new DateTime(2026, 5, 7); + + c.Execute("DELETE FROM cnc_daily_production WHERE production_date=@d", new { d = dt }, t); + c.Execute("DELETE FROM cnc_worker_daily_summary WHERE production_date=@d", new { d = dt }, t); + c.Execute("DELETE FROM cnc_machine_daily_status WHERE production_date=@d", new { d = dt }, t); + + // 汇总 cnc_daily_production + c.Execute( + @"INSERT INTO cnc_daily_production (machine_id,production_date,program_name,total_quantity,segment_count,total_run_time,total_cutting_time,total_cycle_time,created_at,updated_at) + SELECT machine_id,@d,program_name,SUM(COALESCE(quantity,0)),COUNT(*),NULL,NULL,NULL,NOW(),NOW() + FROM cnc_production_segment WHERE DATE(start_time)=@d AND is_settled=1 GROUP BY machine_id,program_name", + new { d = dt }, t); + + // 汇总 cnc_worker_daily_summary + c.Execute( + @"INSERT INTO cnc_worker_daily_summary (worker_id,production_date,total_quantity,machine_count,program_count,created_at,updated_at) + SELECT wm.worker_id,@d,SUM(dp.total_quantity),COUNT(DISTINCT dp.machine_id),COUNT(DISTINCT dp.program_name),NOW(),NOW() + FROM cnc_daily_production dp INNER JOIN cnc_worker_machine wm ON dp.machine_id=wm.machine_id + WHERE dp.production_date=@d GROUP BY wm.worker_id", + new { d = dt }, t); + + // 机床日状态 + c.Execute( + @"INSERT INTO cnc_machine_daily_status (machine_id,production_date,data_status,created_at,updated_at) + SELECT DISTINCT machine_id,@d,'normal',NOW(),NOW() FROM cnc_collect_record WHERE DATE(collect_time)=@d", + new { d = dt }, t); + c.Execute( + @"INSERT INTO cnc_machine_daily_status (machine_id,production_date,data_status,created_at,updated_at) + SELECT m.id,@d,'offline',NOW(),NOW() FROM cnc_machine m WHERE m.is_enabled=1 AND m.id NOT IN + (SELECT DISTINCT machine_id FROM cnc_collect_record WHERE DATE(collect_time)=@d) + AND m.id NOT IN (SELECT machine_id FROM cnc_machine_daily_status WHERE production_date=@d)", + new { d = dt }, t); + + t.Commit(); + } + Console.WriteLine("OK"); + + Console.WriteLine(); + Console.WriteLine($"===== 完成: {DateTime.Now:HH:mm:ss} ====="); + Console.WriteLine($" 总产量: {total:N0}"); + Console.WriteLine($" end_of_day: {eod}段, manual_reset: {mr}段, program_change: {pc}段"); + } + } +} diff --git a/tools/RebuildProduction/RebuildProduction.csproj b/tools/RebuildProduction/RebuildProduction.csproj new file mode 100644 index 0000000..6f9d4b1 --- /dev/null +++ b/tools/RebuildProduction/RebuildProduction.csproj @@ -0,0 +1,16 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + +