Files
XRLib/Assets/Scripts/SHI/modal/GanttJsonParser.cs
2025-11-17 19:30:05 +09:00

203 lines
9.1 KiB
C#

// GanttJsonParser: 행 기반(STDTxx/FNDTxx/DURxx) 간트 데이터 파서
// 요구: 오류가 있어도 삭제하지 말 것.
#nullable enable
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text.RegularExpressions;
using UnityEngine;
namespace SHI.modal
{
/// <summary>
/// isop_chart.json과 같은 행 배열(딕셔너리)의 STDTxx / FNDTxx / DURxx 패턴을 해석하여 <see cref="GanttChartData"/>를 생성합니다.
/// </summary>
public static class GanttJsonParser
{
private static readonly Regex StartRegex = new Regex("^STDT([0-9A-FM]{2})$", RegexOptions.Compiled);
private static readonly Regex EndRegex = new Regex("^FNDT([0-9A-FM]{2})$", RegexOptions.Compiled);
private static readonly Regex DurRegex = new Regex("^DUR([0-9A-FM]{2})$", RegexOptions.Compiled);
private const string DateFormat = "yyyyMMdd";
/// <summary>
/// JSON 배열 문자열을 파싱해 Rows/Segments/MinDate/MaxDate를 채웁니다.
/// Duration 누락 시 (End-Start)+1을 사용합니다.
/// </summary>
public static GanttChartData Parse(string json, bool computeDurationIfMissing = true)
{
var data = new GanttChartData();
if (string.IsNullOrWhiteSpace(json)) return data;
List<Dictionary<string, object>>? rows;
try
{
// Newtonsoft.Json 사용 (Assembly 내 참조 필요). 실패 시 경고 후 빈 데이터 반환.
rows = Newtonsoft.Json.JsonConvert.DeserializeObject<List<Dictionary<string, object>>>(json);
}
catch (Exception ex)
{
Debug.LogWarning($"GanttJsonParser.Parse: JSON deserialize failed: {ex.Message}");
return data;
}
if (rows == null) return data;
DateTime? min = null; DateTime? max = null;
foreach (var raw in rows)
{
if (raw == null) continue;
var row = new BlockScheduleRow
{
ProjNo = GetStr(raw, "PROJ_NO"),
BlockNo = GetStr(raw, "BLK_NO"),
L1 = GetStr(raw, "L1"),
L2 = GetStr(raw, "L2"),
L3 = GetStr(raw, "L3"),
L4 = GetStr(raw, "L4"),
L5 = GetStr(raw, "L5"),
L6 = GetStr(raw, "L6"),
L7 = GetStr(raw, "L7"),
L8 = GetStr(raw, "L8"),
// 추가 메타
BlkDsc = GetStr(raw, "BLK_DSC"),
ShipType = GetStr(raw, "SHIP_TYPE"),
ShrGb = GetStr(raw, "SHR_GB"),
Lth = GetFloat(raw, "LTH"),
Bth = GetFloat(raw, "BTH"),
Hgt = GetFloat(raw, "HGT"),
NetWgt = GetFloat(raw, "NET_WGT"),
EqupWgt = GetFloat(raw, "EQUP_WGT"),
LftWgt = GetFloat(raw, "LFT_WGT"),
WkType = GetStr(raw, "WK_TYPE"),
Wc31 = GetStr(raw, "WC31"),
};
var startMap = new Dictionary<string, DateTime>();
var endMap = new Dictionary<string, DateTime>();
var durMap = new Dictionary<string, int>();
foreach (var kv in raw)
{
if (kv.Key == null || kv.Value == null) continue;
var val = kv.Value.ToString();
if (string.IsNullOrWhiteSpace(val)) continue;
var sm = StartRegex.Match(kv.Key);
if (sm.Success)
{
if (TryParseDate(val, out var sdt))
{
var code = sm.Groups[1].Value;
if (startMap.ContainsKey(code)) Debug.Log($"GanttJsonParser: duplicate STDT for code={code}, last value wins.");
startMap[code] = sdt; UpdateMinMax(ref min, ref max, sdt); continue;
}
else { Debug.LogWarning($"GanttJsonParser: start date parse failed key={kv.Key} value={val}"); continue; }
}
var em = EndRegex.Match(kv.Key);
if (em.Success)
{
if (TryParseDate(val, out var edt))
{
var code = em.Groups[1].Value;
if (endMap.ContainsKey(code)) Debug.Log($"GanttJsonParser: duplicate FNDT for code={code}, last value wins.");
endMap[code] = edt; UpdateMinMax(ref min, ref max, edt); continue;
}
else { Debug.LogWarning($"GanttJsonParser: end date parse failed key={kv.Key} value={val}"); continue; }
}
var dm = DurRegex.Match(kv.Key);
if (dm.Success)
{
var code = dm.Groups[1].Value;
if (int.TryParse(val, out var dur))
{
if (durMap.ContainsKey(code)) Debug.Log($"GanttJsonParser: duplicate DUR for code={code}, last value wins.");
durMap[code] = dur; continue;
}
else { Debug.LogWarning($"GanttJsonParser: duration parse failed key={kv.Key} value={val}"); continue; }
}
}
var codes = new HashSet<string>(startMap.Keys);
codes.UnionWith(endMap.Keys);
codes.UnionWith(durMap.Keys);
foreach (var code in codes)
{
var range = new DateRange();
if (startMap.TryGetValue(code, out var s)) range.Start = s;
if (endMap.TryGetValue(code, out var e)) range.End = e;
if (durMap.TryGetValue(code, out var d)) range.Duration = d;
else if (computeDurationIfMissing && range.Start.HasValue && range.End.HasValue)
range.Duration = (int)(range.End.Value.Date - range.Start.Value.Date).TotalDays + 1;
// Start > End 정책: Skip + Warning
if (range.Start.HasValue && range.End.HasValue && range.End.Value.Date < range.Start.Value.Date)
{
Debug.LogWarning($"GanttJsonParser: Start > End. Skip record block={row.BlockNo} code={code} start={range.Start:yyyyMMdd} end={range.End:yyyyMMdd}");
continue;
}
row.Periods[code] = range;
if (range.Start.HasValue && range.End.HasValue)
{
// Flat segment 동시 생성 (유효한 경우만)
data.Segments.Add(new ScheduleSegment
{
ItemKey = (row.BlockNo ?? string.Empty) + "|" + code,
ItemId = Guid.Empty,
Start = range.Start.Value,
End = range.End.Value,
Progress = 0f,
Type = code
});
}
}
data.Rows.Add(row);
}
data.MinDate = min; data.MaxDate = max;
return data;
}
private static void UpdateMinMax(ref DateTime? min, ref DateTime? max, DateTime v)
{
if (!min.HasValue || v < min.Value) min = v;
if (!max.HasValue || v > max.Value) max = v;
}
private static bool TryParseDate(string s, out DateTime dt)
=> DateTime.TryParseExact(s, DateFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out dt);
private static string? GetStr(Dictionary<string, object> dict, string key)
=> dict.TryGetValue(key, out var v) ? v?.ToString() : null;
private static float? GetFloat(Dictionary<string, object> dict, string key)
{
if (!dict.TryGetValue(key, out var v) || v == null) return null;
try
{
switch (v)
{
case float f: return f;
case double d: return (float)d;
case decimal m: return (float)m;
case int i: return i;
case long l: return l;
case short s: return s;
case string str:
if (string.IsNullOrWhiteSpace(str)) return null;
if (float.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out var pf)) return pf;
if (float.TryParse(str, NumberStyles.Any, CultureInfo.CurrentCulture, out pf)) return pf;
return null;
default:
// 마지막 시도: System.Convert
return (float)Convert.ToDouble(v, CultureInfo.InvariantCulture);
}
}
catch { return null; }
}
}
}