|
|
|
|
@ -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
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 从 log_collect_raw 原始 JSON 重建 2026-05-07 的产量统计数据。
|
|
|
|
|
/// 使用修复后的 ProductionTracker 算法重新生成 cnc_production_segment,
|
|
|
|
|
/// 然后执行日终汇总重新聚合。
|
|
|
|
|
/// </summary>
|
|
|
|
|
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<string, int>(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<Observation>();
|
|
|
|
|
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<Segment>();
|
|
|
|
|
var states = new Dictionary<int, State>();
|
|
|
|
|
|
|
|
|
|
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}段");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|