You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
haoliang-net/tools/RebuildProduction/Program.cs

244 lines
11 KiB
C#

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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}段");
}
}
}