diff --git a/Assets/Resources/log4net.editor.xml b/Assets/Resources/log4net.editor.xml index 1aa446ad..95ed2b8c 100644 --- a/Assets/Resources/log4net.editor.xml +++ b/Assets/Resources/log4net.editor.xml @@ -8,7 +8,7 @@ - A + diff --git a/Assets/Resources/log4net.runtime.xml b/Assets/Resources/log4net.runtime.xml index 9cc254f1..b80401b4 100644 --- a/Assets/Resources/log4net.runtime.xml +++ b/Assets/Resources/log4net.runtime.xml @@ -8,7 +8,7 @@ - A + diff --git a/Assets/Scripts/SampleProject/AppMain.cs b/Assets/Scripts/SampleProject/AppMain.cs index d0b32fa6..8076245e 100644 --- a/Assets/Scripts/SampleProject/AppMain.cs +++ b/Assets/Scripts/SampleProject/AppMain.cs @@ -37,12 +37,12 @@ namespace SampleProject //Dictionary headers = new Dictionary(); //headers.Add("Content-Encoding", "gzip"); //string result = await HttpRequester.RequestPost("http://localhost:8888/AGV/00:00", (string)null, headers); - //Debug.Log($"AGV Result: {result}"); + //ULog.Debug($"AGV Result: {result}"); //MQTTService mqttService = new MQTTService("localhost", 1883); //mqttService.AddTopicHandler("AGV", (topic, message) => //{ - // Debug.Log($"Received message on topic {topic}: {message}"); + // ULog.Debug($"Received message on topic {topic}: {message}"); //}); //mqttService.Connect(); } diff --git a/Assets/Scripts/UVC/Data/DataMapper.cs b/Assets/Scripts/UVC/Data/DataMapper.cs index c4b8f272..ff8f6f0a 100644 --- a/Assets/Scripts/UVC/Data/DataMapper.cs +++ b/Assets/Scripts/UVC/Data/DataMapper.cs @@ -1,7 +1,12 @@ using Newtonsoft.Json.Linq; using System; +using System.Collections.Generic; +using System.IO; using System.Linq; using System.Threading.Tasks; +using Unity.VisualScripting.Antlr3.Runtime; +using UnityEngine; +using UVC.Log; namespace UVC.Data { @@ -42,8 +47,44 @@ namespace UVC.Data // 재귀 호출 제한하기 위한 설정 추가 private int maxRecursionDepth = 10; + /// + /// 재귀 최대 깊이를 설정하거나 가져옵니다. 이 값을 초과하는 중첩 객체는 간소화된 처리가 적용됩니다. + /// + public int MaxRecursionDepth + { + get => maxRecursionDepth; + set => maxRecursionDepth = Math.Max(1, value); // 최소 1 이상 보장 + } + + /// + /// 타입 변환을 위한 마스크 객체 + /// private DataMask mask; + /// + /// 병렬 처리를 적용할 배열의 최소 크기 + /// + private int parallelProcessingThreshold = 1000; + + /// + /// 병렬 처리를 적용할 배열의 최소 크기를 설정하거나 가져옵니다. + /// + public int ParallelProcessingThreshold + { + get => parallelProcessingThreshold; + set => parallelProcessingThreshold = Math.Max(10, value); // 최소 10 이상 보장 + } + + /// + /// 매핑 중 발생한 변환 오류를 추적하는 딕셔너리 + /// + private Dictionary conversionErrors; + + /// + /// 매핑 중 발생한 모든 변환 오류에 접근할 수 있는 읽기 전용 속성 + /// + public IReadOnlyDictionary ConversionErrors => conversionErrors; + /// /// 대용량 JSON 데이터를 스트리밍 방식으로 처리할지 여부를 나타내는 속성입니다. /// @@ -64,7 +105,8 @@ namespace UVC.Data /// public DataMapper(DataMask mask) { - this.mask = mask; + this.mask = mask ?? throw new ArgumentNullException(nameof(mask)); + this.conversionErrors = new Dictionary(); } /// @@ -76,11 +118,11 @@ namespace UVC.Data /// /// var mapper = new DataMapper(maskJson); /// DataObject result = mapper.Map(sourceJson); - /// Debug.Log(result["name"].ToString()); // "김철수" - /// Debug.Log(result["age"].ToObject()); // 30 + /// ULog.Debug(result["name"].ToString()); // "김철수" + /// ULog.Debug(result["age"].ToObject()); // 30 /// /// - public DataObject Mapping(JObject source) + public DataObject Map(JObject source) { return MapObject(source, mask); } @@ -114,9 +156,9 @@ namespace UVC.Data /// // result는 원본 배열과 동일한 구조의 DataArray /// /// - public DataArray Mapping(JArray source) + public DataArray Map(JArray source) { - JArray arr = new JArray(mask); + List arr = new List() { mask }; return MapArray(source, arr); } @@ -127,11 +169,20 @@ namespace UVC.Data /// 매핑된 DataObject public DataObject MapObjectStream(System.IO.Stream jsonStream) { + if (jsonStream == null) + throw new ArgumentNullException(nameof(jsonStream)); + + // 스트림 처리 최적화를 위해 청크 단위로 읽을 수 있지만, + // 현재는 Newtonsoft.Json의 기본 역직렬화 사용 using (var reader = new Newtonsoft.Json.JsonTextReader(new System.IO.StreamReader(jsonStream))) { + // 청크 읽기 설정 - 메모리 사용량 최적화 + reader.SupportMultipleContent = true; + var serializer = new Newtonsoft.Json.JsonSerializer(); var sourceObject = serializer.Deserialize(reader); - return Mapping(sourceObject); + + return Map(sourceObject); } } @@ -142,11 +193,18 @@ namespace UVC.Data /// 매핑된 DataObject public DataArray MapArrayStream(System.IO.Stream jsonStream) { + if (jsonStream == null) + throw new ArgumentNullException(nameof(jsonStream)); + using (var reader = new Newtonsoft.Json.JsonTextReader(new System.IO.StreamReader(jsonStream))) { + // 청크 읽기 설정 - 메모리 사용량 최적화 + reader.SupportMultipleContent = true; + var serializer = new Newtonsoft.Json.JsonSerializer(); var sourceArray = serializer.Deserialize(reader); - return Mapping(sourceArray); + + return Map(sourceArray); } } @@ -155,6 +213,8 @@ namespace UVC.Data /// /// 원본 JSON 객체 /// Mask JSON 객체 + /// 현재 재귀 깊이 + /// 현재 속성 경로(오류 추적용) /// 매핑된 DataObject 객체 /// /// 이 메서드는 중첩된 객체와 배열을 포함하여 JSON 구조를 재귀적으로 처리합니다. @@ -189,51 +249,123 @@ namespace UVC.Data /// // result는 sourceJson과 동일한 중첩 구조를 유지 /// /// - private DataObject MapObject(JObject sourceObject, DataMask maskObject, int depth = 0) + private DataObject MapObject(JObject sourceObject, DataMask maskObject, int depth = 0, string path = "") { - if (depth >= maxRecursionDepth) + if (maskObject == null) return new DataObject(sourceObject); + + // Mask가 비어있으면 원본 객체를 그대로 사용, 깊이 제한에 도달하면 간소화된 처리 + if (maskObject.Count == 0 || depth >= maxRecursionDepth) { - // 깊이 제한에 도달하면 간소화된 처리 - return new DataObject(sourceObject) { IdKey = maskObject.ObjectIdKey, Name = maskObject.ObjectName }; + DataObject dObj = new DataObject() { IdKey = maskObject.ObjectIdKey, Name = maskObject.ObjectName }; + // 속성 이름 변환 처리 + foreach (var property in sourceObject.Properties()) + { + string propertyName = property.Name; + if (maskObject.NamesForReplace != null && maskObject.NamesForReplace.ContainsKey(propertyName)) + { + propertyName = maskObject.NamesForReplace[propertyName]; + } + dObj[propertyName] = ConvertJTokenToObject(property.Value); + } + return dObj; } DataObject target = new DataObject() { IdKey = maskObject.ObjectIdKey, Name = maskObject.ObjectName }; + foreach (var property in sourceObject.Properties()) { - if (maskObject.ContainsKey(property.Name)) + string propertyName = property.Name; + string currentPath = string.IsNullOrEmpty(path) ? propertyName : $"{path}.{propertyName}"; + + if (maskObject.ContainsKey(propertyName)) { - JToken maskValue = maskObject[property.Name]; + object maskValue = maskObject[propertyName]; JToken sourceValue = property.Value; - // 중첩된 객체 처리 - if (sourceValue.Type == JTokenType.Object && maskValue.Type == JTokenType.Object) + // 속성 이름 변환 처리 + if (maskObject.NamesForReplace != null && maskObject.NamesForReplace.ContainsKey(propertyName)) { - target[property.Name] = MapObject((JObject)sourceValue, (DataMask)maskValue, depth + 1); + propertyName = maskObject.NamesForReplace[propertyName]; } - // 중첩된 배열 처리 - else if (sourceValue.Type == JTokenType.Array && maskValue.Type == JTokenType.Array) + + try { - var arr = MapArray((JArray)sourceValue, (JArray)maskValue); - target[property.Name] = arr; - } - else - { - string propertyName = property.Name; - if(maskObject.NamesForReplace != null && maskObject.NamesForReplace.ContainsKey(propertyName)) + // 중첩된 객체 처리 + if (sourceValue.Type == JTokenType.Object && maskValue is DataMask maskSubObject) { - propertyName = maskObject.NamesForReplace[propertyName]; + target[propertyName] = MapObject((JObject)sourceValue, maskSubObject, depth + 1, currentPath); } - MapProperty(propertyName, sourceValue, maskValue, target); + // 중첩된 배열 처리 + else if (sourceValue.Type == JTokenType.Array && maskValue is List maskArrayTemplate) + { + var arr = MapArray((JArray)sourceValue, maskArrayTemplate, currentPath); + target[propertyName] = arr; + } + // 일반 속성 처리 + else + { + MapProperty(propertyName, sourceValue, maskValue, target, currentPath); + } + } + catch (Exception ex) + { + // 예외 정보 저장 후 기본값 설정 + conversionErrors[currentPath] = ex; + ULog.Warning($"매핑 중 오류 발생 - 경로: {currentPath}", ex); + + // 마스크 값 타입에 맞는 기본값 설정 + SetDefaultValueForProperty(propertyName, maskValue, target); } } else { - continue; // Mask에 없는 속성은 무시 + // Mask에 없는 속성은 무시 (필요시 설정을 통해 포함 가능) + continue; } } + return target; } + /// + /// 예외 발생 시 마스크 값 타입에 맞는 기본값을 설정합니다. + /// + private void SetDefaultValueForProperty(string propertyName, object maskValue, DataObject target) + { + if (maskValue is string) + { + target[propertyName] = string.Empty; + } + else if (maskValue is int || maskValue is long || maskValue is short || maskValue is byte) + { + target[propertyName] = 0; + } + else if (maskValue is double || maskValue is float || maskValue is decimal) + { + target[propertyName] = 0.0; + } + else if (maskValue is bool) + { + target[propertyName] = false; + } + else if (maskValue is DateTime) + { + target[propertyName] = null; + } + else if (maskValue is DataMask) + { + target[propertyName] = new DataObject(); + } + else if (maskValue is List) + { + target[propertyName] = new DataArray(); + } + else + { + target[propertyName] = null; + } + } + /// /// JToken을 실제 객체로 변환하는 헬퍼 메서드 /// @@ -245,6 +377,8 @@ namespace UVC.Data /// private object ConvertJTokenToObject(JToken token) { + if (token == null) return null; + switch (token.Type) { case JTokenType.Object: @@ -253,9 +387,9 @@ namespace UVC.Data JArray array = (JArray)token; if (array.All(item => item.Type == JTokenType.Object)) { - return new DataArray((JArray)token); + return new DataArray(array); } - return array.ToObject(); + return array.ToObject(); case JTokenType.Integer: return token.ToObject(); case JTokenType.Float: @@ -278,6 +412,7 @@ namespace UVC.Data /// /// 원본 JSON 배열 /// Mask JSON 배열 + /// 현재 속성 경로(오류 추적용) /// 매핑된 DataArray 객체 /// /// Mask 배열이 비어있으면, 원본 배열의 각 항목을 DataObject로 변환하여 복사합니다. @@ -309,99 +444,209 @@ namespace UVC.Data /// // result.contacts는 원본 배열과 동일한 구조의 DataArray /// /// - private DataArray MapArray(JArray sourceArray, JArray maskArray) + private DataArray MapArray(JArray sourceArray, List maskTemplates, string path = "") { // 빠른 초기 크기 할당으로 재할당 방지 DataArray targetArray = new DataArray(sourceArray.Count); // 특정 크기 이상일 경우 병렬 처리 적용 - if (sourceArray.Count > 1000 && maskArray.Count <= 1) + if (sourceArray.Count > parallelProcessingThreshold && maskTemplates.Count <= 1) { - var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount }; - - // 배열을 병렬로 처리하되, 순서 유지 - var results = new DataObject[sourceArray.Count]; - - Parallel.For(0, sourceArray.Count, parallelOptions, i => - { - var sourceItem = sourceArray[i]; - var maskTemplate = maskArray.Count > 0 ? maskArray[0] : null; - - if (sourceItem.Type == JTokenType.Object && maskTemplate != null && maskTemplate.Type == JTokenType.Object) - { - results[i] = MapObject((JObject)sourceItem, (DataMask)maskTemplate); - } - else - { - // 단순 값 처리... - var dataObject = new DataObject(); - dataObject.Add("value", ConvertJTokenToObject(sourceItem)); - results[i] = dataObject; - } - }); - - // 결과를 순서대로 추가 - targetArray.AddRange(results); - return targetArray; + return MapArrayInParallel(sourceArray, maskTemplates, path); } // Mask 배열이 비어있으면 원본 배열을 그대로 사용 - if (maskArray.Count == 0) + if (maskTemplates == null || maskTemplates.Count == 0) { - foreach (JToken sourceItem in sourceArray) + for (int i = 0; i < sourceArray.Count; i++) + { + JToken sourceItem = sourceArray[i]; + string itemPath = $"{path}[{i}]"; + + try + { + if (sourceItem.Type == JTokenType.Object) + { + targetArray.Add(new DataObject((JObject)sourceItem)); + } + else + { + // DataObject가 아닌 경우, 새 DataObject를 만들고 값을 넣어줍니다 + var dataObject = new DataObject(); + dataObject.Add("value", ConvertJTokenToObject(sourceItem)); + targetArray.Add(dataObject); + } + } + catch (Exception ex) + { + conversionErrors[itemPath] = ex; + ULog.Warning($"배열 항목 변환 오류 - 경로: {itemPath}", ex); + + // 오류 발생 시 빈 객체 추가 + targetArray.Add(new DataObject()); + } + } + return targetArray; + } + + // Mask 템플릿 사용 - 반복 사용 혹은 일치 사용 + for (int i = 0; i < sourceArray.Count; i++) + { + JToken sourceItem = sourceArray[i]; + string itemPath = $"{path}[{i}]"; + + // 현재 인덱스에 맞는 마스크 템플릿 선택 (또는 첫 번째 템플릿 재사용) + DataMask maskTemplate; + if (maskTemplates.Count > 1) + { + // maskTemplates.Count > 1이면서 소스 항목 개수 보다 적은 경우 + // maskTemplates.Count 보다 큰 인덱스는 무시 + // 예: maskTemplates.Count가 3이고, sourceArray.Count가 5인 경우 + // 0, 1, 2 인덱스는 maskTemplates[0], maskTemplates[1], maskTemplates[2] 사용 + // 3, 4 인덱스는 저장 않함 + if( i >= maskTemplates.Count) + continue; + int templateIndex = Math.Min(i, maskTemplates.Count - 1); + maskTemplate = maskTemplates[templateIndex]; + } + else + { + // 단일 템플릿 반복 사용 + maskTemplate = maskTemplates[0]; + } + + try { if (sourceItem.Type == JTokenType.Object) { - targetArray.Add(new DataObject((JObject)sourceItem)); + JObject sourceItem2 = (JObject)sourceItem; + targetArray.Add(MapObject(sourceItem2, maskTemplate, 0, itemPath)); + } + else if (sourceItem.Type == JTokenType.Array) + { + // DataArray는 DataObject만 담을 수 있으므로, 배열을 처리하는 방식을 변경해야 함 + var nestedArray = MapArray((JArray)sourceItem, maskTemplates, itemPath); + var container = new DataObject(); + container.Add("items", nestedArray); + targetArray.Add(container); } else { - // DataObject가 아닌 경우, 새 DataObject를 만들고 값을 넣어줍니다 + // 단순 값을 DataObject로 래핑 var dataObject = new DataObject(); dataObject.Add("value", ConvertJTokenToObject(sourceItem)); targetArray.Add(dataObject); } } - return targetArray; + catch (Exception ex) + { + conversionErrors[itemPath] = ex; + ULog.Warning($"배열 항목 변환 오류 - 경로: {itemPath}", ex); + + // 오류 발생 시 빈 객체 추가 + targetArray.Add(new DataObject()); + } } - // Mask 배열 개수가 1개면 1개만 템플릿으로 사용 - JToken maskTemplate = maskArray.First; - int idx = 0; - foreach (JToken sourceItem in sourceArray) - { - if (maskArray.Count > 1) - { - if (idx > 0 && idx < maskArray.Count) - { - maskTemplate = maskArray[idx]; - } - else if (idx >= maskArray.Count) - { - continue; // Mask 배열의 범위를 벗어나면 무시 - } - } + return targetArray; + } - if (sourceItem.Type == JTokenType.Object && maskTemplate.Type == JTokenType.Object) + /// + /// 대형 배열을 병렬로 처리하는 배열 매핑 메서드 + /// + private DataArray MapArrayInParallel(JArray sourceArray, List maskTemplates, string path) + { + DataArray targetArray = new DataArray(sourceArray.Count); + DataMask maskTemplate = maskTemplates.Count > 0 ? maskTemplates[0] : new DataMask(); + + var parallelOptions = new ParallelOptions + { + MaxDegreeOfParallelism = Environment.ProcessorCount + }; + + // 결과를 담을 배열 (순서 보존을 위해) + var results = new DataObject[sourceArray.Count]; + + try + { + Parallel.For(0, sourceArray.Count, parallelOptions, i => { - targetArray.Add(MapObject((JObject)sourceItem, (DataMask)maskTemplate)); - } - else if (sourceItem.Type == JTokenType.Array && maskTemplate.Type == JTokenType.Array) + var sourceItem = sourceArray[i]; + string itemPath = $"{path}[{i}]"; + + try + { + if (sourceItem.Type == JTokenType.Object && maskTemplate != null) + { + results[i] = MapObject((JObject)sourceItem, maskTemplate, 0, itemPath); + } + else + { + // 단순 값 처리 + var dataObject = new DataObject(); + dataObject.Add("value", ConvertJTokenToObject(sourceItem)); + results[i] = dataObject; + } + } + catch (Exception ex) + { + lock (conversionErrors) + { + conversionErrors[itemPath] = ex; + } + ULog.Warning($"병렬 배열 처리 오류 - 경로: {itemPath}", ex); + + // 오류 발생 시 빈 객체 생성 + results[i] = new DataObject(); + } + }); + + // 결과를 순서대로 추가 + targetArray.AddRange(results); + } + catch (Exception ex) + { + ULog.Error($"병렬 배열 매핑 중 치명적 오류 발생: {ex.Message}", ex); + + // 병렬 처리 실패 시 일반 순차 처리로 대체 + return MapArraySequential(sourceArray, maskTemplates, path); + } + + return targetArray; + } + + /// + /// 병렬 처리가 실패할 경우 대체 사용할 순차적 배열 처리 메서드 + /// + private DataArray MapArraySequential(JArray sourceArray, List maskTemplates, string path) + { + DataArray targetArray = new DataArray(sourceArray.Count); + DataMask maskTemplate = maskTemplates.Count > 0 ? maskTemplates[0] : new DataMask(); + + for (int i = 0; i < sourceArray.Count; i++) + { + JToken sourceItem = sourceArray[i]; + string itemPath = $"{path}[{i}]"; + + try { - // DataArray는 DataObject만 담을 수 있으므로, 배열을 처리하는 방식을 변경해야 함 - var nestedArray = MapArray((JArray)sourceItem, (JArray)maskTemplate); - var container = new DataObject(); - container.Add("items", nestedArray); - targetArray.Add(container); + if (sourceItem.Type == JTokenType.Object) + { + targetArray.Add(MapObject((JObject)sourceItem, maskTemplate, 0, itemPath)); + } + else + { + var dataObject = new DataObject(); + dataObject.Add("value", ConvertJTokenToObject(sourceItem)); + targetArray.Add(dataObject); + } } - else + catch (Exception ex) { - // 단순 값을 DataObject로 래핑 - var dataObject = new DataObject(); - dataObject.Add("value", ConvertJTokenToObject(sourceItem)); - targetArray.Add(dataObject); + conversionErrors[itemPath] = ex; + ULog.Warning($"순차 배열 처리 오류 - 경로: {itemPath}", ex); + targetArray.Add(new DataObject()); } - idx++; } return targetArray; @@ -414,17 +659,20 @@ namespace UVC.Data /// 매핑할 원본 값 /// 타입을 결정하는 Mask 값 /// 값을 추가할 대상 DataObject + /// 현재 속성 경로(오류 추적용) /// /// 이 메서드는 Mask 값의 타입에 따라 원본 값을 적절한 타입으로 변환합니다. /// /// 지원되는 타입: - /// - 문자열 (JTokenType.String) - /// - 정수 (JTokenType.Integer) - /// - 실수 (JTokenType.Float) - /// - 불리언 (JTokenType.Boolean) - /// - 날짜/시간 (JTokenType.Date) + /// - 문자열 (string) + /// - 정수 (int) + /// - 실수 (double, float) + /// - 불리언 (bool) + /// - 날짜/시간 (DateTime) /// - 열거형 (Enum) /// - DataMap (문자열 매핑 딕셔너리) + /// - DataMask (중첩된 마스크 객체) + /// - List (배열 마스크) /// /// 타입 변환이 불가능한 경우 원본 값이 그대로 사용됩니다. /// @@ -456,17 +704,340 @@ namespace UVC.Data /// // result에는 모든 속성이 적절한 타입으로 변환됨 /// /// - private void MapProperty(string propertyName, JToken sourceValue, JToken maskValue, DataObject target) + private void MapProperty(string propertyName, JToken sourceValue, object maskValue, DataObject target, string path) { // 소스 값이 널이면 바로 널 설정 - if (sourceValue.Type == JTokenType.Null) + if (sourceValue == null || sourceValue.Type == JTokenType.Null) { target[propertyName] = null; return; } - if (sourceValue.Type == maskValue.Type) + // 타입별 매핑 처리를 분리된 메서드로 호출 + if (maskValue is string) + { + MapStringProperty(propertyName, sourceValue, target); + } + else if (maskValue is int || maskValue is long || maskValue is short || maskValue is byte) + { + MapIntegerProperty(propertyName, sourceValue, target); + } + else if (maskValue is double || maskValue is float || maskValue is decimal) + { + MapFloatingPointProperty(propertyName, sourceValue, target); + } + else if (maskValue is bool) + { + MapBooleanProperty(propertyName, sourceValue, target); + } + else if (maskValue is DateTime) + { + MapDateTimeProperty(propertyName, sourceValue, target); + } + else if (maskValue is Enum || (maskValue?.GetType()?.IsEnum ?? false)) + { + MapEnumProperty(propertyName, sourceValue, maskValue, target); + } + else if (maskValue is Dictionary dicObj && dicObj.GetType() == typeof(DataMask)) + { + MapNestedObjectProperty(propertyName, sourceValue, (DataMask)maskValue, target, path); + } + else if (maskValue is List) + { + MapNestedArrayProperty(propertyName, sourceValue, (List)maskValue, target, path); + } + else if (maskValue is Dictionary || maskValue is DataMap) + { + MapDataMapProperty(propertyName, sourceValue, maskValue, target); + } + else if (maskValue is JToken jToken) + { + MapJTokenProperty(propertyName, sourceValue, jToken, target); + } + else + { + // 기타 타입은 그대로 객체로 변환 + target[propertyName] = ConvertJTokenToObject(sourceValue); + } + } + + #region 타입별 속성 매핑 메서드 + + /// + /// 문자열 타입 속성 매핑 + /// + private void MapStringProperty(string propertyName, JToken sourceValue, DataObject target) + { + if (sourceValue.Type == JTokenType.String || sourceValue.Type == JTokenType.Integer || + sourceValue.Type == JTokenType.Float || sourceValue.Type == JTokenType.Boolean) + { + target[propertyName] = sourceValue.ToObject(); + } + else + { + target[propertyName] = sourceValue.ToString(); + } + } + + /// + /// 정수 타입 속성 매핑 + /// + private void MapIntegerProperty(string propertyName, JToken sourceValue, DataObject target) + { + if (sourceValue.Type == JTokenType.Integer || sourceValue.Type == JTokenType.Float || + sourceValue.Type == JTokenType.String) + { + try + { + // 명시적으로 값 범위 검사를 통해 오버플로우 방지 + var doubleValue = sourceValue.ToObject(); + if (doubleValue > int.MaxValue || doubleValue < int.MinValue) + { + throw new OverflowException($"값 {doubleValue}가 Int32 범위를 벗어남"); + } + + target[propertyName] = Convert.ToInt32(doubleValue); + } + catch (Exception) + { + target[propertyName] = 0; // 변환 실패시 기본값 + } + } + else + { + target[propertyName] = 0; + } + } + + /// + /// 부동소수점 타입 속성 매핑 + /// + private void MapFloatingPointProperty(string propertyName, JToken sourceValue, DataObject target) + { + if (sourceValue.Type == JTokenType.Float || sourceValue.Type == JTokenType.Integer || + sourceValue.Type == JTokenType.String) + { + try + { + target[propertyName] = sourceValue.ToObject(); + } + catch + { + target[propertyName] = 0.0; // 변환 실패시 기본값 + } + } + else + { + target[propertyName] = 0.0; + } + } + + /// + /// 불리언 타입 속성 매핑 + /// + private void MapBooleanProperty(string propertyName, JToken sourceValue, DataObject target) + { + if (sourceValue.Type == JTokenType.Boolean) + { + target[propertyName] = sourceValue.ToObject(); + } + else if (sourceValue.Type == JTokenType.Integer) + { + // 0은 false, 나머지는 true로 처리 + target[propertyName] = sourceValue.ToObject() != 0; + } + else if (sourceValue.Type == JTokenType.String) + { + string strValue = sourceValue.ToString().ToLower(); + // 일반적인 참/거짓 문자열 처리 + if (strValue == "true" || strValue == "yes" || strValue == "y" || strValue == "1") + { + target[propertyName] = true; + } + else if (strValue == "false" || strValue == "no" || strValue == "n" || strValue == "0") + { + target[propertyName] = false; + } + else + { + // 나머지는 bool.Parse 시도 + try + { + target[propertyName] = bool.Parse(strValue); + } + catch + { + target[propertyName] = false; // 변환 실패시 기본값 + } + } + } + else + { + target[propertyName] = false; + } + } + + /// + /// DateTime 타입 속성 매핑 + /// + private void MapDateTimeProperty(string propertyName, JToken sourceValue, DataObject target) + { + if (sourceValue.Type == JTokenType.Date) + { + target[propertyName] = sourceValue.ToObject(); + } + else if (sourceValue.Type == JTokenType.String) + { + string dateStr = sourceValue.ToString(); + if (DateTime.TryParse(dateStr, out DateTime dateValue)) + { + target[propertyName] = dateValue; + } + else + { + target[propertyName] = null; + } + } + else + { + target[propertyName] = null; + } + } + + /// + /// 열거형 타입 속성 매핑 + /// + private void MapEnumProperty(string propertyName, JToken sourceValue, object maskValue, DataObject target) + { + try + { + Type enumType = maskValue.GetType(); + if (sourceValue.Type == JTokenType.String) + { + target[propertyName] = Enum.Parse(enumType, sourceValue.ToString(), true); + } + else if (sourceValue.Type == JTokenType.Integer) + { + target[propertyName] = Enum.ToObject(enumType, sourceValue.ToObject()); + } + else + { + target[propertyName] = maskValue; // 변환 불가능하면 마스크 기본값 사용 + } + } + catch + { + target[propertyName] = maskValue; // 변환 실패시 마스크 기본값 사용 + } + } + + /// + /// 중첩된 객체 타입 속성 매핑 + /// + private void MapNestedObjectProperty(string propertyName, JToken sourceValue, DataMask nestedMask, DataObject target, string path) + { + if (sourceValue.Type == JTokenType.Object) + { + target[propertyName] = MapObject((JObject)sourceValue, nestedMask, 0, $"{path}.{propertyName}"); + } + else if (sourceValue.Type == JTokenType.String) + { + // 문자열을 JSON으로 파싱 시도 + try + { + JObject jObj = JObject.Parse(sourceValue.ToString()); + target[propertyName] = MapObject(jObj, nestedMask, 0, $"{path}.{propertyName}"); + } + catch + { + target[propertyName] = sourceValue.ToString(); + } + } + else + { + target[propertyName] = ConvertJTokenToObject(sourceValue); + } + } + + /// + /// 중첩된 배열 타입 속성 매핑 + /// + private void MapNestedArrayProperty(string propertyName, JToken sourceValue, List maskTemplates, DataObject target, string path) + { + if (sourceValue.Type == JTokenType.Array) + { + target[propertyName] = MapArray((JArray)sourceValue, maskTemplates, $"{path}.{propertyName}"); + } + else if (sourceValue.Type == JTokenType.String) + { + // 문자열을 JSON 배열로 파싱 시도 + try + { + JArray jArr = JArray.Parse(sourceValue.ToString()); + target[propertyName] = MapArray(jArr, maskTemplates, $"{path}.{propertyName}"); + } + catch + { + // 배열 파싱 실패시 단일 항목 배열로 처리 + var dataObject = new DataObject(); + dataObject.Add("value", sourceValue.ToString()); + var array = new DataArray(1); + array.Add(dataObject); + target[propertyName] = array; + } + } + else + { + // 다른 타입은 단일 항목 배열로 변환 + var dataObject = new DataObject(); + dataObject.Add("value", ConvertJTokenToObject(sourceValue)); + var array = new DataArray(1); + array.Add(dataObject); + target[propertyName] = array; + } + } + + /// + /// DataMap 타입 속성 매핑 (문자열 매핑) + /// + private void MapDataMapProperty(string propertyName, JToken sourceValue, object maskValue, DataObject target) + { + Dictionary dataMap; + + if (maskValue is DataMap dm) + { + dataMap = dm; + } + else + { + dataMap = (Dictionary)maskValue; + } + + if (sourceValue.Type == JTokenType.String) + { + string strValue = sourceValue.ToString(); + if (dataMap.ContainsKey(strValue)) + { + target[propertyName] = dataMap[strValue]; + } + else + { + target[propertyName] = strValue; + } + } + else + { + target[propertyName] = sourceValue.ToObject(); + } + } + + /// + /// JToken 타입 속성 매핑 (이전 코드와의 호환성) + /// + private void MapJTokenProperty(string propertyName, JToken sourceValue, JToken jToken, DataObject target) + { + if (sourceValue.Type == jToken.Type) { switch (sourceValue.Type) { @@ -482,11 +1053,13 @@ namespace UVC.Data case JTokenType.Boolean: target[propertyName] = sourceValue.ToObject(); return; + case JTokenType.Date: + target[propertyName] = sourceValue.ToObject(); + return; } } - - if (maskValue.Type == JTokenType.Date && sourceValue.Type == JTokenType.String) + if (jToken.Type == JTokenType.Date && sourceValue.Type == JTokenType.String) { string dateStr = sourceValue.ToObject(); if (DateTime.TryParse(dateStr, out DateTime dateValue)) @@ -498,23 +1071,18 @@ namespace UVC.Data target[propertyName] = null; } } - else if (maskValue.ToObject()?.GetType()?.IsEnum == true && sourceValue.Type == JTokenType.String) - { - Type enumType = maskValue.ToObject().GetType(); - target[propertyName] = Enum.Parse(enumType, sourceValue.ToObject(), true); - } - else if (maskValue.Type == JTokenType.Object && sourceValue.Type == JTokenType.String) + else if (jToken.Type == JTokenType.Object && sourceValue.Type == JTokenType.String) { try { // 먼저 DataMap로 변환 시도 - var DataMap = maskValue.ToObject(); - if (DataMap != null) + var dataMap = jToken.ToObject(); + if (dataMap != null) { string strValue = sourceValue.ToObject(); - if (DataMap.ContainsKey(strValue)) + if (dataMap.ContainsKey(strValue)) { - target[propertyName] = DataMap[strValue]; + target[propertyName] = dataMap[strValue]; } else { @@ -540,6 +1108,8 @@ namespace UVC.Data } } + #endregion + } } diff --git a/Assets/Scripts/UVC/Data/DataMask.cs b/Assets/Scripts/UVC/Data/DataMask.cs index f44ebb3f..2ebd3576 100644 --- a/Assets/Scripts/UVC/Data/DataMask.cs +++ b/Assets/Scripts/UVC/Data/DataMask.cs @@ -1,13 +1,16 @@ #nullable enable using Newtonsoft.Json.Linq; +using System; using System.Collections.Generic; +using System.Linq; +using Unity.VisualScripting.Antlr3.Runtime; namespace UVC.Data { /// /// JSON 데이터의 구조와 변환 규칙을 정의하는 마스크 클래스입니다. - /// Newtonsoft.Json.Linq.JObject를 상속하여 JSON 데이터 구조를 표현하고, + /// Dictionary를 상속하여 JSON 데이터 구조를 표현하고, /// 데이터 매핑, 필드 이름 변환, 그리고 타입 변환을 위한 메타데이터를 제공합니다. /// /// @@ -49,7 +52,7 @@ namespace UVC.Data /// // result는 변환된 필드 이름과 타입을 가진 DataObject 객체 /// /// - public class DataMask : JObject + public class DataMask : Dictionary { /// /// DataObject의 Id에 해당하는 key 문자열입니다. @@ -97,5 +100,224 @@ namespace UVC.Data /// public Dictionary? NamesForReplace { get; set; } + public DataMask() { } + + public DataMask(string jsonString) + { + if (string.IsNullOrEmpty(jsonString)) return; + JObject jObj = JObject.Parse(jsonString); + foreach (var property in jObj.Properties()) + { + this[property.Name] = ConvertJTokenToObject(property); + } + } + + public DataMask(JObject jObj) + { + if (jObj == null) return; + foreach (var property in jObj.Properties()) + { + this[property.Name] = ConvertJTokenToObject(property.Value); + } + } + + /// + /// JToken을 실제 객체로 변환하는 헬퍼 메서드 + /// + /// 변환할 JToken 객체 + /// 변환된 .NET 객체 + /// + /// 이 메서드는 JToken의 타입에 따라 적절한 .NET 타입의 객체로 변환합니다. + /// 객체는 DataObject로, 배열은 DataArray로 변환되며, 기본 타입은 해당하는 .NET 타입으로 변환됩니다. + /// + private object ConvertJTokenToObject(JToken token) + { + if (token == null) return null; + + switch (token.Type) + { + case JTokenType.Property: + JProperty prop = (JProperty)token; + return ConvertJTokenToObject(prop.Value); + case JTokenType.Object: + return new DataMask((JObject)token); + case JTokenType.Array: + JArray array = (JArray)token; + List dataMasks = new List(); + if (array.All(item => item.Type == JTokenType.Object)) + { + foreach (var item in array) + { + dataMasks.Add(new DataMask((JObject)item)); + } + } + return dataMasks; + case JTokenType.Integer: + return token.ToObject(); + case JTokenType.Float: + return token.ToObject(); + case JTokenType.String: + return token.ToObject(); + case JTokenType.Boolean: + return token.ToObject(); + case JTokenType.Date: + return token.ToObject(); + case JTokenType.Null: + return null; + default: + return token.ToObject(); + } + } + + /// + /// JObject 타입과의 호환성을 위한 메서드입니다. + /// Dictionary에 저장된 모든 속성을 JObject 형식으로 반환합니다. + /// + /// 현재 DataMask의 내용을 담은 JObject + public JObject ToJObject() + { + JObject result = new JObject(); + foreach (var pair in this) + { + if (pair.Value is DataMask) + { + result[pair.Key] = ((DataMask)pair.Value).ToJObject(); + } + else + { + result[pair.Key] = JToken.FromObject(pair.Value); + } + } + return result; + } + + /// + /// JObject에서 속성을 가져오기 위한 확장 메서드입니다. + /// + /// 속성을 추출할 JObject + /// JObject의 모든 속성 + private IEnumerable> GetProperties(JObject jObj) + { + foreach (var property in jObj.Properties()) + { + yield return new KeyValuePair(property.Name, property.Value); + } + } + + /// + /// 다른 DataMask 객체의 속성을 현재 객체로 복사합니다. + /// + /// 복사할 원본 DataMask 객체 + /// true인 경우 메타데이터 속성만 복사하고 데이터 필드는 복사하지 않음 + /// + /// maskPropertyOnly가 true인 경우 ObjectIdKey, ObjectName, NamesForReplace와 같은 + /// 메타데이터 속성만 복사되고, 실제 데이터 필드(Dictionary의 요소)는 복사되지 않습니다. + /// false인 경우 모든 속성과 데이터 필드가 복사됩니다. + /// + /// + /// + /// var sourceMask = new DataMask(); + /// sourceMask.ObjectIdKey = "id"; + /// sourceMask["name"] = ""; + /// sourceMask["age"] = 0; + /// + /// var targetMask = new DataMask(); + /// // 모든 속성 복사 + /// targetMask.CopyFrom(sourceMask); + /// + /// // 메타데이터만 복사 + /// var metaOnlyMask = new DataMask(); + /// metaOnlyMask.CopyFrom(sourceMask, true); + /// + /// + public void CopyFrom(DataMask other, bool maskPropertyOnly = false) + { + if (other == null) return; + this.ObjectIdKey = other.ObjectIdKey; + this.ObjectName = other.ObjectName; + this.NamesForReplace = other.NamesForReplace != null ? new Dictionary(other.NamesForReplace) : null; + if (maskPropertyOnly) return; + foreach (var pair in other) + { + this[pair.Key] = pair.Value; + } + } + + /// + /// JObject의 속성을 현재 DataMask 객체로 복사합니다. + /// + /// 복사할 원본 JObject + /// + /// 이 메서드는 JObject의 각 속성을 적절한 .NET 객체로 변환하여 + /// DataMask에 추가합니다. JObject에서 데이터를 가져오는 데 유용합니다. + /// + /// + /// + /// var jsonObject = JObject.Parse(@"{ + /// ""name"": ""김철수"", + /// ""age"": 30, + /// ""isActive"": true + /// }"); + /// + /// var mask = new DataMask(); + /// mask.CopyFrom(jsonObject); + /// // mask에는 jsonObject의 모든 속성이 복사됨 + /// + /// + public void CopyFrom(JObject other) + { + if (other == null) return; + foreach (var property in other.Properties()) + { + this[property.Name] = property.Value.ToObject(); + } + } + + /// + /// 현재 DataMask 객체의 깊은 복사본을 생성합니다. + /// + /// 현재 객체의 모든 값과 메타데이터를 포함한 새로운 DataMask 객체 + /// + /// 이 메서드는 현재 DataMask의 모든 속성(ObjectIdKey, ObjectName, NamesForReplace)과 + /// 모든 데이터 필드를 새 DataMask 인스턴스에 복사합니다. 복사된 객체는 원본과 + /// 독립적으로 수정할 수 있습니다. + /// + /// + /// + /// var originalMask = new DataMask(); + /// originalMask.ObjectIdKey = "id"; + /// originalMask.ObjectName = "users"; + /// originalMask["name"] = ""; + /// originalMask["age"] = 0; + /// + /// // 깊은 복사 생성 + /// var copiedMask = originalMask.DeepClone(); + /// + /// // 원본에 영향 없이 복사본 수정 가능 + /// copiedMask.ObjectName = "employees"; + /// copiedMask["salary"] = 0.0; + /// + /// + public DataMask DeepClone() + { + DataMask mask = new DataMask(); + mask.ObjectIdKey = ObjectIdKey; + mask.ObjectName = ObjectName; + mask.NamesForReplace = NamesForReplace != null ? new Dictionary(NamesForReplace) : null; + foreach (var pair in this) + { + mask[pair.Key] = pair.Value; + } + return mask; + } + + /// + /// Json 문자열로 변환합니다. + /// + /// + public string ToJsonString() + { + return ToJObject().ToString(Newtonsoft.Json.Formatting.Indented); + } } -} +} \ No newline at end of file diff --git a/Assets/Scripts/UVC/Data/DataObject.cs b/Assets/Scripts/UVC/Data/DataObject.cs index 2e7bf746..d59f1650 100644 --- a/Assets/Scripts/UVC/Data/DataObject.cs +++ b/Assets/Scripts/UVC/Data/DataObject.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; +using UVC.Log; namespace UVC.Data { @@ -393,7 +394,25 @@ namespace UVC.Data return new DataObject(jObject); if (value is Dictionary dict) return new DataObject(dict); + } + + // propertyName으로 직접 객체를 찾지 못했거나, 찾았지만 적절한 타입이 아닌 경우, + // 현재 DataObject의 값들 중 DataObject 타입인 것들을 순회하며 내부에서 propertyName을 다시 검색합니다. + foreach (KeyValuePair keyValue in this) + { + if (keyValue.Value is DataObject nestedDataObject) + { + // 중첩된 DataObject에서 propertyName을 검색합니다. + // 여기서 defaultValue로 null을 전달하여, 이 탐색 단계에서 찾지 못하면 + // 외부 호출의 defaultValue가 사용되도록 합니다. + DataObject? foundInNested = nestedDataObject.GetDataObject(propertyName, null); + if (foundInNested != null) + { + return foundInNested; // 중첩된 객체에서 찾았으면 반환 + } + } } + return defaultValue; } diff --git a/Assets/Scripts/UVC/Data/DataRepository.cs b/Assets/Scripts/UVC/Data/DataRepository.cs index 96cd3a68..37aa5912 100644 --- a/Assets/Scripts/UVC/Data/DataRepository.cs +++ b/Assets/Scripts/UVC/Data/DataRepository.cs @@ -3,6 +3,7 @@ using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using UnityEngine; +using UVC.Log; namespace UVC.Data { @@ -390,7 +391,7 @@ namespace UVC.Data } catch (JsonException ex) { - Debug.LogError($"JSON 파싱 오류: {ex.Message}"); + ULog.Error($"JSON 파싱 오류", ex); } } diff --git a/Assets/Scripts/UVC/Data/HttpPipeLine.cs b/Assets/Scripts/UVC/Data/HttpPipeLine.cs index c685c03f..a04a9c08 100644 --- a/Assets/Scripts/UVC/Data/HttpPipeLine.cs +++ b/Assets/Scripts/UVC/Data/HttpPipeLine.cs @@ -9,6 +9,7 @@ using System.IO; using System.Text; using System.Threading; using UnityEngine; +using UVC.Log; using UVC.Network; using UVC.Tests; @@ -47,7 +48,7 @@ namespace UVC.Data /// .setDataMapper(dataMapper) /// .setHandler(data => { /// // 데이터 처리 로직 - /// Debug.Log($"데이터 수신: {data?.ToString() ?? "null"}"); + /// ULog.Debug($"데이터 수신: {data?.ToString() ?? "null"}"); /// }); /// httpPipeline.Add("dataRequest", singleRequest); /// @@ -56,7 +57,7 @@ namespace UVC.Data /// .setDataMapper(dataMapper) /// .setHandler(data => { /// // 상태 데이터 처리 - /// Debug.Log($"상태 업데이트: {data?.ToString() ?? "null"}"); + /// ULog.Debug($"상태 업데이트: {data?.ToString() ?? "null"}"); /// }) /// .setRepeat(true, 0, 5000); // 5초마다 무한 반복 /// httpPipeline.Add("statusMonitor", repeatingRequest); @@ -245,7 +246,7 @@ namespace UVC.Data else { JObject source = JObject.Parse(result); - dataObject = info.DataMapper.Mapping(source); + dataObject = info.DataMapper.Map(source); } } } @@ -263,14 +264,14 @@ namespace UVC.Data else { JArray source = JArray.Parse(result); - dataObject = info.DataMapper.Mapping(source); + dataObject = info.DataMapper.Map(source); } } } } catch (JsonException ex) { - Debug.LogError($"JSON parsing error in ExecuteSingle for {key}: {ex.Message}\nResponse: {result}"); + ULog.Error($"JSON parsing error in ExecuteSingle for {key}: {ex.Message}\nResponse: {result}", ex); throw; // 상위에서 처리하도록 다시 throw } } @@ -310,7 +311,7 @@ namespace UVC.Data { throw new OperationCanceledException("Operation cancelled", cancellationToken); } - Debug.LogWarning($"Request failed for '{key}', retry {retryCount}/{info.MaxRetryCount} after {info.RetryDelay}ms: {ex.Message}"); + ULog.Warning($"Request failed for '{key}', retry {retryCount}/{info.MaxRetryCount} after {info.RetryDelay}ms: {ex.Message}", ex); await UniTask.Delay(info.RetryDelay); } } @@ -324,7 +325,7 @@ namespace UVC.Data } // 모든 재시도 후에도 실패 - Debug.LogError($"Request failed for '{key}' after {info.MaxRetryCount} retries: {lastException?.Message}"); + ULog.Error($"Request failed for '{key}' after {info.MaxRetryCount} retries: {lastException?.Message}", lastException); throw lastException; } @@ -390,7 +391,7 @@ namespace UVC.Data catch (Exception ex) { // 다른 예외 처리 - Debug.LogError($"Error in repeat execution for '{key}': {ex.Message}"); + ULog.Error($"Error in repeat execution for '{key}': {ex.Message}", ex); if (cts.IsCancellationRequested) { break; @@ -494,7 +495,7 @@ namespace UVC.Data /// var activeRequests = httpPipeline.GetActiveRequests(); /// foreach (var request in activeRequests) /// { - /// Debug.Log($"요청 키: {request.Key}, 활성 상태: {request.Value.IsActive}, " + + /// ULog.Debug($"요청 키: {request.Key}, 활성 상태: {request.Value.IsActive}, " + /// $"반복 중: {request.Value.IsRepeating}, 반복 간격: {request.Value.RepeatInterval}ms"); /// } /// diff --git a/Assets/Scripts/UVC/Data/MQTTPipeLine.cs b/Assets/Scripts/UVC/Data/MQTTPipeLine.cs index 2bd62cdd..862b3777 100644 --- a/Assets/Scripts/UVC/Data/MQTTPipeLine.cs +++ b/Assets/Scripts/UVC/Data/MQTTPipeLine.cs @@ -141,12 +141,12 @@ namespace UVC.Data if (message.StartsWith("{")) { JObject source = JObject.Parse(message); - if (info.DataMapper != null) dataObject = info.DataMapper.Mapping(source); + if (info.DataMapper != null) dataObject = info.DataMapper.Map(source); } else if (message.StartsWith("[")) { JArray source = JArray.Parse(message); - if (info.DataMapper != null) dataObject = info.DataMapper.Mapping(source); + if (info.DataMapper != null) dataObject = info.DataMapper.Map(source); } } if (dataObject != null) dataObject = DataRepository.Instance.AddData(topic, dataObject, info.UpdatedDataOnly); diff --git a/Assets/Scripts/UVC/Extention/RenderTextureEx.cs b/Assets/Scripts/UVC/Extention/RenderTextureEx.cs index 41c7d1b1..9ec9e6a3 100644 --- a/Assets/Scripts/UVC/Extention/RenderTextureEx.cs +++ b/Assets/Scripts/UVC/Extention/RenderTextureEx.cs @@ -1,4 +1,5 @@ using UnityEngine; +using UVC.Log; namespace UVC.Extension { @@ -87,7 +88,7 @@ namespace UVC.Extension } catch (System.Exception e) { - Debug.LogError($"RenderTexture PNG ϴ ߽ϴ: {e.Message}"); + ULog.Error($"RenderTexture PNG ϴ ߽ϴ: {e.Message}", e); return false; } } diff --git a/Assets/Scripts/UVC/Extention/StringEx.cs b/Assets/Scripts/UVC/Extention/StringEx.cs index d3c2210d..ded5e48b 100644 --- a/Assets/Scripts/UVC/Extention/StringEx.cs +++ b/Assets/Scripts/UVC/Extention/StringEx.cs @@ -5,6 +5,7 @@ using System.Text; using System.Text.RegularExpressions; using UnityEngine; using UVC.Json; +using UVC.Log; namespace UVC.Extention { @@ -195,7 +196,7 @@ namespace UVC.Extention catch (Exception ex) { // 변환 실패 시 예외 정보 로깅 (선택적) - Debug.LogError($"JSON을 Dictionary로 변환하는 중 오류 발생: {ex.Message}"); + ULog.Error($"JSON을 Dictionary로 변환하는 중 오류 발생: {ex.Message}", ex); return new Dictionary(); } } diff --git a/Assets/Scripts/UVC/Log/ServerLog.cs b/Assets/Scripts/UVC/Log/ServerLog.cs index 81d3b810..211def4b 100644 --- a/Assets/Scripts/UVC/Log/ServerLog.cs +++ b/Assets/Scripts/UVC/Log/ServerLog.cs @@ -124,15 +124,16 @@ namespace UVC.Log if (db == null) { string dbPath = string.Empty; + // unity runtime 일때 if (Application.platform == RuntimePlatform.WindowsPlayer) { - string unityLogsPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"unityLogs"); if (!Directory.Exists(unityLogsPath)) Directory.CreateDirectory(unityLogsPath); dbPath = Path.Combine(unityLogsPath, @"unityLogs\serverLog.db"); } else { + // unity editor 일때 DirectoryInfo di = new DirectoryInfo(Application.dataPath).Parent; //di 하위에 Logs 디렉토리가 없으면 생성 DirectoryInfo di2 = new DirectoryInfo(Path.Combine(di.FullName, @"Logs")); diff --git a/Assets/Scripts/UVC/Log/ULog.cs b/Assets/Scripts/UVC/Log/ULog.cs new file mode 100644 index 00000000..ae88e113 --- /dev/null +++ b/Assets/Scripts/UVC/Log/ULog.cs @@ -0,0 +1,202 @@ +using log4net; +using log4net.Appender; +using log4net.Core; +using log4net.Filter; +using log4net.Layout; +using log4net.Repository.Hierarchy; +using System; +using System.Diagnostics; +using System.IO; +using UnityEngine; + +namespace UVC.Log +{ + public static class ULog + { + private static readonly ILog logger = LogManager.GetLogger(typeof(ULog)); + + private static readonly bool useUnityDebug = true; // Unity Debug 사용 여부 + + static ULog() + { + if (!useUnityDebug) + { + // unity runtime 일때 + if (Application.platform == RuntimePlatform.WindowsPlayer) + { + log4net.Config.XmlConfigurator.Configure(new System.IO.FileInfo(Path.Combine(Application.dataPath, @"Resources\log4net.runtime.xml"))); + } + else + { + log4net.Config.XmlConfigurator.Configure(new System.IO.FileInfo(Path.Combine(Application.dataPath, @"Resources\log4net.editor.xml"))); + //FileConfigure(); + } + } + } + + + public static void Debug(string msg) + { + if (logger.IsDebugEnabled) + { + if (useUnityDebug) + { + UnityEngine.Debug.Log(msg); + } + else + { + StackFrame frame = (new StackTrace(true)).GetFrame(1); + var method = frame.GetMethod(); + var type = method.DeclaringType; + var name = method.Name; + int lineNumber = frame.GetFileLineNumber(); + + logger.Debug(type.Name + "." + name + " [line " + lineNumber + "] " + msg); + } + } + } + + public static void Info(string msg) + { + if (logger.IsInfoEnabled) + { + if (useUnityDebug) + { + UnityEngine.Debug.Log(msg); + } + else + { + StackFrame frame = (new StackTrace(true)).GetFrame(1); + var method = frame.GetMethod(); + var type = method.DeclaringType; + var name = method.Name; + + int lineNumber = frame.GetFileLineNumber(); + logger.Info(type.Name + "." + name + " [line " + lineNumber + "] " + msg); + } + } + } + + + public static void Warning(string msg, Exception ex) + { + if (logger.IsWarnEnabled) + { + if (useUnityDebug) + { + UnityEngine.Debug.LogWarning(msg + ", " + ex); + } + else + { + StackFrame frame = (new StackTrace(true)).GetFrame(1); + var method = frame.GetMethod(); + var type = method.DeclaringType; + var name = method.Name; + int lineNumber = frame.GetFileLineNumber(); + + logger.Warn(type.Name + "." + name + " [line " + lineNumber + "] " + msg, ex); + } + } + } + + public static void Error(string msg, Exception ex) + { + if (logger.IsErrorEnabled) + { + if (useUnityDebug) + { + UnityEngine.Debug.LogError(msg + ", " + ex); + } + else + { + StackFrame frame = (new StackTrace(true)).GetFrame(1); + var method = frame.GetMethod(); + var type = method.DeclaringType; + var name = method.Name; + int lineNumber = frame.GetFileLineNumber(); + + logger.Error(type.Name + "." + name + " [line " + lineNumber + "] " + msg, ex); + } + } + } + + public static void Fatal(string msg, Exception ex) + { + if (logger.IsFatalEnabled) + { + if (useUnityDebug) + { + UnityEngine.Debug.LogError(msg + ", " + ex); + } + else + { + StackFrame frame = (new StackTrace(true)).GetFrame(1); + var method = frame.GetMethod(); + var type = method.DeclaringType; + var name = method.Name; + int lineNumber = frame.GetFileLineNumber(); + + logger.Fatal(type.Name + "." + name + " [line " + lineNumber + "] " + msg, ex); + } + } + } + + + /// + /// log4net 파일 설정을 구성합니다. + /// Assets/Resources/log4net.editor.xml log4net.runtime.xml 파일로 구성하였기에 사용안함. + /// + private static void FileConfigure() + { + try + { + var hierarchy = (Hierarchy)LogManager.GetRepository(); + + var patternLayout = new PatternLayout(); + patternLayout.ConversionPattern = "[%date][%thread][%level][%logger] %message%newline"; + patternLayout.ActivateOptions(); + + //var folder = new DirectoryInfo(Path.Combine(Application.streamingAssetsPath, "unityLogs")); + //if (!folder.Exists) folder.Create(); + + var fileAppender = new RollingFileAppender + { + File = Path.Combine(System.IO.Directory.GetCurrentDirectory(), @"Logs\"), + DatePattern = "yyyy-MM-dd'.log'", + AppendToFile = true, + RollingStyle = RollingFileAppender.RollingMode.Date, + StaticLogFileName = false, + Layout = patternLayout, + }; + fileAppender.AddFilter(new LevelRangeFilter() { LevelMin = Level.Info, LevelMax = Level.Fatal }); + fileAppender.ActivateOptions(); + + var fatalFileAppender = new RollingFileAppender + { + File = Path.Combine(System.IO.Directory.GetCurrentDirectory(), @"Logs\fatal.log"), + MaxSizeRollBackups = 100, + MaximumFileSize = "10MB", + AppendToFile = true, + RollingStyle = RollingFileAppender.RollingMode.Size, + StaticLogFileName = true, + Layout = patternLayout, + }; + fatalFileAppender.AddFilter(new LevelRangeFilter() { LevelMin = Level.Fatal, LevelMax = Level.Fatal }); + fatalFileAppender.ActivateOptions(); + + hierarchy.Root.AddAppender(fileAppender); + hierarchy.Root.AddAppender(fatalFileAppender); + + //var appender = new UnityDefaultLogAppender(); + //appender.Layout = patternLayout; + //hierarchy.Root.AddAppender(appender); + + hierarchy.Root.Level = Level.All; + hierarchy.Configured = true; + } + catch (Exception ex) { } + + + } + } +} diff --git a/Assets/Scripts/UVC/Log/ULog.cs.meta b/Assets/Scripts/UVC/Log/ULog.cs.meta new file mode 100644 index 00000000..15d8fa7b --- /dev/null +++ b/Assets/Scripts/UVC/Log/ULog.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: f9bdccce4e787f243b74ffe085472598 \ No newline at end of file diff --git a/Assets/Scripts/UVC/Network/HttpRequester.cs b/Assets/Scripts/UVC/Network/HttpRequester.cs index a75ccdd6..6a11edf4 100644 --- a/Assets/Scripts/UVC/Network/HttpRequester.cs +++ b/Assets/Scripts/UVC/Network/HttpRequester.cs @@ -407,7 +407,7 @@ namespace UVC.Network headerObject.Add(kvp.Key, kvp.Value); } } - //Debug.Log($"RequestWithDuration APIToken :{AuthService.Instance.Entiti.accessToken}"); + //ULog.Debug($"RequestWithDuration APIToken :{AuthService.Instance.Entiti.accessToken}"); //if (useAuth) request.SetHeader("access-token", AuthService.Instance.Entiti.accessToken); if (body != null) { @@ -472,7 +472,7 @@ namespace UVC.Network /// public static HTTPRequest Download(string url, string savePath, Action OnComplete, Action OnProgress = null, Action OnError = null) { - Debug.Log($"Download {url}"); + ULog.Debug($"Download {url}"); if (File.Exists(savePath)) { OnComplete.Invoke(); @@ -495,12 +495,12 @@ namespace UVC.Network } else { - Debug.Log($"Server error({resp.StatusCode} - {resp.Message})! " + new Exception(resp.Message)); + ULog.Debug($"Server error({resp.StatusCode} - {resp.Message})! " + new Exception(resp.Message)); OnError?.Invoke($"Server error({resp.StatusCode} - {resp.Message})!"); } break; default: - Debug.Log(req.State.ToString() + ", " + new Exception(resp.Message)); + ULog.Debug(req.State.ToString() + ", " + new Exception(resp.Message)); OnError?.Invoke(req.State.ToString()); break; } @@ -510,17 +510,17 @@ namespace UVC.Network request.DownloadSettings.OnDownloadStarted += async (HTTPRequest req, HTTPResponse resp, DownloadContentStream stream) => { - Debug.Log("Download Started"); + ULog.Debug("Download Started"); long len = await UniTask.RunOnThreadPool(() => ConsumeDownloadStream(savePath, stream as BlockingDownloadContentStream)); if (len == 0) OnError?.Invoke($"Download Fail!"); - Debug.Log($"Download finished!"); + ULog.Debug($"Download finished!"); }; request.DownloadSettings.OnDownloadProgress += (HTTPRequest req, long progress, long length) => { - Debug.Log($"Download Progress! progress:{progress} length:{length}"); + ULog.Debug($"Download Progress! progress:{progress} length:{length}"); OnProgress?.Invoke(progress, length); }; request.DownloadSettings.DownloadStreamFactory = (req, resp, bufferAvailableHandler) @@ -553,14 +553,14 @@ namespace UVC.Network { try { - //Debug.Log($"ConsumeDownloadStream blockingStream:{blockingStream == null}"); + //ULog.Debug($"ConsumeDownloadStream blockingStream:{blockingStream == null}"); while (blockingStream != null && !blockingStream.IsCompleted) { if (blockingStream.TryTake(out var buffer)) { try { - //Debug.Log($"ConsumeDownloadStream buffer.Data:{buffer.Data.Length}, Offset:{buffer.Offset}, Count:{buffer.Count}"); + //ULog.Debug($"ConsumeDownloadStream buffer.Data:{buffer.Data.Length}, Offset:{buffer.Offset}, Count:{buffer.Count}"); fs.Write(buffer.Data, buffer.Offset, buffer.Count); } finally diff --git a/Assets/Scripts/UVC/Network/MQTTService.cs b/Assets/Scripts/UVC/Network/MQTTService.cs index c4e253e6..f17f4936 100644 --- a/Assets/Scripts/UVC/Network/MQTTService.cs +++ b/Assets/Scripts/UVC/Network/MQTTService.cs @@ -67,7 +67,7 @@ namespace UVC.network /// private void CreateMQTTClient() { - Debug.Log($"MQTT Domain:{MQTTDomain} , MQTTPORT:{MQTTPort}"); + ULog.Debug($"MQTT Domain:{MQTTDomain} , MQTTPORT:{MQTTPort}"); var options = new ConnectionOptionsBuilder() .WithTCP(MQTTDomain, MQTTPort) .Build(); @@ -252,7 +252,7 @@ namespace UVC.network /// private void OnConnectedMQTT(MQTTClient client) { - Debug.Log($"MQTT OnConnected"); + ULog.Debug($"MQTT OnConnected"); BulkSubscribePacketBuilder builder = client.CreateBulkSubscriptionBuilder(); foreach (var topic in topicHandler.Keys) { @@ -269,7 +269,7 @@ namespace UVC.network /// 새 상태 private void OnStateChangedMQTT(MQTTClient client, ClientStates oldState, ClientStates newState) { - Debug.Log($"MQTT OnStateChanged {oldState} => {newState}"); + ULog.Debug($"MQTT OnStateChanged {oldState} => {newState}"); } /// @@ -284,7 +284,7 @@ namespace UVC.network private void OnDisconnectedMQTT(MQTTClient client, DisconnectReasonCodes code, string reason) { //ULog.Info($"MQTT Disconnected - code: {code}, reason: '{reason}'"); - Debug.Log($"MQTTDisconnected pcTime={DateTime.Now}"); + ULog.Debug($"MQTTDisconnected pcTime={DateTime.Now}"); // 지연 후에만 한 번 연결 if (Application.isPlaying && autoReconnect) { @@ -305,7 +305,7 @@ namespace UVC.network /// private void OnErrorMQTT(MQTTClient client, string reason) { - Debug.LogError($"MQTT OnError reason: '{reason}'"); + ULog.Error($"MQTT OnError reason: '{reason}'", new Exception(reason)); } /// @@ -321,7 +321,7 @@ namespace UVC.network private void OnTopic(MQTTClient client, SubscriptionTopic topic, string topicName, ApplicationMessage message) { string payload = Encoding.UTF8.GetString(message.Payload.Data, message.Payload.Offset, message.Payload.Count); - Debug.Log($"MQTT OnTopic {topic.Filter.OriginalFilter} => {payload}"); + ULog.Debug($"MQTT OnTopic {topic.Filter.OriginalFilter} => {payload}"); ServerLog.LogMqtt(MQTTDomain, MQTTPort.ToString(), topic.Filter.OriginalFilter, payload, DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ss.fffZ")); if(topicHandler.TryGetValue(topic.Filter.OriginalFilter, out var handler)) @@ -357,7 +357,7 @@ namespace UVC.network { if (client == null || client.State != ClientStates.Connected) { - Debug.LogError("MQTT client is not connected. Cannot publish message."); + ULog.Error("MQTT client is not connected. Cannot publish message.", new Exception("MQTT client is not connected. Cannot publish message.")); return; } client.CreateApplicationMessageBuilder(topic) diff --git a/Assets/Scripts/UVC/Tests/Data/DataMapperTests.cs b/Assets/Scripts/UVC/Tests/Data/DataMapperTests.cs index 241c23df..ce6a75d9 100644 --- a/Assets/Scripts/UVC/Tests/Data/DataMapperTests.cs +++ b/Assets/Scripts/UVC/Tests/Data/DataMapperTests.cs @@ -41,6 +41,10 @@ namespace UVC.Tests.Data RunTest(nameof(Map_ComplexNestedStructure_MapsCorrectly), Map_ComplexNestedStructure_MapsCorrectly); RunTest(nameof(Map_MixedArrayTypes_HandlesCorrectly), Map_MixedArrayTypes_HandlesCorrectly); RunTest(nameof(Map_WithNamesForReplace_ShouldRenameProperties), Map_WithNamesForReplace_ShouldRenameProperties); + RunTest(nameof(Map_DeepClone_CopiesAllProperties), Map_DeepClone_CopiesAllProperties); + RunTest(nameof(Map_ToJObject_ConvertsCorrectly), Map_ToJObject_ConvertsCorrectly); + RunTest(nameof(Map_ParallelArrayProcessing_WorksCorrectly), Map_ParallelArrayProcessing_WorksCorrectly); + RunTest(nameof(Map_RecursionDepthLimit_HandlesCorrectly), Map_RecursionDepthLimit_HandlesCorrectly); Debug.Log("===== DataMapper 테스트 완료 ====="); } @@ -80,7 +84,7 @@ namespace UVC.Tests.Data var mapper = new DataMapper(mask); // Act - var result = mapper.Mapping(source); + var result = mapper.Map(source); // Assert Assert.IsTrue(result.ContainsKey("Name")); @@ -104,7 +108,7 @@ namespace UVC.Tests.Data var mapper = new DataMapper(mask); // Act - var result = mapper.Mapping(source); + var result = mapper.Map(source); // Assert Assert.IsTrue(result.ContainsKey("Age")); @@ -128,7 +132,7 @@ namespace UVC.Tests.Data var mapper = new DataMapper(mask); // Act - var result = mapper.Mapping(source); + var result = mapper.Map(source); // Assert Assert.IsTrue(result.ContainsKey("Height")); @@ -151,7 +155,7 @@ namespace UVC.Tests.Data var mapper = new DataMapper(mask); // Act - var result = mapper.Mapping(source); + var result = mapper.Map(source); // Assert Assert.IsTrue(result.ContainsKey("IsActive")); @@ -175,7 +179,7 @@ namespace UVC.Tests.Data var mapper = new DataMapper(mask); // Act - var result = mapper.Mapping(source); + var result = mapper.Map(source); // Assert Assert.IsTrue(result.ContainsKey("BirthDate")); @@ -200,7 +204,7 @@ namespace UVC.Tests.Data var mapper = new DataMapper(mask); // Act - var result = mapper.Mapping(source); + var result = mapper.Map(source); // Assert Assert.IsTrue(result.ContainsKey("Status")); @@ -225,7 +229,7 @@ namespace UVC.Tests.Data var mapper = new DataMapper(mask); // Act - var result = mapper.Mapping(source); + var result = mapper.Map(source); // Assert Assert.IsTrue(result.ContainsKey("Status")); @@ -247,7 +251,7 @@ namespace UVC.Tests.Data var mapper = new DataMapper(mask); // Act - var result = mapper.Mapping(source); + var result = mapper.Map(source); // Assert Assert.IsTrue(result.ContainsKey("Status")); @@ -269,7 +273,7 @@ namespace UVC.Tests.Data var mapper = new DataMapper(mask); // Act - var result = mapper.Mapping(source); + var result = mapper.Map(source); // Assert Assert.IsFalse(result.ContainsKey("Email")); @@ -291,7 +295,7 @@ namespace UVC.Tests.Data var mapper = new DataMapper(mask); // Act - var result = mapper.Mapping(source); + var result = mapper.Map(source); // Assert Assert.IsTrue(result.ContainsKey("BirthDate")); @@ -327,7 +331,7 @@ namespace UVC.Tests.Data var mapper = new DataMapper(mask); // Act - var result = mapper.Mapping(source); + var result = mapper.Map(source); // Assert Assert.AreEqual(6, result.Count); @@ -374,7 +378,7 @@ namespace UVC.Tests.Data var mapper = new DataMapper(mask); // Act - var result = mapper.Mapping(source); + var result = mapper.Map(source); // Assert Assert.IsTrue(result.ContainsKey("User")); @@ -399,7 +403,7 @@ namespace UVC.Tests.Data // Arrange var mask = new DataMask { - ["Contacts"] = new JArray + ["Contacts"] = new List { new DataMask { @@ -419,7 +423,7 @@ namespace UVC.Tests.Data var mapper = new DataMapper(mask); // Act - var result = mapper.Mapping(source); + var result = mapper.Map(source); // Assert Assert.IsTrue(result.ContainsKey("Contacts")); @@ -448,7 +452,7 @@ namespace UVC.Tests.Data // Arrange var mask = new DataMask { - ["Tags"] = new JArray() + ["Tags"] = new List() }; var source = JObject.Parse(@"{ @@ -458,7 +462,7 @@ namespace UVC.Tests.Data var mapper = new DataMapper(mask); // Act - var result = mapper.Mapping(source); + var result = mapper.Map(source); // Assert Assert.IsTrue(result.ContainsKey("Tags")); @@ -472,7 +476,7 @@ namespace UVC.Tests.Data var item = tags[i]; Assert.IsNotNull(item); // DataObject는 "value" 키에 실제 값을 저장 - Assert.AreEqual(((JArray)source["Tags"])[i].ToString(), item.GetString("value")); + Assert.AreEqual((source["Tags"])[i].ToString(), item.GetString("value")); } } @@ -489,12 +493,12 @@ namespace UVC.Tests.Data { ["Name"] = "회사명", ["Founded"] = JToken.FromObject(DateTime.Now), - ["Departments"] = new JArray + ["Departments"] = new List { new DataMask { ["Name"] = "부서명", - ["Employees"] = new JArray + ["Employees"] = new List { new DataMask { @@ -535,7 +539,7 @@ namespace UVC.Tests.Data var mapper = new DataMapper(mask); // Act - var result = mapper.Mapping(source); + var result = mapper.Map(source); // Assert var company = result.GetDataObject("Company"); @@ -573,25 +577,15 @@ namespace UVC.Tests.Data public void Map_MixedArrayTypes_HandlesCorrectly() { // Arrange - var mask = new DataMask - { - ["MixedArray"] = new JArray - { - "문자열", - 123, - true, - new DataMask { ["Key"] = "Value" }, - new JArray{1, 1} - } - }; + var mask = new DataMask(@"{ + ""MixedArray"": [ + { ""Key"": ""Value"", ""Key1"": 1, ""Key2"": true}, + ] + }"); var source = JObject.Parse(@"{ ""MixedArray"": [ - ""테스트"", - 456, - false, - { ""Key"": ""NewValue"", ""Extra"": ""ExtraValue"" }, - [1, 2, 3], + { ""Key"": ""NewValue"", ""Key1"": 456, ""Key2"": false, ""Extra"": ""ExtraValue"" }, { ""items"": [1, 2, 3] } ] }"); @@ -599,40 +593,30 @@ namespace UVC.Tests.Data var mapper = new DataMapper(mask); // Act - var result = mapper.Mapping(source); + var result = mapper.Map(source); // Assert Assert.IsTrue(result.ContainsKey("MixedArray")); var mixedArray = result.GetDataArray("MixedArray"); Assert.IsNotNull(mixedArray); - Assert.AreEqual(5, mixedArray.Count); + Assert.AreEqual(2, mixedArray.Count); // 문자열 항목 검증 - Assert.AreEqual("테스트", mixedArray[0].GetString("value")); + Assert.AreEqual("NewValue", mixedArray[0].GetString("Key")); // 숫자 항목 검증 - Assert.AreEqual(456, mixedArray[1].GetInt("value")); + Assert.AreEqual(456, mixedArray[0].GetInt("Key1")); // 불리언 항목 검증 - Assert.AreEqual(false, mixedArray[2].GetBool("value")); + Assert.AreEqual(false, mixedArray[0].GetBool("Key2")); // 객체 항목 검증 - var objInArray = mixedArray[3]; + var objInArray = mixedArray[0]; Assert.IsNotNull(objInArray); Assert.AreEqual("NewValue", objInArray.GetString("Key")); - - // 중첩 배열 항목 검증 - var nestedItem = mixedArray[4]; - Assert.IsNotNull(nestedItem); - // DataMapper는 중첩 배열을 "items" 키를 가진 DataObject로 포장합니다 - var nestedArray = nestedItem.GetDataArray("items"); - Assert.IsNotNull(nestedArray); - Assert.AreEqual(2, nestedArray.Count); - Assert.AreEqual(1, nestedArray[0].GetInt("value")); - Assert.AreEqual(2, nestedArray[1].GetInt("value")); } /// - /// NamesForReplace 속성이 제대로 구현되었을 때 예상되는 동작을 테스트합니다. + /// NamesForReplace 속성이 제대로 구현되었는지 테스트합니다. /// [Test] public void Map_WithNamesForReplace_ShouldRenameProperties() @@ -650,16 +634,183 @@ namespace UVC.Tests.Data var mapper = new DataMapper(mask); // Act - var result = mapper.Mapping(source); + var result = mapper.Map(source); // Assert - // NamesForReplace가 제대로 구현되면 이 테스트는 통과해야 함 Assert.IsTrue(result.ContainsKey("NewName")); Assert.AreEqual("김철수", result.GetString("NewName")); Assert.IsFalse(result.ContainsKey("OldName")); Assert.IsTrue(result.ContainsKey("Age")); Assert.AreEqual(30, result.GetInt("Age")); } + + /// + /// DataMask의 DeepClone 메서드가 모든 속성을 올바르게 복사하는지 테스트합니다. + /// + [Test] + public void Map_DeepClone_CopiesAllProperties() + { + // Arrange + var originalMask = new DataMask(); + originalMask["Name"] = "홍길동"; + originalMask["Age"] = 25; + originalMask.ObjectIdKey = "Name"; + originalMask.ObjectName = "Person"; + originalMask.NamesForReplace = new Dictionary { + { "full_name", "Name" }, + { "user_age", "Age" } + }; + + // Act + var clonedMask = originalMask.DeepClone(); + + // 원본 마스크 수정 + originalMask["Name"] = "변경된 이름"; + originalMask.ObjectName = "ModifiedPerson"; + originalMask.NamesForReplace["full_name"] = "FullName"; + + // Assert - 클론은 변경되지 않아야 함 + Assert.AreEqual("홍길동", clonedMask["Name"]); + Assert.AreEqual(25, clonedMask["Age"]); + Assert.AreEqual("Name", clonedMask.ObjectIdKey); + Assert.AreEqual("Person", clonedMask.ObjectName); + Assert.AreEqual("Name", clonedMask.NamesForReplace["full_name"]); + Assert.AreEqual("Age", clonedMask.NamesForReplace["user_age"]); + + // 원본 마스크는 변경되어야 함 + Assert.AreEqual("변경된 이름", originalMask["Name"]); + Assert.AreEqual("ModifiedPerson", originalMask.ObjectName); + Assert.AreEqual("FullName", originalMask.NamesForReplace["full_name"]); + } + + /// + /// DataMask의 ToJObject 메서드가 올바르게 동작하는지 테스트합니다. + /// + [Test] + public void Map_ToJObject_ConvertsCorrectly() + { + // Arrange + var mask = new DataMask(); + mask["Name"] = "홍길동"; + mask["Age"] = 25; + mask["IsActive"] = true; + mask["Height"] = 175.5; + + // Act + var jObject = mask.ToJObject(); + + // Assert + Assert.IsNotNull(jObject); + Assert.AreEqual(4, jObject.Count); + Assert.AreEqual("홍길동", jObject["Name"].ToString()); + Assert.AreEqual(25, jObject["Age"].ToObject()); + Assert.AreEqual(true, jObject["IsActive"].ToObject()); + Assert.AreEqual(175.5, jObject["Height"].ToObject()); + } + + /// + /// DataMapper의 병렬 처리 기능이 대용량 배열에서 올바르게 동작하는지 테스트합니다. + /// + [Test] + public void Map_ParallelArrayProcessing_WorksCorrectly() + { + // Arrange + var mask = new DataMask(); + mask["Items"] = new List { new DataMask { ["Id"] = 0, ["Name"] = "" } }; + + // 2000개 아이템을 가진 소스 배열 생성 + var itemsArray = new JArray(); + for (int i = 1; i <= 2000; i++) + { + itemsArray.Add(new JObject + { + ["Id"] = i, + ["Name"] = $"Item {i}" + }); + } + + var source = new JObject { ["Items"] = itemsArray }; + var mapper = new DataMapper(mask); + + // Act + var result = mapper.Map(source); + + // Assert + var items = result.GetDataArray("Items"); + Assert.IsNotNull(items); + Assert.AreEqual(2000, items.Count); + + // 결과 검증 - 몇 개 아이템만 샘플링 + Assert.AreEqual(1, items[0].GetInt("Id")); + Assert.AreEqual("Item 1", items[0].GetString("Name")); + + Assert.AreEqual(1000, items[999].GetInt("Id")); + Assert.AreEqual("Item 1000", items[999].GetString("Name")); + + Assert.AreEqual(2000, items[1999].GetInt("Id")); + Assert.AreEqual("Item 2000", items[1999].GetString("Name")); + } + + /// + /// 재귀 깊이 제한이 올바르게 동작하는지 테스트합니다. + /// + [Test] + public void Map_RecursionDepthLimit_HandlesCorrectly() + { + // Arrange - 매우 깊게 중첩된 JSON 객체 생성 + var mask = CreateDeepNestedMask(20); + var source = CreateDeepNestedJObject(20); + + var mapper = new DataMapper(mask); + + string maskJsonString = mask.ToJsonString(); + + // Act - 예외 없이 매핑이 수행되어야 함 + var result = mapper.Map(source); + + // Assert - 최대 중첩 깊이(현재 10)까지 매핑된 구조 확인 + var level1 = result.GetDataObject("Level20"); + Assert.IsNotNull(level1); + + var level5 = level1.GetDataObject("Level15") + .GetDataObject("Level14") + .GetDataObject("Level13") + .GetDataObject("Level12"); + Assert.IsNotNull(level5); + + // 깊이 제한 이후 원본 객체를 그대로 반환하므로 + // 원래 깊이보다 더 깊게 들어갈 수 있어야 함 + var level10 = level5.GetDataObject("Level10") + .GetDataObject("Level9") + .GetDataObject("Level8") + .GetDataObject("Level7") + .GetDataObject("Level6"); + Assert.IsNotNull(level10); + } + + /// + /// 테스트용 깊게 중첩된 DataMask 생성 + /// + private DataMask CreateDeepNestedMask(int depth) + { + if (depth <= 0) return new DataMask { ["Value"] = "마스크 값" }; + + var mask = new DataMask(); + mask[$"Level{depth}"] = CreateDeepNestedMask(depth - 1); + return mask; + } + + /// + /// 테스트용 깊게 중첩된 JObject 생성 + /// + private JObject CreateDeepNestedJObject(int depth) + { + if (depth <= 0) return new JObject { ["Value"] = "소스 값" }; + + var obj = new JObject(); + obj[$"Level{depth}"] = CreateDeepNestedJObject(depth - 1); + return obj; + } } /// diff --git a/Assets/Scripts/UVC/Tests/Data/MQTTPipeLineTests.cs b/Assets/Scripts/UVC/Tests/Data/MQTTPipeLineTests.cs index 0116a913..cc435688 100644 --- a/Assets/Scripts/UVC/Tests/Data/MQTTPipeLineTests.cs +++ b/Assets/Scripts/UVC/Tests/Data/MQTTPipeLineTests.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; using UnityEngine; using UVC.Data; +using UVC.Log; namespace UVC.Tests.Data { @@ -42,14 +43,14 @@ namespace UVC.Tests.Data { Setup(); Debug.Log("===== MQTTPipeLine 테스트 시작 ====="); - await RunTestAsync(nameof(ExecutePipeLine_AllTopics_RegistersAndHandlesMessages), ExecutePipeLine_AllTopics_RegistersAndHandlesMessages); - await RunTestAsync(nameof(RemoveTopic_ShouldStopReceivingMessages), RemoveTopic_ShouldStopReceivingMessages); - await RunTestAsync(nameof(UpdatedDataOnly_ShouldOnlyCallHandlerForUpdatedData), UpdatedDataOnly_ShouldOnlyCallHandlerForUpdatedData); + //await RunTestAsync(nameof(ExecutePipeLine_AllTopics_RegistersAndHandlesMessages), ExecutePipeLine_AllTopics_RegistersAndHandlesMessages); + //await RunTestAsync(nameof(RemoveTopic_ShouldStopReceivingMessages), RemoveTopic_ShouldStopReceivingMessages); + //await RunTestAsync(nameof(UpdatedDataOnly_ShouldOnlyCallHandlerForUpdatedData), UpdatedDataOnly_ShouldOnlyCallHandlerForUpdatedData); await RunTestAsync(nameof(UpdatedDataOnly_WithMockMQTTService_ShouldOnlyReceiveUpdatedData), UpdatedDataOnly_WithMockMQTTService_ShouldOnlyReceiveUpdatedData); - RunTest(nameof(OnTopicMessage_ValidJsonObject_CallsHandler), OnTopicMessage_ValidJsonObject_CallsHandler); - RunTest(nameof(OnTopicMessage_JsonArray_CallsHandler), OnTopicMessage_JsonArray_CallsHandler); - RunTest(nameof(OnTopicMessage_EmptyMessage_DoesNotCallHandler), OnTopicMessage_EmptyMessage_DoesNotCallHandler); - RunTest(nameof(OnTopicMessage_InvalidJson_DoesNotCallHandler), OnTopicMessage_InvalidJson_DoesNotCallHandler); + //RunTest(nameof(OnTopicMessage_ValidJsonObject_CallsHandler), OnTopicMessage_ValidJsonObject_CallsHandler); + //RunTest(nameof(OnTopicMessage_JsonArray_CallsHandler), OnTopicMessage_JsonArray_CallsHandler); + //RunTest(nameof(OnTopicMessage_EmptyMessage_DoesNotCallHandler), OnTopicMessage_EmptyMessage_DoesNotCallHandler); + //RunTest(nameof(OnTopicMessage_InvalidJson_DoesNotCallHandler), OnTopicMessage_InvalidJson_DoesNotCallHandler); Debug.Log("===== MQTTPipeLine 테스트 완료 ====="); } @@ -99,6 +100,7 @@ namespace UVC.Tests.Data { case "AGV": dataMask.ObjectIdKey = "VHL_NAME"; + dataMask.ObjectName = "AGV"; dataMask["VHL_NAME"] = "HFF09CNA8013"; dataMask["AGV_IDX"] = 12; dataMask["B_INSTALL"] = "Y"; @@ -126,6 +128,7 @@ namespace UVC.Tests.Data break; case "CARRIER": dataMask.ObjectIdKey = "MAIN_CARR_ID"; + dataMask.ObjectName = "Carrier"; dataMask["MAIN_CARR_ID"] = "2F02365"; dataMask["SUB_CARR_ID"] = "2F02365,2F70671,2F28723"; dataMask["CARR_SEQ"] = "3"; @@ -144,6 +147,7 @@ namespace UVC.Tests.Data break; case "STOCKER_STACK": dataMask.ObjectIdKey = "STOCKER_NAME"; + dataMask.ObjectName = "StokerStack"; dataMask["STOCKER_NAME"] = "HFF09AGN0300"; dataMask["CAPACITY"] = "89.57"; dataMask["MAXIMUM_CAPACITY"] = "834"; @@ -160,9 +164,11 @@ namespace UVC.Tests.Data dataMask["KOR_EQP_NAME"] = "상온Aging #03"; dataMask["ENG_EQP_NAME"] = "상온Aging #03"; dataMask["TIMESTAMP"] = DateTime.Now; - dataMask["STEP"] = new JArray + dataMask["STEP"] = new List() { - new DataObject { + new DataMask { + ObjectIdKey = "STOCKER_NAME", + ObjectName = "StokerStep", ["STOCKER_NAME"] = "HFF09AGN0300", ["STEP_ID"] = "8106", ["RACK_STEP_COUNT"] = "88", @@ -173,9 +179,9 @@ namespace UVC.Tests.Data break; case "ALL": // ALL 토픽은 ObjectIdKey 없음 - dataMask["AGV"] = new JArray() + dataMask["AGV"] = new List() { - new DataObject + new DataMask { ["VHL_NAME"] = "HFF09CNA8053", ["AGV_IDX"] = 52, @@ -202,9 +208,9 @@ namespace UVC.Tests.Data ["FACTOR"] = 69.3, } }; - dataMask["CARRIER"] = new JArray() + dataMask["CARRIER"] = new List() { - new DataObject + new DataMask { ["MAIN_CARR_ID"] = "2F02365", ["SUB_CARR_ID"] = "2F02365,2F70671,2F28723", @@ -223,9 +229,9 @@ namespace UVC.Tests.Data ["CURRENTLOCATION"] = "HFF09CNV0300", } }; - dataMask["CARRIER"] = new JArray() + dataMask["CARRIER"] = new List() { - new DataObject + new DataMask { ["STOCKER_NAME"] = "HFF09AGN0300", ["CAPACITY"] = "89.57", @@ -243,9 +249,9 @@ namespace UVC.Tests.Data ["KOR_EQP_NAME"] = "상온Aging #03", ["ENG_EQP_NAME"] = "상온Aging #03", ["TIMESTAMP"] = DateTime.Now, - ["STEP"] = new JArray + ["STEP"] = new List { - new DataObject { + new DataMask { ["STOCKER_NAME"] = "HFF09AGN0300", ["STEP_ID"] = "8106", ["RACK_STEP_COUNT"] = "88", @@ -276,10 +282,10 @@ namespace UVC.Tests.Data pipeLine.Add(pipelineInfo); } - + Debug.Log("파이프라인 설정 완료."); // Act - 파이프라인 실행 pipeLine.Execute(); - + Debug.Log("파이프라인 Execute."); // Assert - 일정 시간 기다린 후 각 핸들러가 호출되었는지 확인 await UniTask.Delay(1500); @@ -495,7 +501,7 @@ namespace UVC.Tests.Data Assert.IsTrue(firstUpdatedCount > 0, "첫 번째 메시지에 업데이트된 데이터가 없습니다."); // 다음 데이터 세트가 도착하기를 기다림 - await UniTask.Delay(1500); + await UniTask.Delay(3000); // 두 번째 메시지가 도착했는지 확인 int finalCallCount = handler.CallCount; @@ -532,7 +538,7 @@ namespace UVC.Tests.Data public void HandleData(IDataObject? dataObject) { CallCount++; - + ULog.Debug($"UpdatedDataTrackingHandler 호출됨. CallCount: {CallCount}"); if (dataObject != null) { // 업데이트 개수 기록 @@ -553,7 +559,6 @@ namespace UVC.Tests.Data { HasUpdatedExistingItems = true; } - // 새로운 항목 추가 ReceivedAgvItems.Add(vhlName); } diff --git a/Assets/Scripts/UVC/Tests/MockMQTTService.cs b/Assets/Scripts/UVC/Tests/MockMQTTService.cs index 59ff8d64..1f75de2c 100644 --- a/Assets/Scripts/UVC/Tests/MockMQTTService.cs +++ b/Assets/Scripts/UVC/Tests/MockMQTTService.cs @@ -1,6 +1,7 @@ using Cysharp.Threading.Tasks; using System; using System.Collections.Concurrent; +using UVC.Log; namespace UVC.Tests { @@ -18,7 +19,7 @@ namespace UVC.Tests private int messageIdx = 0; public void Connect() - { + { connected = true; nofityMessage(); } @@ -159,9 +160,9 @@ namespace UVC.Tests "[{\"MAIN_CARR_ID\":\"2F02365\",\"SUB_CARR_ID\":\"2F02365,2F70671,2F28723\",\"CARR_SEQ\":\"3\",\"CARR_USE\":\"EMPTY\",\"CURRENTPORT\":\"HFF09CNV0300_ABP3003\",\"CURRENTRACK\":null,\"MOVE_JOBID\":null,\"MOVESTATUS\":\"ARRIVED\",\"FINALTOOLID\":null,\"MOVEFLAG\":\"0\",\"PROD_ID\":null,\"FTY_NO\":null,\"WORK_TYPE\":null,\"MFG_TYPE\":null,\"PROD_DETAIL_CODE\":\"E3A\",\"STEP_ID\":null,\"NEXT_STEP_ID\":null,\"ASSIGN_LOT_QTY\":\"0\",\"FRMT_BATCH_ID\":null,\"CARR_SIZE_TYPE\":\"STACK3\",\"ABNM_VALUE\":\"0\",\"LINE_ID\":\"FM0I\",\"TIMESTAMP\":\"2025-03-25T11:59:57.000Z\",\"INPUT_QTY\":null,\"GOOD_QTY\":\"0\",\"BAD_QTY\":null,\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"NEXT_KOR_STEP_GROUP_NAME\":null,\"NEXT_ENG_STEP_GROUP_NAME\":null,\"LOT_ID\":null,\"CTH_REEL_ID\":null,\"ANODE_REEL_ID\":null,\"CARR_NO\":null,\"BATCH_GUBUN\":null,\"PROC_IN_TIME\":null,\"IN_CARR_QTY\":null,\"LAST_TKIN_TIME\":null,\"VHCL_ID\":null,\"FIRST_FRMT_INPUT_TIME\":null,\"CURRENTLOCATION\":\"HFF09CNV0300\",\"JOB_ID\":null,\"FROM_PORT\":null,\"TO_PORT\":null,\"TRANSPORT_JOB_TIMESTAMP\":null},{\"MAIN_CARR_ID\":\"2F52504\",\"SUB_CARR_ID\":\"2F52504,2F11450,2F51910\",\"CARR_SEQ\":\"3\",\"CARR_USE\":\"FULL\",\"CURRENTPORT\":\"HFF09CDS0200_UBP02\",\"CURRENTRACK\":null,\"MOVE_JOBID\":null,\"MOVESTATUS\":\"ARRIVED\",\"FINALTOOLID\":null,\"MOVEFLAG\":\"0\",\"PROD_ID\":\"CP7024F111A\",\"FTY_NO\":\"70B0\",\"WORK_TYPE\":\"NM\",\"MFG_TYPE\":\"PP02\",\"PROD_DETAIL_CODE\":\"E3A\",\"STEP_ID\":\"8024\",\"NEXT_STEP_ID\":\"8025\",\"ASSIGN_LOT_QTY\":\"288\",\"FRMT_BATCH_ID\":\"H1J70SA253HO22\",\"CARR_SIZE_TYPE\":\"STACK3\",\"ABNM_VALUE\":\"0\",\"LINE_ID\":\"FM0I\",\"TIMESTAMP\":\"2025-03-25T11:59:57.000Z\",\"INPUT_QTY\":\"288\",\"GOOD_QTY\":\"288\",\"BAD_QTY\":null,\"KOR_STEP_GROUP_NAME\":\"고온 Aging3\",\"ENG_STEP_GROUP_NAME\":\"High Temp Aging3\",\"NEXT_KOR_STEP_GROUP_NAME\":\"냉각 Aging3\",\"NEXT_ENG_STEP_GROUP_NAME\":\"Cold Temp Aging3\",\"LOT_ID\":\"36092532270707BKDS1B\",\"CTH_REEL_ID\":\"H16CP25310A118D\",\"ANODE_REEL_ID\":\"H16AP25321A113E\",\"CARR_NO\":\"H1J70SA251DC13-5524\",\"BATCH_GUBUN\":\"PP02\",\"PROC_IN_TIME\":\"2025-03-22T21:57:57.000Z\",\"IN_CARR_QTY\":\"288\",\"LAST_TKIN_TIME\":\"2025-03-25T09:46:35.000Z\",\"VHCL_ID\":null,\"FIRST_FRMT_INPUT_TIME\":\"2025-03-22T21:58:00.000Z\",\"CURRENTLOCATION\":\"HFF09CDS0200\",\"JOB_ID\":null,\"FROM_PORT\":null,\"TO_PORT\":null,\"TRANSPORT_JOB_TIMESTAMP\":\"2025-03-25T11:57:14.000Z\"},{\"MAIN_CARR_ID\":\"3F18182\",\"SUB_CARR_ID\":\"3F18182\",\"CARR_SEQ\":\"1\",\"CARR_USE\":\"FULL\",\"CURRENTPORT\":\"HFF11CNV0500_ABP4714\",\"CURRENTRACK\":null,\"MOVE_JOBID\":null,\"MOVESTATUS\":\"IN-EQP\",\"FINALTOOLID\":null,\"MOVEFLAG\":\"0\",\"PROD_ID\":\"CP7024F111A\",\"FTY_NO\":\"70B0\",\"WORK_TYPE\":\"NM\",\"MFG_TYPE\":\"PP02\",\"PROD_DETAIL_CODE\":\"E3A\",\"STEP_ID\":\"8108\",\"NEXT_STEP_ID\":\"8116\",\"ASSIGN_LOT_QTY\":\"96\",\"FRMT_BATCH_ID\":\"H1M70SB253GL21\",\"CARR_SIZE_TYPE\":\"STACK4\",\"ABNM_VALUE\":\"0\",\"LINE_ID\":\"FM0M\",\"TIMESTAMP\":\"2025-03-25T11:59:55.000Z\",\"INPUT_QTY\":\"96\",\"GOOD_QTY\":\"96\",\"BAD_QTY\":null,\"KOR_STEP_GROUP_NAME\":\"출하 IR/OCV\",\"ENG_STEP_GROUP_NAME\":\"G/I IR/OCV\",\"NEXT_KOR_STEP_GROUP_NAME\":\"IR/OCV4 불량선별\",\"NEXT_ENG_STEP_GROUP_NAME\":\"IR/OCV4 Defect Detect\",\"LOT_ID\":\"36112531922037BKDS1B\",\"CTH_REEL_ID\":\"H16CP25309A120B\",\"ANODE_REEL_ID\":\"H16AP25309A121D\",\"CARR_NO\":\"H1M70SA253GJ13-022\",\"BATCH_GUBUN\":\"PP02\",\"PROC_IN_TIME\":\"2025-03-19T16:38:53.000Z\",\"IN_CARR_QTY\":\"96\",\"LAST_TKIN_TIME\":\"2025-03-25T11:59:54.000Z\",\"VHCL_ID\":null,\"FIRST_FRMT_INPUT_TIME\":\"2025-03-19T16:38:55.000Z\",\"CURRENTLOCATION\":\"HFF11CNV0500\",\"JOB_ID\":null,\"FROM_PORT\":null,\"TO_PORT\":null,\"TRANSPORT_JOB_TIMESTAMP\":null}]" }; private string[] stockerStackMessage = new string[]{ - "[{\"STOCKER_NAME\":\"HFF09AGN0300\",\"CAPACITY\":\"89.57\",\"MAXIMUM_CAPACITY\":\"834\",\"TRAY_CAPACITY\":\"83.25\",\"MAXIMUM_TRAY_CAPACITY\":\"2502\",\"RACK_LOAD_COUNT\":\"747\",\"RACK_EMPTY_COUNT\":\"87\",\"RESERVATED_RETURN_COUNT\":\"5\",\"TRAY_COUNT\":\"2083\",\"TRAY_REWORK_COUNT_AVG\":\"3\",\"TRAY_REWORK_COUNT_MAX\":\"153\",\"TRAY_REWORK_COUNT_MIN\":\"0\",\"RACK_DISABLE_COUNT\":\"4\",\"KOR_EQP_NAME\":\"상온Aging #03\",\"ENG_EQP_NAME\":\"상온Aging #03\",\"TIMESTAMP\":\"2025-03-25T11:59:56.000Z\",\"STEP\":[{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8106\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8220\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8100\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8038\",\"KOR_STEP_GROUP_NAME\":\"디게싱에이징1\",\"ENG_STEP_GROUP_NAME\":\"Deggassing Aging1\",\"RACK_STEP_COUNT\":\"8\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"0.96\",\"TIMESTAMP\":\"2025-03-25T11:59:59.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8150\",\"KOR_STEP_GROUP_NAME\":\"용량검사\",\"ENG_STEP_GROUP_NAME\":\"Capacity Inspection\",\"RACK_STEP_COUNT\":\"1\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"0.12\",\"TIMESTAMP\":\"2025-03-25T11:21:18.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8136\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8134\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8116\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8028\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8012\",\"KOR_STEP_GROUP_NAME\":\"상온 Aging2\",\"ENG_STEP_GROUP_NAME\":\"Normal Temp Aging2\",\"RACK_STEP_COUNT\":\"24\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"2.88\",\"TIMESTAMP\":\"2025-03-25T11:59:59.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8057\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8070\",\"KOR_STEP_GROUP_NAME\":\"호퍼 pre-c 전 임시밀폐제거\",\"ENG_STEP_GROUP_NAME\":\"Temp Sealing Remove(Pre-C)\",\"RACK_STEP_COUNT\":\"1\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"0.12\",\"TIMESTAMP\":\"2025-03-25T10:56:56.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8140\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8138\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8025\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8010\",\"KOR_STEP_GROUP_NAME\":\"상온 Aging1\",\"ENG_STEP_GROUP_NAME\":\"Normal Temp Aging1\",\"RACK_STEP_COUNT\":\"123\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"14.75\",\"TIMESTAMP\":\"2025-03-25T11:59:59.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8014\",\"KOR_STEP_GROUP_NAME\":\"상온 Aging3\",\"ENG_STEP_GROUP_NAME\":\"Normal Temp Aging3\",\"RACK_STEP_COUNT\":\"106\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"12.71\",\"TIMESTAMP\":\"2025-03-25T11:59:59.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8016\",\"KOR_STEP_GROUP_NAME\":\"상온 Aging4\",\"ENG_STEP_GROUP_NAME\":\"Normal Temp Aging4\",\"RACK_STEP_COUNT\":\"117\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"14.03\",\"TIMESTAMP\":\"2025-03-25T11:59:59.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8040\",\"KOR_STEP_GROUP_NAME\":\"출하 Aging\",\"ENG_STEP_GROUP_NAME\":\"G/I Aging\",\"RACK_STEP_COUNT\":\"215\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"25.78\",\"TIMESTAMP\":\"2025-03-25T11:59:59.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8042\",\"KOR_STEP_GROUP_NAME\":\"상온 Aging5\",\"ENG_STEP_GROUP_NAME\":\"Normal Temp Aging5\",\"RACK_STEP_COUNT\":\"3\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"0.36\",\"TIMESTAMP\":\"2025-03-25T11:59:59.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8044\",\"KOR_STEP_GROUP_NAME\":\"상온 Aging6\",\"ENG_STEP_GROUP_NAME\":\"Normal Temp Aging6\",\"RACK_STEP_COUNT\":\"6\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"0.72\",\"TIMESTAMP\":\"2025-03-25T11:59:59.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8045\",\"KOR_STEP_GROUP_NAME\":\"상온 Aging7\",\"ENG_STEP_GROUP_NAME\":\"Normal Temp Aging7\",\"RACK_STEP_COUNT\":\"21\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"2.52\",\"TIMESTAMP\":\"2025-03-25T11:59:59.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8046\",\"KOR_STEP_GROUP_NAME\":\"상온 Aging8\",\"ENG_STEP_GROUP_NAME\":\"Normal Temp Aging8\",\"RACK_STEP_COUNT\":\"3\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"0.36\",\"TIMESTAMP\":\"2025-03-25T11:59:59.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8190\",\"KOR_STEP_GROUP_NAME\":\"방치 AGING #1\",\"ENG_STEP_GROUP_NAME\":\"Boxing Aging 1\",\"RACK_STEP_COUNT\":\"1\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"0.12\",\"TIMESTAMP\":\"2025-03-25T11:59:59.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8400\",\"KOR_STEP_GROUP_NAME\":\"포장\",\"ENG_STEP_GROUP_NAME\":\"Packing\",\"RACK_STEP_COUNT\":\"76\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"9.11\",\"TIMESTAMP\":\"2025-03-25T11:59:59.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8132\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8245\",\"KOR_STEP_GROUP_NAME\":\"외관검사1\",\"ENG_STEP_GROUP_NAME\":\"External Inspection 1\",\"RACK_STEP_COUNT\":\"1\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"0.12\",\"TIMESTAMP\":\"2025-03-25T10:41:12.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8182\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8108\",\"KOR_STEP_GROUP_NAME\":\"출하 IR/OCV\",\"ENG_STEP_GROUP_NAME\":\"G/I IR/OCV\",\"RACK_STEP_COUNT\":\"1\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"0.12\",\"TIMESTAMP\":\"2025-03-25T11:04:49.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8102\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8192\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"0\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"44\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"5.28\",\"TIMESTAMP\":\"2025-03-25T11:59:59.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8047\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8104\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8024\",\"KOR_STEP_GROUP_NAME\":\"고온 Aging3\",\"ENG_STEP_GROUP_NAME\":\"High Temp Aging3\",\"RACK_STEP_COUNT\":\"1\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"0.12\",\"TIMESTAMP\":\"2025-03-25T09:08:25.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8130\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":null,\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"87\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.43\",\"TIMESTAMP\":\"2025-03-25T11:59:59.000Z\"}]},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"CAPACITY\":\"95.43\",\"MAXIMUM_CAPACITY\":\"810\",\"TRAY_CAPACITY\":\"93.54\",\"MAXIMUM_TRAY_CAPACITY\":\"2430\",\"RACK_LOAD_COUNT\":\"773\",\"RACK_EMPTY_COUNT\":\"37\",\"RESERVATED_RETURN_COUNT\":\"9\",\"TRAY_COUNT\":\"2273\",\"TRAY_REWORK_COUNT_AVG\":\"6\",\"TRAY_REWORK_COUNT_MAX\":\"132\",\"TRAY_REWORK_COUNT_MIN\":\"0\",\"RACK_DISABLE_COUNT\":\"55\",\"KOR_EQP_NAME\":\"출하창고(Module Cell Aging) #01\",\"ENG_EQP_NAME\":\"출하창고(Module Cell Aging) #01\",\"TIMESTAMP\":\"2025-03-25T11:59:56.000Z\",\"STEP\":[{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8040\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"38\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.69\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"0\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"38\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.69\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8046\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"38\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.69\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8192\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"38\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.69\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8190\",\"KOR_STEP_GROUP_NAME\":\"방치 AGING #1\",\"ENG_STEP_GROUP_NAME\":\"Boxing Aging 1\",\"RACK_STEP_COUNT\":\"5\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"0.62\",\"TIMESTAMP\":\"2025-03-25T11:59:59.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8400\",\"KOR_STEP_GROUP_NAME\":\"포장\",\"ENG_STEP_GROUP_NAME\":\"Packing\",\"RACK_STEP_COUNT\":\"768\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"94.81\",\"TIMESTAMP\":\"2025-03-25T11:59:59.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8245\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"38\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.69\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8106\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"38\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.69\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8010\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"38\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.69\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8182\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"38\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.69\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8220\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"38\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.69\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8014\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"38\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.69\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8108\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"38\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.69\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8250\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"38\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.69\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8016\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"38\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.69\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8047\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"38\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.69\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":null,\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"37\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.57\",\"TIMESTAMP\":\"2025-03-25T11:59:59.000Z\"}]},{\"STOCKER_NAME\":\"HFF09AGH0100\",\"CAPACITY\":\"89.9\",\"MAXIMUM_CAPACITY\":\"406\",\"TRAY_CAPACITY\":\"88.59\",\"MAXIMUM_TRAY_CAPACITY\":\"1218\",\"RACK_LOAD_COUNT\":\"365\",\"RACK_EMPTY_COUNT\":\"41\",\"RESERVATED_RETURN_COUNT\":\"7\",\"TRAY_COUNT\":\"1079\",\"TRAY_REWORK_COUNT_AVG\":\"6\",\"TRAY_REWORK_COUNT_MAX\":\"64\",\"TRAY_REWORK_COUNT_MIN\":\"0\",\"RACK_DISABLE_COUNT\":\"0\",\"KOR_EQP_NAME\":\"고온Aging #01\",\"ENG_EQP_NAME\":\"고온Aging #01\",\"TIMESTAMP\":\"2025-03-25T11:59:56.000Z\",\"STEP\":[{\"STOCKER_NAME\":\"HFF09AGH0100\",\"STEP_ID\":\"8190\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"39\",\"TOTAL\":\"406\",\"STEP_CAPACITY\":\"9.61\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGH0100\",\"STEP_ID\":\"8040\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"39\",\"TOTAL\":\"406\",\"STEP_CAPACITY\":\"9.61\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGH0100\",\"STEP_ID\":\"8016\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"39\",\"TOTAL\":\"406\",\"STEP_CAPACITY\":\"9.61\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGH0100\",\"STEP_ID\":\"8020\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"39\",\"TOTAL\":\"406\",\"STEP_CAPACITY\":\"9.61\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGH0100\",\"STEP_ID\":\"8150\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"39\",\"TOTAL\":\"406\",\"STEP_CAPACITY\":\"9.61\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGH0100\",\"STEP_ID\":\"8024\",\"KOR_STEP_GROUP_NAME\":\"고온 Aging3\",\"ENG_STEP_GROUP_NAME\":\"High Temp Aging3\",\"RACK_STEP_COUNT\":\"251\",\"TOTAL\":\"406\",\"STEP_CAPACITY\":\"61.82\",\"TIMESTAMP\":\"2025-03-25T11:59:59.000Z\"},{\"STOCKER_NAME\":\"HFF09AGH0100\",\"STEP_ID\":\"8028\",\"KOR_STEP_GROUP_NAME\":\"고온 Aging5\",\"ENG_STEP_GROUP_NAME\":\"High Temp Aging5\",\"RACK_STEP_COUNT\":\"1\",\"TOTAL\":\"406\",\"STEP_CAPACITY\":\"0.25\",\"TIMESTAMP\":\"2025-03-25T11:59:59.000Z\"},{\"STOCKER_NAME\":\"HFF09AGH0100\",\"STEP_ID\":\"0\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"113\",\"TOTAL\":\"406\",\"STEP_CAPACITY\":\"27.83\",\"TIMESTAMP\":\"2025-03-25T11:59:59.000Z\"},{\"STOCKER_NAME\":\"HFF09AGH0100\",\"STEP_ID\":\"8025\",\"KOR_STEP_GROUP_NAME\":\"냉각 Aging3\",\"ENG_STEP_GROUP_NAME\":\"Cold Temp Aging3\",\"RACK_STEP_COUNT\":\"1\",\"TOTAL\":\"406\",\"STEP_CAPACITY\":\"0.25\",\"TIMESTAMP\":\"2025-03-25T11:23:53.000Z\"},{\"STOCKER_NAME\":\"HFF09AGH0100\",\"STEP_ID\":\"8022\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"39\",\"TOTAL\":\"406\",\"STEP_CAPACITY\":\"9.61\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGH0100\",\"STEP_ID\":\"8026\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"39\",\"TOTAL\":\"406\",\"STEP_CAPACITY\":\"9.61\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGH0100\",\"STEP_ID\":\"8029\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"39\",\"TOTAL\":\"406\",\"STEP_CAPACITY\":\"9.61\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGH0100\",\"STEP_ID\":null,\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"41\",\"TOTAL\":\"406\",\"STEP_CAPACITY\":\"10.1\",\"TIMESTAMP\":\"2025-03-25T11:59:59.000Z\"}]}]", - "[{\"STOCKER_NAME\":\"HFF09AGN0300\",\"CAPACITY\":\"89.57\",\"MAXIMUM_CAPACITY\":\"834\",\"TRAY_CAPACITY\":\"83.25\",\"MAXIMUM_TRAY_CAPACITY\":\"2502\",\"RACK_LOAD_COUNT\":\"747\",\"RACK_EMPTY_COUNT\":\"87\",\"RESERVATED_RETURN_COUNT\":\"5\",\"TRAY_COUNT\":\"2083\",\"TRAY_REWORK_COUNT_AVG\":\"3\",\"TRAY_REWORK_COUNT_MAX\":\"153\",\"TRAY_REWORK_COUNT_MIN\":\"0\",\"RACK_DISABLE_COUNT\":\"4\",\"KOR_EQP_NAME\":\"상온Aging #03\",\"ENG_EQP_NAME\":\"상온Aging #03\",\"TIMESTAMP\":\"2025-03-25T11:59:56.000Z\",\"STEP\":[{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8106\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8220\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8100\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8038\",\"KOR_STEP_GROUP_NAME\":\"디게싱에이징1\",\"ENG_STEP_GROUP_NAME\":\"Deggassing Aging1\",\"RACK_STEP_COUNT\":\"8\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"0.96\",\"TIMESTAMP\":\"2025-03-25T12:00:00.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8150\",\"KOR_STEP_GROUP_NAME\":\"용량검사\",\"ENG_STEP_GROUP_NAME\":\"Capacity Inspection\",\"RACK_STEP_COUNT\":\"1\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"0.12\",\"TIMESTAMP\":\"2025-03-25T11:21:18.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8136\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8134\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8116\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8028\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8012\",\"KOR_STEP_GROUP_NAME\":\"상온 Aging2\",\"ENG_STEP_GROUP_NAME\":\"Normal Temp Aging2\",\"RACK_STEP_COUNT\":\"24\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"2.88\",\"TIMESTAMP\":\"2025-03-25T12:00:00.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8057\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8070\",\"KOR_STEP_GROUP_NAME\":\"호퍼 pre-c 전 임시밀폐제거\",\"ENG_STEP_GROUP_NAME\":\"Temp Sealing Remove(Pre-C)\",\"RACK_STEP_COUNT\":\"1\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"0.12\",\"TIMESTAMP\":\"2025-03-25T10:56:56.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8140\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8138\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8025\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8010\",\"KOR_STEP_GROUP_NAME\":\"상온 Aging1\",\"ENG_STEP_GROUP_NAME\":\"Normal Temp Aging1\",\"RACK_STEP_COUNT\":\"123\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"14.75\",\"TIMESTAMP\":\"2025-03-25T12:00:00.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8014\",\"KOR_STEP_GROUP_NAME\":\"상온 Aging3\",\"ENG_STEP_GROUP_NAME\":\"Normal Temp Aging3\",\"RACK_STEP_COUNT\":\"106\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"12.71\",\"TIMESTAMP\":\"2025-03-25T12:00:00.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8016\",\"KOR_STEP_GROUP_NAME\":\"상온 Aging4\",\"ENG_STEP_GROUP_NAME\":\"Normal Temp Aging4\",\"RACK_STEP_COUNT\":\"117\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"14.03\",\"TIMESTAMP\":\"2025-03-25T12:00:00.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8040\",\"KOR_STEP_GROUP_NAME\":\"출하 Aging\",\"ENG_STEP_GROUP_NAME\":\"G/I Aging\",\"RACK_STEP_COUNT\":\"215\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"25.78\",\"TIMESTAMP\":\"2025-03-25T12:00:00.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8042\",\"KOR_STEP_GROUP_NAME\":\"상온 Aging5\",\"ENG_STEP_GROUP_NAME\":\"Normal Temp Aging5\",\"RACK_STEP_COUNT\":\"3\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"0.36\",\"TIMESTAMP\":\"2025-03-25T12:00:00.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8044\",\"KOR_STEP_GROUP_NAME\":\"상온 Aging6\",\"ENG_STEP_GROUP_NAME\":\"Normal Temp Aging6\",\"RACK_STEP_COUNT\":\"6\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"0.72\",\"TIMESTAMP\":\"2025-03-25T12:00:00.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8045\",\"KOR_STEP_GROUP_NAME\":\"상온 Aging7\",\"ENG_STEP_GROUP_NAME\":\"Normal Temp Aging7\",\"RACK_STEP_COUNT\":\"21\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"2.52\",\"TIMESTAMP\":\"2025-03-25T12:00:00.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8046\",\"KOR_STEP_GROUP_NAME\":\"상온 Aging8\",\"ENG_STEP_GROUP_NAME\":\"Normal Temp Aging8\",\"RACK_STEP_COUNT\":\"3\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"0.36\",\"TIMESTAMP\":\"2025-03-25T12:00:00.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8190\",\"KOR_STEP_GROUP_NAME\":\"방치 AGING #1\",\"ENG_STEP_GROUP_NAME\":\"Boxing Aging 1\",\"RACK_STEP_COUNT\":\"1\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"0.12\",\"TIMESTAMP\":\"2025-03-25T12:00:00.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8400\",\"KOR_STEP_GROUP_NAME\":\"포장\",\"ENG_STEP_GROUP_NAME\":\"Packing\",\"RACK_STEP_COUNT\":\"76\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"9.11\",\"TIMESTAMP\":\"2025-03-25T12:00:00.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8132\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8245\",\"KOR_STEP_GROUP_NAME\":\"외관검사1\",\"ENG_STEP_GROUP_NAME\":\"External Inspection 1\",\"RACK_STEP_COUNT\":\"1\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"0.12\",\"TIMESTAMP\":\"2025-03-25T10:41:12.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8182\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8108\",\"KOR_STEP_GROUP_NAME\":\"출하 IR/OCV\",\"ENG_STEP_GROUP_NAME\":\"G/I IR/OCV\",\"RACK_STEP_COUNT\":\"1\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"0.12\",\"TIMESTAMP\":\"2025-03-25T11:04:49.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8102\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8192\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"0\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"44\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"5.28\",\"TIMESTAMP\":\"2025-03-25T12:00:00.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8047\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8104\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8024\",\"KOR_STEP_GROUP_NAME\":\"고온 Aging3\",\"ENG_STEP_GROUP_NAME\":\"High Temp Aging3\",\"RACK_STEP_COUNT\":\"1\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"0.12\",\"TIMESTAMP\":\"2025-03-25T09:08:25.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8130\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":null,\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"87\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.43\",\"TIMESTAMP\":\"2025-03-25T12:00:00.000Z\"}]},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"CAPACITY\":\"95.43\",\"MAXIMUM_CAPACITY\":\"810\",\"TRAY_CAPACITY\":\"93.54\",\"MAXIMUM_TRAY_CAPACITY\":\"2430\",\"RACK_LOAD_COUNT\":\"773\",\"RACK_EMPTY_COUNT\":\"37\",\"RESERVATED_RETURN_COUNT\":\"9\",\"TRAY_COUNT\":\"2273\",\"TRAY_REWORK_COUNT_AVG\":\"6\",\"TRAY_REWORK_COUNT_MAX\":\"132\",\"TRAY_REWORK_COUNT_MIN\":\"0\",\"RACK_DISABLE_COUNT\":\"55\",\"KOR_EQP_NAME\":\"출하창고(Module Cell Aging) #01\",\"ENG_EQP_NAME\":\"출하창고(Module Cell Aging) #01\",\"TIMESTAMP\":\"2025-03-25T11:59:56.000Z\",\"STEP\":[{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8040\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"38\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.69\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"0\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"38\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.69\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8046\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"38\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.69\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8192\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"38\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.69\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8190\",\"KOR_STEP_GROUP_NAME\":\"방치 AGING #1\",\"ENG_STEP_GROUP_NAME\":\"Boxing Aging 1\",\"RACK_STEP_COUNT\":\"5\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"0.62\",\"TIMESTAMP\":\"2025-03-25T12:00:00.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8400\",\"KOR_STEP_GROUP_NAME\":\"포장\",\"ENG_STEP_GROUP_NAME\":\"Packing\",\"RACK_STEP_COUNT\":\"768\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"94.81\",\"TIMESTAMP\":\"2025-03-25T12:00:00.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8245\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"38\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.69\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8106\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"38\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.69\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8010\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"38\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.69\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8182\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"38\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.69\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8220\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"38\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.69\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8014\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"38\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.69\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8108\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"38\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.69\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8250\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"38\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.69\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8016\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"38\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.69\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8047\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"38\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.69\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":null,\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"37\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.57\",\"TIMESTAMP\":\"2025-03-25T12:00:00.000Z\"}]},{\"STOCKER_NAME\":\"HFF09AGH0100\",\"CAPACITY\":\"89.9\",\"MAXIMUM_CAPACITY\":\"406\",\"TRAY_CAPACITY\":\"88.59\",\"MAXIMUM_TRAY_CAPACITY\":\"1218\",\"RACK_LOAD_COUNT\":\"365\",\"RACK_EMPTY_COUNT\":\"41\",\"RESERVATED_RETURN_COUNT\":\"7\",\"TRAY_COUNT\":\"1079\",\"TRAY_REWORK_COUNT_AVG\":\"6\",\"TRAY_REWORK_COUNT_MAX\":\"64\",\"TRAY_REWORK_COUNT_MIN\":\"0\",\"RACK_DISABLE_COUNT\":\"0\",\"KOR_EQP_NAME\":\"고온Aging #01\",\"ENG_EQP_NAME\":\"고온Aging #01\",\"TIMESTAMP\":\"2025-03-25T11:59:56.000Z\",\"STEP\":[{\"STOCKER_NAME\":\"HFF09AGH0100\",\"STEP_ID\":\"8190\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"39\",\"TOTAL\":\"406\",\"STEP_CAPACITY\":\"9.61\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGH0100\",\"STEP_ID\":\"8040\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"39\",\"TOTAL\":\"406\",\"STEP_CAPACITY\":\"9.61\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGH0100\",\"STEP_ID\":\"8016\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"39\",\"TOTAL\":\"406\",\"STEP_CAPACITY\":\"9.61\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGH0100\",\"STEP_ID\":\"8020\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"39\",\"TOTAL\":\"406\",\"STEP_CAPACITY\":\"9.61\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGH0100\",\"STEP_ID\":\"8150\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"39\",\"TOTAL\":\"406\",\"STEP_CAPACITY\":\"9.61\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGH0100\",\"STEP_ID\":\"8024\",\"KOR_STEP_GROUP_NAME\":\"고온 Aging3\",\"ENG_STEP_GROUP_NAME\":\"High Temp Aging3\",\"RACK_STEP_COUNT\":\"251\",\"TOTAL\":\"406\",\"STEP_CAPACITY\":\"61.82\",\"TIMESTAMP\":\"2025-03-25T12:00:00.000Z\"},{\"STOCKER_NAME\":\"HFF09AGH0100\",\"STEP_ID\":\"8028\",\"KOR_STEP_GROUP_NAME\":\"고온 Aging5\",\"ENG_STEP_GROUP_NAME\":\"High Temp Aging5\",\"RACK_STEP_COUNT\":\"1\",\"TOTAL\":\"406\",\"STEP_CAPACITY\":\"0.25\",\"TIMESTAMP\":\"2025-03-25T12:00:00.000Z\"},{\"STOCKER_NAME\":\"HFF09AGH0100\",\"STEP_ID\":\"0\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"113\",\"TOTAL\":\"406\",\"STEP_CAPACITY\":\"27.83\",\"TIMESTAMP\":\"2025-03-25T12:00:00.000Z\"},{\"STOCKER_NAME\":\"HFF09AGH0100\",\"STEP_ID\":\"8025\",\"KOR_STEP_GROUP_NAME\":\"냉각 Aging3\",\"ENG_STEP_GROUP_NAME\":\"Cold Temp Aging3\",\"RACK_STEP_COUNT\":\"1\",\"TOTAL\":\"406\",\"STEP_CAPACITY\":\"0.25\",\"TIMESTAMP\":\"2025-03-25T11:23:53.000Z\"},{\"STOCKER_NAME\":\"HFF09AGH0100\",\"STEP_ID\":\"8022\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"39\",\"TOTAL\":\"406\",\"STEP_CAPACITY\":\"9.61\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGH0100\",\"STEP_ID\":\"8026\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"39\",\"TOTAL\":\"406\",\"STEP_CAPACITY\":\"9.61\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGH0100\",\"STEP_ID\":\"8029\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"39\",\"TOTAL\":\"406\",\"STEP_CAPACITY\":\"9.61\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGH0100\",\"STEP_ID\":null,\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"41\",\"TOTAL\":\"406\",\"STEP_CAPACITY\":\"10.1\",\"TIMESTAMP\":\"2025-03-25T12:00:00.000Z\"}]}]", - "[{\"STOCKER_NAME\":\"HFF09AGN0300\",\"CAPACITY\":\"89.57\",\"MAXIMUM_CAPACITY\":\"834\",\"TRAY_CAPACITY\":\"83.25\",\"MAXIMUM_TRAY_CAPACITY\":\"2502\",\"RACK_LOAD_COUNT\":\"747\",\"RACK_EMPTY_COUNT\":\"87\",\"RESERVATED_RETURN_COUNT\":\"5\",\"TRAY_COUNT\":\"2083\",\"TRAY_REWORK_COUNT_AVG\":\"3\",\"TRAY_REWORK_COUNT_MAX\":\"153\",\"TRAY_REWORK_COUNT_MIN\":\"0\",\"RACK_DISABLE_COUNT\":\"4\",\"KOR_EQP_NAME\":\"상온Aging #03\",\"ENG_EQP_NAME\":\"상온Aging #03\",\"TIMESTAMP\":\"2025-03-25T12:00:00.000Z\",\"STEP\":[{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8106\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8220\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8100\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8038\",\"KOR_STEP_GROUP_NAME\":\"디게싱에이징1\",\"ENG_STEP_GROUP_NAME\":\"Deggassing Aging1\",\"RACK_STEP_COUNT\":\"8\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"0.96\",\"TIMESTAMP\":\"2025-03-25T12:00:01.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8150\",\"KOR_STEP_GROUP_NAME\":\"용량검사\",\"ENG_STEP_GROUP_NAME\":\"Capacity Inspection\",\"RACK_STEP_COUNT\":\"1\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"0.12\",\"TIMESTAMP\":\"2025-03-25T11:21:18.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8136\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8134\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8116\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8028\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8012\",\"KOR_STEP_GROUP_NAME\":\"상온 Aging2\",\"ENG_STEP_GROUP_NAME\":\"Normal Temp Aging2\",\"RACK_STEP_COUNT\":\"24\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"2.88\",\"TIMESTAMP\":\"2025-03-25T12:00:01.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8057\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8070\",\"KOR_STEP_GROUP_NAME\":\"호퍼 pre-c 전 임시밀폐제거\",\"ENG_STEP_GROUP_NAME\":\"Temp Sealing Remove(Pre-C)\",\"RACK_STEP_COUNT\":\"1\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"0.12\",\"TIMESTAMP\":\"2025-03-25T10:56:56.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8140\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8138\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8025\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8010\",\"KOR_STEP_GROUP_NAME\":\"상온 Aging1\",\"ENG_STEP_GROUP_NAME\":\"Normal Temp Aging1\",\"RACK_STEP_COUNT\":\"123\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"14.75\",\"TIMESTAMP\":\"2025-03-25T12:00:01.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8014\",\"KOR_STEP_GROUP_NAME\":\"상온 Aging3\",\"ENG_STEP_GROUP_NAME\":\"Normal Temp Aging3\",\"RACK_STEP_COUNT\":\"106\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"12.71\",\"TIMESTAMP\":\"2025-03-25T12:00:01.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8016\",\"KOR_STEP_GROUP_NAME\":\"상온 Aging4\",\"ENG_STEP_GROUP_NAME\":\"Normal Temp Aging4\",\"RACK_STEP_COUNT\":\"117\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"14.03\",\"TIMESTAMP\":\"2025-03-25T12:00:01.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8040\",\"KOR_STEP_GROUP_NAME\":\"출하 Aging\",\"ENG_STEP_GROUP_NAME\":\"G/I Aging\",\"RACK_STEP_COUNT\":\"215\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"25.78\",\"TIMESTAMP\":\"2025-03-25T12:00:01.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8042\",\"KOR_STEP_GROUP_NAME\":\"상온 Aging5\",\"ENG_STEP_GROUP_NAME\":\"Normal Temp Aging5\",\"RACK_STEP_COUNT\":\"3\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"0.36\",\"TIMESTAMP\":\"2025-03-25T12:00:01.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8044\",\"KOR_STEP_GROUP_NAME\":\"상온 Aging6\",\"ENG_STEP_GROUP_NAME\":\"Normal Temp Aging6\",\"RACK_STEP_COUNT\":\"6\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"0.72\",\"TIMESTAMP\":\"2025-03-25T12:00:01.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8045\",\"KOR_STEP_GROUP_NAME\":\"상온 Aging7\",\"ENG_STEP_GROUP_NAME\":\"Normal Temp Aging7\",\"RACK_STEP_COUNT\":\"21\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"2.52\",\"TIMESTAMP\":\"2025-03-25T12:00:01.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8046\",\"KOR_STEP_GROUP_NAME\":\"상온 Aging8\",\"ENG_STEP_GROUP_NAME\":\"Normal Temp Aging8\",\"RACK_STEP_COUNT\":\"3\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"0.36\",\"TIMESTAMP\":\"2025-03-25T12:00:01.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8190\",\"KOR_STEP_GROUP_NAME\":\"방치 AGING #1\",\"ENG_STEP_GROUP_NAME\":\"Boxing Aging 1\",\"RACK_STEP_COUNT\":\"1\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"0.12\",\"TIMESTAMP\":\"2025-03-25T12:00:01.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8400\",\"KOR_STEP_GROUP_NAME\":\"포장\",\"ENG_STEP_GROUP_NAME\":\"Packing\",\"RACK_STEP_COUNT\":\"76\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"9.11\",\"TIMESTAMP\":\"2025-03-25T12:00:01.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8132\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8245\",\"KOR_STEP_GROUP_NAME\":\"외관검사1\",\"ENG_STEP_GROUP_NAME\":\"External Inspection 1\",\"RACK_STEP_COUNT\":\"1\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"0.12\",\"TIMESTAMP\":\"2025-03-25T10:41:12.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8182\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8108\",\"KOR_STEP_GROUP_NAME\":\"출하 IR/OCV\",\"ENG_STEP_GROUP_NAME\":\"G/I IR/OCV\",\"RACK_STEP_COUNT\":\"1\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"0.12\",\"TIMESTAMP\":\"2025-03-25T11:04:49.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8102\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8192\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"0\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"44\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"5.28\",\"TIMESTAMP\":\"2025-03-25T12:00:01.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8047\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8104\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8024\",\"KOR_STEP_GROUP_NAME\":\"고온 Aging3\",\"ENG_STEP_GROUP_NAME\":\"High Temp Aging3\",\"RACK_STEP_COUNT\":\"1\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"0.12\",\"TIMESTAMP\":\"2025-03-25T09:08:25.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8130\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":null,\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"87\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.43\",\"TIMESTAMP\":\"2025-03-25T12:00:01.000Z\"}]},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"CAPACITY\":\"95.43\",\"MAXIMUM_CAPACITY\":\"810\",\"TRAY_CAPACITY\":\"93.54\",\"MAXIMUM_TRAY_CAPACITY\":\"2430\",\"RACK_LOAD_COUNT\":\"773\",\"RACK_EMPTY_COUNT\":\"37\",\"RESERVATED_RETURN_COUNT\":\"9\",\"TRAY_COUNT\":\"2273\",\"TRAY_REWORK_COUNT_AVG\":\"6\",\"TRAY_REWORK_COUNT_MAX\":\"132\",\"TRAY_REWORK_COUNT_MIN\":\"0\",\"RACK_DISABLE_COUNT\":\"55\",\"KOR_EQP_NAME\":\"출하창고(Module Cell Aging) #01\",\"ENG_EQP_NAME\":\"출하창고(Module Cell Aging) #01\",\"TIMESTAMP\":\"2025-03-25T12:00:00.000Z\",\"STEP\":[{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8040\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"38\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.69\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"0\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"38\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.69\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8046\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"38\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.69\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8192\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"38\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.69\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8190\",\"KOR_STEP_GROUP_NAME\":\"방치 AGING #1\",\"ENG_STEP_GROUP_NAME\":\"Boxing Aging 1\",\"RACK_STEP_COUNT\":\"5\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"0.62\",\"TIMESTAMP\":\"2025-03-25T12:00:01.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8400\",\"KOR_STEP_GROUP_NAME\":\"포장\",\"ENG_STEP_GROUP_NAME\":\"Packing\",\"RACK_STEP_COUNT\":\"768\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"94.81\",\"TIMESTAMP\":\"2025-03-25T12:00:01.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8245\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"38\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.69\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8106\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"38\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.69\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8010\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"38\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.69\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8182\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"38\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.69\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8220\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"38\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.69\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8014\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"38\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.69\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8108\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"38\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.69\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8250\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"38\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.69\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8016\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"38\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.69\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8047\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"38\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.69\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":null,\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"37\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.57\",\"TIMESTAMP\":\"2025-03-25T12:00:01.000Z\"}]},{\"STOCKER_NAME\":\"HFF09AGH0100\",\"CAPACITY\":\"89.9\",\"MAXIMUM_CAPACITY\":\"406\",\"TRAY_CAPACITY\":\"88.59\",\"MAXIMUM_TRAY_CAPACITY\":\"1218\",\"RACK_LOAD_COUNT\":\"365\",\"RACK_EMPTY_COUNT\":\"41\",\"RESERVATED_RETURN_COUNT\":\"7\",\"TRAY_COUNT\":\"1079\",\"TRAY_REWORK_COUNT_AVG\":\"6\",\"TRAY_REWORK_COUNT_MAX\":\"64\",\"TRAY_REWORK_COUNT_MIN\":\"0\",\"RACK_DISABLE_COUNT\":\"0\",\"KOR_EQP_NAME\":\"고온Aging #01\",\"ENG_EQP_NAME\":\"고온Aging #01\",\"TIMESTAMP\":\"2025-03-25T12:00:00.000Z\",\"STEP\":[{\"STOCKER_NAME\":\"HFF09AGH0100\",\"STEP_ID\":\"8190\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"39\",\"TOTAL\":\"406\",\"STEP_CAPACITY\":\"9.61\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGH0100\",\"STEP_ID\":\"8040\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"39\",\"TOTAL\":\"406\",\"STEP_CAPACITY\":\"9.61\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGH0100\",\"STEP_ID\":\"8016\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"39\",\"TOTAL\":\"406\",\"STEP_CAPACITY\":\"9.61\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGH0100\",\"STEP_ID\":\"8020\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"39\",\"TOTAL\":\"406\",\"STEP_CAPACITY\":\"9.61\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGH0100\",\"STEP_ID\":\"8150\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"39\",\"TOTAL\":\"406\",\"STEP_CAPACITY\":\"9.61\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGH0100\",\"STEP_ID\":\"8024\",\"KOR_STEP_GROUP_NAME\":\"고온 Aging3\",\"ENG_STEP_GROUP_NAME\":\"High Temp Aging3\",\"RACK_STEP_COUNT\":\"251\",\"TOTAL\":\"406\",\"STEP_CAPACITY\":\"61.82\",\"TIMESTAMP\":\"2025-03-25T12:00:01.000Z\"},{\"STOCKER_NAME\":\"HFF09AGH0100\",\"STEP_ID\":\"8028\",\"KOR_STEP_GROUP_NAME\":\"고온 Aging5\",\"ENG_STEP_GROUP_NAME\":\"High Temp Aging5\",\"RACK_STEP_COUNT\":\"1\",\"TOTAL\":\"406\",\"STEP_CAPACITY\":\"0.25\",\"TIMESTAMP\":\"2025-03-25T12:00:01.000Z\"},{\"STOCKER_NAME\":\"HFF09AGH0100\",\"STEP_ID\":\"0\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"113\",\"TOTAL\":\"406\",\"STEP_CAPACITY\":\"27.83\",\"TIMESTAMP\":\"2025-03-25T12:00:01.000Z\"},{\"STOCKER_NAME\":\"HFF09AGH0100\",\"STEP_ID\":\"8025\",\"KOR_STEP_GROUP_NAME\":\"냉각 Aging3\",\"ENG_STEP_GROUP_NAME\":\"Cold Temp Aging3\",\"RACK_STEP_COUNT\":\"1\",\"TOTAL\":\"406\",\"STEP_CAPACITY\":\"0.25\",\"TIMESTAMP\":\"2025-03-25T11:23:53.000Z\"},{\"STOCKER_NAME\":\"HFF09AGH0100\",\"STEP_ID\":\"8022\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"39\",\"TOTAL\":\"406\",\"STEP_CAPACITY\":\"9.61\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGH0100\",\"STEP_ID\":\"8026\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"39\",\"TOTAL\":\"406\",\"STEP_CAPACITY\":\"9.61\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGH0100\",\"STEP_ID\":\"8029\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"39\",\"TOTAL\":\"406\",\"STEP_CAPACITY\":\"9.61\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGH0100\",\"STEP_ID\":null,\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"41\",\"TOTAL\":\"406\",\"STEP_CAPACITY\":\"10.1\",\"TIMESTAMP\":\"2025-03-25T12:00:01.000Z\"}]}]" + "[{\"STOCKER_NAME\": \"HFF09AGN0300\",\"CAPACITY\": \"89.57\",\"MAXIMUM_CAPACITY\": \"834\",\"TRAY_CAPACITY\": \"83.25\",\"MAXIMUM_TRAY_CAPACITY\": \"2502\",\"RACK_LOAD_COUNT\": \"747\",\"RACK_EMPTY_COUNT\": \"87\",\"RESERVATED_RETURN_COUNT\": \"5\",\"TRAY_COUNT\": \"2083\",\"TRAY_REWORK_COUNT_AVG\": \"3\",\"TRAY_REWORK_COUNT_MAX\": \"153\",\"TRAY_REWORK_COUNT_MIN\": \"0\",\"RACK_DISABLE_COUNT\": \"4\",\"KOR_EQP_NAME\": \"상온Aging #03\",\"ENG_EQP_NAME\": \"상온Aging #03\",\"TIMESTAMP\": \"2025-03-25T11:59:56.000Z\",\"STEP\": [{\"STOCKER_NAME\": \"HFF09AGN0300\",\"STEP_ID\": \"8106\",\"KOR_STEP_GROUP_NAME\": null,\"ENG_STEP_GROUP_NAME\": null,\"RACK_STEP_COUNT\": \"88\",\"TOTAL\": \"834\",\"STEP_CAPACITY\": \"10.55\",\"TIMESTAMP\": \"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\": \"HFF09AGN0300\",\"STEP_ID\": \"8220\",\"KOR_STEP_GROUP_NAME\": null,\"ENG_STEP_GROUP_NAME\": null,\"RACK_STEP_COUNT\": \"88\",\"TOTAL\": \"834\",\"STEP_CAPACITY\": \"10.55\",\"TIMESTAMP\": \"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\": \"HFF09AGN0300\",\"STEP_ID\": \"8100\",\"KOR_STEP_GROUP_NAME\": null,\"ENG_STEP_GROUP_NAME\": null,\"RACK_STEP_COUNT\": \"88\",\"TOTAL\": \"834\",\"STEP_CAPACITY\": \"10.55\",\"TIMESTAMP\": \"2025-03-25T08:24:51.000Z\"}]},{\"STOCKER_NAME\": \"HFF09AGM0100\",\"CAPACITY\": \"95.43\",\"MAXIMUM_CAPACITY\": \"810\",\"TRAY_CAPACITY\": \"93.54\",\"MAXIMUM_TRAY_CAPACITY\": \"2430\",\"RACK_LOAD_COUNT\": \"773\",\"RACK_EMPTY_COUNT\": \"37\",\"RESERVATED_RETURN_COUNT\": \"9\",\"TRAY_COUNT\": \"2273\",\"TRAY_REWORK_COUNT_AVG\": \"6\",\"TRAY_REWORK_COUNT_MAX\": \"132\",\"TRAY_REWORK_COUNT_MIN\": \"0\",\"RACK_DISABLE_COUNT\": \"55\",\"KOR_EQP_NAME\": \"출하창고(Module Cell Aging) #01\",\"ENG_EQP_NAME\": \"출하창고(Module Cell Aging) #01\",\"TIMESTAMP\": \"2025-03-25T11:59:56.000Z\",\"STEP\": [{\"STOCKER_NAME\": \"HFF09AGM0100\",\"STEP_ID\": \"8040\",\"KOR_STEP_GROUP_NAME\": null,\"ENG_STEP_GROUP_NAME\": null,\"RACK_STEP_COUNT\": \"38\",\"TOTAL\": \"810\",\"STEP_CAPACITY\": \"4.69\",\"TIMESTAMP\": \"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\": \"HFF09AGM0100\",\"STEP_ID\": \"0\",\"KOR_STEP_GROUP_NAME\": null,\"ENG_STEP_GROUP_NAME\": null,\"RACK_STEP_COUNT\": \"38\",\"TOTAL\": \"810\",\"STEP_CAPACITY\": \"4.69\",\"TIMESTAMP\": \"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\": \"HFF09AGM0100\",\"STEP_ID\": \"8046\",\"KOR_STEP_GROUP_NAME\": null,\"ENG_STEP_GROUP_NAME\": null,\"RACK_STEP_COUNT\": \"38\",\"TOTAL\": \"810\",\"STEP_CAPACITY\": \"4.69\",\"TIMESTAMP\": \"2025-03-25T08:24:51.000Z\"}]},{\"STOCKER_NAME\": \"HFF09AGH0100\",\"CAPACITY\": \"89.9\",\"MAXIMUM_CAPACITY\": \"406\",\"TRAY_CAPACITY\": \"88.59\",\"MAXIMUM_TRAY_CAPACITY\": \"1218\",\"RACK_LOAD_COUNT\": \"365\",\"RACK_EMPTY_COUNT\": \"41\",\"RESERVATED_RETURN_COUNT\": \"7\",\"TRAY_COUNT\": \"1079\",\"TRAY_REWORK_COUNT_AVG\": \"6\",\"TRAY_REWORK_COUNT_MAX\": \"64\",\"TRAY_REWORK_COUNT_MIN\": \"0\",\"RACK_DISABLE_COUNT\": \"0\",\"KOR_EQP_NAME\": \"고온Aging #01\",\"ENG_EQP_NAME\": \"고온Aging #01\",\"TIMESTAMP\": \"2025-03-25T11:59:56.000Z\",\"STEP\": [{\"STOCKER_NAME\": \"HFF09AGH0100\",\"STEP_ID\": \"8190\",\"KOR_STEP_GROUP_NAME\": null,\"ENG_STEP_GROUP_NAME\": null,\"RACK_STEP_COUNT\": \"39\",\"TOTAL\": \"406\",\"STEP_CAPACITY\": \"9.61\",\"TIMESTAMP\": \"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\": \"HFF09AGH0100\",\"STEP_ID\": \"8040\",\"KOR_STEP_GROUP_NAME\": null,\"ENG_STEP_GROUP_NAME\": null,\"RACK_STEP_COUNT\": \"39\",\"TOTAL\": \"406\",\"STEP_CAPACITY\": \"9.61\",\"TIMESTAMP\": \"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\": \"HFF09AGH0100\",\"STEP_ID\": \"8016\",\"KOR_STEP_GROUP_NAME\": null,\"ENG_STEP_GROUP_NAME\": null,\"RACK_STEP_COUNT\": \"39\",\"TOTAL\": \"406\",\"STEP_CAPACITY\": \"9.61\",\"TIMESTAMP\": \"2025-03-25T08:24:51.000Z\"}]}]", + "[{\"STOCKER_NAME\": \"HFF09AGN0300\",\"CAPACITY\": \"89.57\",\"MAXIMUM_CAPACITY\": \"834\",\"TRAY_CAPACITY\": \"83.25\",\"MAXIMUM_TRAY_CAPACITY\": \"2502\",\"RACK_LOAD_COUNT\": \"747\",\"RACK_EMPTY_COUNT\": \"87\",\"RESERVATED_RETURN_COUNT\": \"5\",\"TRAY_COUNT\": \"2083\",\"TRAY_REWORK_COUNT_AVG\": \"3\",\"TRAY_REWORK_COUNT_MAX\": \"153\",\"TRAY_REWORK_COUNT_MIN\": \"0\",\"RACK_DISABLE_COUNT\": \"4\",\"KOR_EQP_NAME\": \"상온Aging #03\",\"ENG_EQP_NAME\": \"상온Aging #03\",\"TIMESTAMP\": \"2025-03-25T11:59:56.000Z\",\"STEP\": [{\"STOCKER_NAME\": \"HFF09AGN0300\",\"STEP_ID\": \"8106\",\"KOR_STEP_GROUP_NAME\": null,\"ENG_STEP_GROUP_NAME\": null,\"RACK_STEP_COUNT\": \"88\",\"TOTAL\": \"834\",\"STEP_CAPACITY\": \"10.55\",\"TIMESTAMP\": \"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\": \"HFF09AGN0300\",\"STEP_ID\": \"8220\",\"KOR_STEP_GROUP_NAME\": null,\"ENG_STEP_GROUP_NAME\": null,\"RACK_STEP_COUNT\": \"88\",\"TOTAL\": \"834\",\"STEP_CAPACITY\": \"10.55\",\"TIMESTAMP\": \"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\": \"HFF09AGN0300\",\"STEP_ID\": \"8100\",\"KOR_STEP_GROUP_NAME\": null,\"ENG_STEP_GROUP_NAME\": null,\"RACK_STEP_COUNT\": \"88\",\"TOTAL\": \"834\",\"STEP_CAPACITY\": \"10.55\",\"TIMESTAMP\": \"2025-03-25T08:24:51.000Z\"}]},{\"STOCKER_NAME\": \"HFF09AGM0100\",\"CAPACITY\": \"95.43\",\"MAXIMUM_CAPACITY\": \"810\",\"TRAY_CAPACITY\": \"93.54\",\"MAXIMUM_TRAY_CAPACITY\": \"2430\",\"RACK_LOAD_COUNT\": \"773\",\"RACK_EMPTY_COUNT\": \"37\",\"RESERVATED_RETURN_COUNT\": \"9\",\"TRAY_COUNT\": \"2273\",\"TRAY_REWORK_COUNT_AVG\": \"6\",\"TRAY_REWORK_COUNT_MAX\": \"132\",\"TRAY_REWORK_COUNT_MIN\": \"0\",\"RACK_DISABLE_COUNT\": \"55\",\"KOR_EQP_NAME\": \"출하창고(Module Cell Aging) #01\",\"ENG_EQP_NAME\": \"출하창고(Module Cell Aging) #01\",\"TIMESTAMP\": \"2025-03-25T11:59:56.000Z\",\"STEP\": [{\"STOCKER_NAME\": \"HFF09AGM0100\",\"STEP_ID\": \"8040\",\"KOR_STEP_GROUP_NAME\": null,\"ENG_STEP_GROUP_NAME\": null,\"RACK_STEP_COUNT\": \"38\",\"TOTAL\": \"810\",\"STEP_CAPACITY\": \"4.69\",\"TIMESTAMP\": \"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\": \"HFF09AGM0100\",\"STEP_ID\": \"0\",\"KOR_STEP_GROUP_NAME\": null,\"ENG_STEP_GROUP_NAME\": null,\"RACK_STEP_COUNT\": \"38\",\"TOTAL\": \"810\",\"STEP_CAPACITY\": \"4.69\",\"TIMESTAMP\": \"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\": \"HFF09AGM0100\",\"STEP_ID\": \"8046\",\"KOR_STEP_GROUP_NAME\": null,\"ENG_STEP_GROUP_NAME\": null,\"RACK_STEP_COUNT\": \"38\",\"TOTAL\": \"810\",\"STEP_CAPACITY\": \"4.69\",\"TIMESTAMP\": \"2025-03-25T08:24:51.000Z\"}]},{\"STOCKER_NAME\": \"HFF09AGH0100\",\"CAPACITY\": \"89.9\",\"MAXIMUM_CAPACITY\": \"406\",\"TRAY_CAPACITY\": \"88.59\",\"MAXIMUM_TRAY_CAPACITY\": \"1218\",\"RACK_LOAD_COUNT\": \"365\",\"RACK_EMPTY_COUNT\": \"41\",\"RESERVATED_RETURN_COUNT\": \"7\",\"TRAY_COUNT\": \"1079\",\"TRAY_REWORK_COUNT_AVG\": \"6\",\"TRAY_REWORK_COUNT_MAX\": \"64\",\"TRAY_REWORK_COUNT_MIN\": \"0\",\"RACK_DISABLE_COUNT\": \"0\",\"KOR_EQP_NAME\": \"고온Aging #01\",\"ENG_EQP_NAME\": \"고온Aging #01\",\"TIMESTAMP\": \"2025-03-25T11:59:56.000Z\",\"STEP\": [{\"STOCKER_NAME\": \"HFF09AGH0100\",\"STEP_ID\": \"8190\",\"KOR_STEP_GROUP_NAME\": null,\"ENG_STEP_GROUP_NAME\": null,\"RACK_STEP_COUNT\": \"39\",\"TOTAL\": \"406\",\"STEP_CAPACITY\": \"9.61\",\"TIMESTAMP\": \"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\": \"HFF09AGH0100\",\"STEP_ID\": \"8040\",\"KOR_STEP_GROUP_NAME\": null,\"ENG_STEP_GROUP_NAME\": null,\"RACK_STEP_COUNT\": \"39\",\"TOTAL\": \"406\",\"STEP_CAPACITY\": \"9.61\",\"TIMESTAMP\": \"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\": \"HFF09AGH0100\",\"STEP_ID\": \"8016\",\"KOR_STEP_GROUP_NAME\": null,\"ENG_STEP_GROUP_NAME\": null,\"RACK_STEP_COUNT\": \"39\",\"TOTAL\": \"406\",\"STEP_CAPACITY\": \"9.61\",\"TIMESTAMP\": \"2025-03-25T08:24:51.000Z\"}]}]", + "[{\"STOCKER_NAME\": \"HFF09AGN0300\",\"CAPACITY\": \"89.57\",\"MAXIMUM_CAPACITY\": \"834\",\"TRAY_CAPACITY\": \"83.25\",\"MAXIMUM_TRAY_CAPACITY\": \"2502\",\"RACK_LOAD_COUNT\": \"747\",\"RACK_EMPTY_COUNT\": \"87\",\"RESERVATED_RETURN_COUNT\": \"5\",\"TRAY_COUNT\": \"2083\",\"TRAY_REWORK_COUNT_AVG\": \"3\",\"TRAY_REWORK_COUNT_MAX\": \"153\",\"TRAY_REWORK_COUNT_MIN\": \"0\",\"RACK_DISABLE_COUNT\": \"4\",\"KOR_EQP_NAME\": \"상온Aging #03\",\"ENG_EQP_NAME\": \"상온Aging #03\",\"TIMESTAMP\": \"2025-03-25T12:00:00.000Z\",\"STEP\": [{\"STOCKER_NAME\": \"HFF09AGN0300\",\"STEP_ID\": \"8106\",\"KOR_STEP_GROUP_NAME\": null,\"ENG_STEP_GROUP_NAME\": null,\"RACK_STEP_COUNT\": \"88\",\"TOTAL\": \"834\",\"STEP_CAPACITY\": \"10.55\",\"TIMESTAMP\": \"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\": \"HFF09AGN0300\",\"STEP_ID\": \"8220\",\"KOR_STEP_GROUP_NAME\": null,\"ENG_STEP_GROUP_NAME\": null,\"RACK_STEP_COUNT\": \"88\",\"TOTAL\": \"834\",\"STEP_CAPACITY\": \"10.55\",\"TIMESTAMP\": \"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\": \"HFF09AGN0300\",\"STEP_ID\": \"8100\",\"KOR_STEP_GROUP_NAME\": null,\"ENG_STEP_GROUP_NAME\": null,\"RACK_STEP_COUNT\": \"88\",\"TOTAL\": \"834\",\"STEP_CAPACITY\": \"10.55\",\"TIMESTAMP\": \"2025-03-25T08:24:51.000Z\"}]},{\"STOCKER_NAME\": \"HFF09AGM0100\",\"CAPACITY\": \"95.43\",\"MAXIMUM_CAPACITY\": \"810\",\"TRAY_CAPACITY\": \"93.54\",\"MAXIMUM_TRAY_CAPACITY\": \"2430\",\"RACK_LOAD_COUNT\": \"773\",\"RACK_EMPTY_COUNT\": \"37\",\"RESERVATED_RETURN_COUNT\": \"9\",\"TRAY_COUNT\": \"2273\",\"TRAY_REWORK_COUNT_AVG\": \"6\",\"TRAY_REWORK_COUNT_MAX\": \"132\",\"TRAY_REWORK_COUNT_MIN\": \"0\",\"RACK_DISABLE_COUNT\": \"55\",\"KOR_EQP_NAME\": \"출하창고(Module Cell Aging) #01\",\"ENG_EQP_NAME\": \"출하창고(Module Cell Aging) #01\",\"TIMESTAMP\": \"2025-03-25T12:00:00.000Z\",\"STEP\": [{\"STOCKER_NAME\": \"HFF09AGM0100\",\"STEP_ID\": \"8040\",\"KOR_STEP_GROUP_NAME\": null,\"ENG_STEP_GROUP_NAME\": null,\"RACK_STEP_COUNT\": \"38\",\"TOTAL\": \"810\",\"STEP_CAPACITY\": \"4.69\",\"TIMESTAMP\": \"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\": \"HFF09AGM0100\",\"STEP_ID\": \"0\",\"KOR_STEP_GROUP_NAME\": null,\"ENG_STEP_GROUP_NAME\": null,\"RACK_STEP_COUNT\": \"38\",\"TOTAL\": \"810\",\"STEP_CAPACITY\": \"4.69\",\"TIMESTAMP\": \"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\": \"HFF09AGM0100\",\"STEP_ID\": \"8046\",\"KOR_STEP_GROUP_NAME\": null,\"ENG_STEP_GROUP_NAME\": null,\"RACK_STEP_COUNT\": \"38\",\"TOTAL\": \"810\",\"STEP_CAPACITY\": \"4.69\",\"TIMESTAMP\": \"2025-03-25T08:24:51.000Z\"}]},{\"STOCKER_NAME\": \"HFF09AGH0100\",\"CAPACITY\": \"89.9\",\"MAXIMUM_CAPACITY\": \"406\",\"TRAY_CAPACITY\": \"88.59\",\"MAXIMUM_TRAY_CAPACITY\": \"1218\",\"RACK_LOAD_COUNT\": \"365\",\"RACK_EMPTY_COUNT\": \"41\",\"RESERVATED_RETURN_COUNT\": \"7\",\"TRAY_COUNT\": \"1079\",\"TRAY_REWORK_COUNT_AVG\": \"6\",\"TRAY_REWORK_COUNT_MAX\": \"64\",\"TRAY_REWORK_COUNT_MIN\": \"0\",\"RACK_DISABLE_COUNT\": \"0\",\"KOR_EQP_NAME\": \"고온Aging #01\",\"ENG_EQP_NAME\": \"고온Aging #01\",\"TIMESTAMP\": \"2025-03-25T12:00:00.000Z\",\"STEP\": [{\"STOCKER_NAME\": \"HFF09AGH0100\",\"STEP_ID\": \"8190\",\"KOR_STEP_GROUP_NAME\": null,\"ENG_STEP_GROUP_NAME\": null,\"RACK_STEP_COUNT\": \"39\",\"TOTAL\": \"406\",\"STEP_CAPACITY\": \"9.61\",\"TIMESTAMP\": \"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\": \"HFF09AGH0100\",\"STEP_ID\": \"8040\",\"KOR_STEP_GROUP_NAME\": null,\"ENG_STEP_GROUP_NAME\": null,\"RACK_STEP_COUNT\": \"39\",\"TOTAL\": \"406\",\"STEP_CAPACITY\": \"9.61\",\"TIMESTAMP\": \"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\": \"HFF09AGH0100\",\"STEP_ID\": \"8016\",\"KOR_STEP_GROUP_NAME\": null,\"ENG_STEP_GROUP_NAME\": null,\"RACK_STEP_COUNT\": \"39\",\"TOTAL\": \"406\",\"STEP_CAPACITY\": \"9.61\",\"TIMESTAMP\": \"2025-03-25T08:24:51.000Z\"}]}]" }; private string[] baseInfoMessage = new string[]{ "{\r\n \"AGV\":[{\"VHL_NAME\":\"HFF09CNA8053\",\"AGV_IDX\":52,\"B_INSTALL\":\"Y\",\"NODE_ID\":99226,\"REAL_ID\":99226,\"VHL_STATE\":11,\"BAY_LIST\":\"2;\",\"X\":223316,\"Y\":218171,\"MODE\":1,\"BATT\":69,\"SUB_GOAL\":1657,\"FINAL_GOAL\":81044,\"TIMESTAMP\":\"2025-03-25T11:59:58.952Z\",\"DEGREE\":359.7,\"CARRIER_ID\":null,\"STOP_STATE\":0,\"LOT_ID\":null,\"BATCH_ID\":null,\"CARRIER_TIMESTAMP\":null,\"JOB_ID\":null,\"DESTINATION_PORT\":null,\"SOURCE_PORT\":null,\"FROM\":null,\"TO\":null,\"TRANSPORT_JOB_TIMESTAMP\":null,\"FACTOR\":51.88,\"AGV_FACTOR_TIMESTAMP\":\"2025-03-25T11:59:04.000Z\"},{\"VHL_NAME\":\"HFF09CNA8033\",\"AGV_IDX\":32,\"B_INSTALL\":\"Y\",\"NODE_ID\":734,\"REAL_ID\":734,\"VHL_STATE\":11,\"BAY_LIST\":\"5;15;\",\"X\":136275,\"Y\":112783,\"MODE\":1,\"BATT\":74,\"SUB_GOAL\":182,\"FINAL_GOAL\":99107,\"TIMESTAMP\":\"2025-03-25T11:59:59.968Z\",\"DEGREE\":180.2,\"CARRIER_ID\":\"2F22537,2F08542,2F06190\",\"STOP_STATE\":0,\"LOT_ID\":\"36092531461205BKDS1B\",\"BATCH_ID\":\"H1I70SA253FG21\",\"CARRIER_TIMESTAMP\":\"2025-03-25T11:58:11.000Z\",\"JOB_ID\":\"2F22537_260_7060790056483393\",\"DESTINATION_PORT\":\"HFB11CNV0100_LIP02\",\"SOURCE_PORT\":\"HFF09AGN0300_UOP04\",\"FROM\":\"HFF09AGN0300,null,0202204\",\"TO\":\"HFB11CNV0100,HFB11CNV0100_UOP03,null\",\"TRANSPORT_JOB_TIMESTAMP\":\"2025-03-25T11:57:57.000Z\",\"FACTOR\":67.91,\"AGV_FACTOR_TIMESTAMP\":\"2025-03-25T11:59:04.000Z\"},{\"VHL_NAME\":\"HFF09CNA8051\",\"AGV_IDX\":50,\"B_INSTALL\":\"Y\",\"NODE_ID\":80004,\"REAL_ID\":80004,\"VHL_STATE\":94,\"BAY_LIST\":\"8;15;\",\"X\":250153,\"Y\":115715,\"MODE\":1,\"BATT\":79,\"SUB_GOAL\":0,\"FINAL_GOAL\":80004,\"TIMESTAMP\":\"2025-03-25T11:59:58.959Z\",\"DEGREE\":269.3,\"CARRIER_ID\":null,\"STOP_STATE\":0,\"LOT_ID\":null,\"BATCH_ID\":null,\"CARRIER_TIMESTAMP\":null,\"JOB_ID\":\"2F50746_398_7061037573669158\",\"DESTINATION_PORT\":\"HFB11CNV0100_LIP02\",\"SOURCE_PORT\":\"HFF11AGN0100_UOP04\",\"FROM\":\"HFF11AGN0100,null,0102111\",\"TO\":\"HFB11CNV0100,HFB11CNV0100_UOP03,null\",\"TRANSPORT_JOB_TIMESTAMP\":\"2025-03-25T11:59:57.000Z\",\"FACTOR\":70.37,\"AGV_FACTOR_TIMESTAMP\":\"2025-03-25T11:59:04.000Z\"}],\r\n \"STOCKER_STACK\":[{\"STOCKER_NAME\":\"HFF09AGN0300\",\"CAPACITY\":\"89.57\",\"MAXIMUM_CAPACITY\":\"834\",\"TRAY_CAPACITY\":\"83.25\",\"MAXIMUM_TRAY_CAPACITY\":\"2502\",\"RACK_LOAD_COUNT\":\"747\",\"RACK_EMPTY_COUNT\":\"87\",\"RESERVATED_RETURN_COUNT\":\"5\",\"TRAY_COUNT\":\"2083\",\"TRAY_REWORK_COUNT_AVG\":\"3\",\"TRAY_REWORK_COUNT_MAX\":\"153\",\"TRAY_REWORK_COUNT_MIN\":\"0\",\"RACK_DISABLE_COUNT\":\"4\",\"KOR_EQP_NAME\":\"상온Aging #03\",\"ENG_EQP_NAME\":\"상온Aging #03\",\"TIMESTAMP\":\"2025-03-25T11:59:56.000Z\",\"STEP\":[{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8106\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8220\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8100\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8038\",\"KOR_STEP_GROUP_NAME\":\"디게싱에이징1\",\"ENG_STEP_GROUP_NAME\":\"Deggassing Aging1\",\"RACK_STEP_COUNT\":\"8\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"0.96\",\"TIMESTAMP\":\"2025-03-25T11:59:59.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8150\",\"KOR_STEP_GROUP_NAME\":\"용량검사\",\"ENG_STEP_GROUP_NAME\":\"Capacity Inspection\",\"RACK_STEP_COUNT\":\"1\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"0.12\",\"TIMESTAMP\":\"2025-03-25T11:21:18.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8136\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8134\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8116\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8028\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8012\",\"KOR_STEP_GROUP_NAME\":\"상온 Aging2\",\"ENG_STEP_GROUP_NAME\":\"Normal Temp Aging2\",\"RACK_STEP_COUNT\":\"24\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"2.88\",\"TIMESTAMP\":\"2025-03-25T11:59:59.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8057\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8070\",\"KOR_STEP_GROUP_NAME\":\"호퍼 pre-c 전 임시밀폐제거\",\"ENG_STEP_GROUP_NAME\":\"Temp Sealing Remove(Pre-C)\",\"RACK_STEP_COUNT\":\"1\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"0.12\",\"TIMESTAMP\":\"2025-03-25T10:56:56.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8140\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8138\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8025\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8010\",\"KOR_STEP_GROUP_NAME\":\"상온 Aging1\",\"ENG_STEP_GROUP_NAME\":\"Normal Temp Aging1\",\"RACK_STEP_COUNT\":\"123\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"14.75\",\"TIMESTAMP\":\"2025-03-25T11:59:59.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8014\",\"KOR_STEP_GROUP_NAME\":\"상온 Aging3\",\"ENG_STEP_GROUP_NAME\":\"Normal Temp Aging3\",\"RACK_STEP_COUNT\":\"106\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"12.71\",\"TIMESTAMP\":\"2025-03-25T11:59:59.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8016\",\"KOR_STEP_GROUP_NAME\":\"상온 Aging4\",\"ENG_STEP_GROUP_NAME\":\"Normal Temp Aging4\",\"RACK_STEP_COUNT\":\"117\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"14.03\",\"TIMESTAMP\":\"2025-03-25T11:59:59.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8040\",\"KOR_STEP_GROUP_NAME\":\"출하 Aging\",\"ENG_STEP_GROUP_NAME\":\"G/I Aging\",\"RACK_STEP_COUNT\":\"215\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"25.78\",\"TIMESTAMP\":\"2025-03-25T11:59:59.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8042\",\"KOR_STEP_GROUP_NAME\":\"상온 Aging5\",\"ENG_STEP_GROUP_NAME\":\"Normal Temp Aging5\",\"RACK_STEP_COUNT\":\"3\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"0.36\",\"TIMESTAMP\":\"2025-03-25T11:59:59.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8044\",\"KOR_STEP_GROUP_NAME\":\"상온 Aging6\",\"ENG_STEP_GROUP_NAME\":\"Normal Temp Aging6\",\"RACK_STEP_COUNT\":\"6\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"0.72\",\"TIMESTAMP\":\"2025-03-25T11:59:59.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8045\",\"KOR_STEP_GROUP_NAME\":\"상온 Aging7\",\"ENG_STEP_GROUP_NAME\":\"Normal Temp Aging7\",\"RACK_STEP_COUNT\":\"21\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"2.52\",\"TIMESTAMP\":\"2025-03-25T11:59:59.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8046\",\"KOR_STEP_GROUP_NAME\":\"상온 Aging8\",\"ENG_STEP_GROUP_NAME\":\"Normal Temp Aging8\",\"RACK_STEP_COUNT\":\"3\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"0.36\",\"TIMESTAMP\":\"2025-03-25T11:59:59.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8190\",\"KOR_STEP_GROUP_NAME\":\"방치 AGING #1\",\"ENG_STEP_GROUP_NAME\":\"Boxing Aging 1\",\"RACK_STEP_COUNT\":\"1\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"0.12\",\"TIMESTAMP\":\"2025-03-25T11:59:59.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8400\",\"KOR_STEP_GROUP_NAME\":\"포장\",\"ENG_STEP_GROUP_NAME\":\"Packing\",\"RACK_STEP_COUNT\":\"76\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"9.11\",\"TIMESTAMP\":\"2025-03-25T11:59:59.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8132\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8245\",\"KOR_STEP_GROUP_NAME\":\"외관검사1\",\"ENG_STEP_GROUP_NAME\":\"External Inspection 1\",\"RACK_STEP_COUNT\":\"1\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"0.12\",\"TIMESTAMP\":\"2025-03-25T10:41:12.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8182\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8108\",\"KOR_STEP_GROUP_NAME\":\"출하 IR/OCV\",\"ENG_STEP_GROUP_NAME\":\"G/I IR/OCV\",\"RACK_STEP_COUNT\":\"1\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"0.12\",\"TIMESTAMP\":\"2025-03-25T11:04:49.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8102\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8192\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"0\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"44\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"5.28\",\"TIMESTAMP\":\"2025-03-25T11:59:59.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8047\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8104\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8024\",\"KOR_STEP_GROUP_NAME\":\"고온 Aging3\",\"ENG_STEP_GROUP_NAME\":\"High Temp Aging3\",\"RACK_STEP_COUNT\":\"1\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"0.12\",\"TIMESTAMP\":\"2025-03-25T09:08:25.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":\"8130\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"88\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.55\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGN0300\",\"STEP_ID\":null,\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"87\",\"TOTAL\":\"834\",\"STEP_CAPACITY\":\"10.43\",\"TIMESTAMP\":\"2025-03-25T11:59:59.000Z\"}]},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"CAPACITY\":\"95.43\",\"MAXIMUM_CAPACITY\":\"810\",\"TRAY_CAPACITY\":\"93.54\",\"MAXIMUM_TRAY_CAPACITY\":\"2430\",\"RACK_LOAD_COUNT\":\"773\",\"RACK_EMPTY_COUNT\":\"37\",\"RESERVATED_RETURN_COUNT\":\"9\",\"TRAY_COUNT\":\"2273\",\"TRAY_REWORK_COUNT_AVG\":\"6\",\"TRAY_REWORK_COUNT_MAX\":\"132\",\"TRAY_REWORK_COUNT_MIN\":\"0\",\"RACK_DISABLE_COUNT\":\"55\",\"KOR_EQP_NAME\":\"출하창고(Module Cell Aging) #01\",\"ENG_EQP_NAME\":\"출하창고(Module Cell Aging) #01\",\"TIMESTAMP\":\"2025-03-25T11:59:56.000Z\",\"STEP\":[{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8040\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"38\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.69\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"0\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"38\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.69\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8046\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"38\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.69\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8192\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"38\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.69\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8190\",\"KOR_STEP_GROUP_NAME\":\"방치 AGING #1\",\"ENG_STEP_GROUP_NAME\":\"Boxing Aging 1\",\"RACK_STEP_COUNT\":\"5\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"0.62\",\"TIMESTAMP\":\"2025-03-25T11:59:59.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8400\",\"KOR_STEP_GROUP_NAME\":\"포장\",\"ENG_STEP_GROUP_NAME\":\"Packing\",\"RACK_STEP_COUNT\":\"768\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"94.81\",\"TIMESTAMP\":\"2025-03-25T11:59:59.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8245\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"38\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.69\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8106\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"38\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.69\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8010\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"38\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.69\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8182\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"38\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.69\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8220\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"38\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.69\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8014\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"38\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.69\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8108\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"38\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.69\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8250\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"38\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.69\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8016\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"38\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.69\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":\"8047\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"38\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.69\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGM0100\",\"STEP_ID\":null,\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"37\",\"TOTAL\":\"810\",\"STEP_CAPACITY\":\"4.57\",\"TIMESTAMP\":\"2025-03-25T11:59:59.000Z\"}]},{\"STOCKER_NAME\":\"HFF09AGH0100\",\"CAPACITY\":\"89.9\",\"MAXIMUM_CAPACITY\":\"406\",\"TRAY_CAPACITY\":\"88.59\",\"MAXIMUM_TRAY_CAPACITY\":\"1218\",\"RACK_LOAD_COUNT\":\"365\",\"RACK_EMPTY_COUNT\":\"41\",\"RESERVATED_RETURN_COUNT\":\"7\",\"TRAY_COUNT\":\"1079\",\"TRAY_REWORK_COUNT_AVG\":\"6\",\"TRAY_REWORK_COUNT_MAX\":\"64\",\"TRAY_REWORK_COUNT_MIN\":\"0\",\"RACK_DISABLE_COUNT\":\"0\",\"KOR_EQP_NAME\":\"고온Aging #01\",\"ENG_EQP_NAME\":\"고온Aging #01\",\"TIMESTAMP\":\"2025-03-25T11:59:56.000Z\",\"STEP\":[{\"STOCKER_NAME\":\"HFF09AGH0100\",\"STEP_ID\":\"8190\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"39\",\"TOTAL\":\"406\",\"STEP_CAPACITY\":\"9.61\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGH0100\",\"STEP_ID\":\"8040\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"39\",\"TOTAL\":\"406\",\"STEP_CAPACITY\":\"9.61\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGH0100\",\"STEP_ID\":\"8016\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"39\",\"TOTAL\":\"406\",\"STEP_CAPACITY\":\"9.61\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGH0100\",\"STEP_ID\":\"8020\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"39\",\"TOTAL\":\"406\",\"STEP_CAPACITY\":\"9.61\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGH0100\",\"STEP_ID\":\"8150\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"39\",\"TOTAL\":\"406\",\"STEP_CAPACITY\":\"9.61\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGH0100\",\"STEP_ID\":\"8024\",\"KOR_STEP_GROUP_NAME\":\"고온 Aging3\",\"ENG_STEP_GROUP_NAME\":\"High Temp Aging3\",\"RACK_STEP_COUNT\":\"251\",\"TOTAL\":\"406\",\"STEP_CAPACITY\":\"61.82\",\"TIMESTAMP\":\"2025-03-25T11:59:59.000Z\"},{\"STOCKER_NAME\":\"HFF09AGH0100\",\"STEP_ID\":\"8028\",\"KOR_STEP_GROUP_NAME\":\"고온 Aging5\",\"ENG_STEP_GROUP_NAME\":\"High Temp Aging5\",\"RACK_STEP_COUNT\":\"1\",\"TOTAL\":\"406\",\"STEP_CAPACITY\":\"0.25\",\"TIMESTAMP\":\"2025-03-25T11:59:59.000Z\"},{\"STOCKER_NAME\":\"HFF09AGH0100\",\"STEP_ID\":\"0\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"113\",\"TOTAL\":\"406\",\"STEP_CAPACITY\":\"27.83\",\"TIMESTAMP\":\"2025-03-25T11:59:59.000Z\"},{\"STOCKER_NAME\":\"HFF09AGH0100\",\"STEP_ID\":\"8025\",\"KOR_STEP_GROUP_NAME\":\"냉각 Aging3\",\"ENG_STEP_GROUP_NAME\":\"Cold Temp Aging3\",\"RACK_STEP_COUNT\":\"1\",\"TOTAL\":\"406\",\"STEP_CAPACITY\":\"0.25\",\"TIMESTAMP\":\"2025-03-25T11:23:53.000Z\"},{\"STOCKER_NAME\":\"HFF09AGH0100\",\"STEP_ID\":\"8022\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"39\",\"TOTAL\":\"406\",\"STEP_CAPACITY\":\"9.61\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGH0100\",\"STEP_ID\":\"8026\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"39\",\"TOTAL\":\"406\",\"STEP_CAPACITY\":\"9.61\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGH0100\",\"STEP_ID\":\"8029\",\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"39\",\"TOTAL\":\"406\",\"STEP_CAPACITY\":\"9.61\",\"TIMESTAMP\":\"2025-03-25T08:24:51.000Z\"},{\"STOCKER_NAME\":\"HFF09AGH0100\",\"STEP_ID\":null,\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"RACK_STEP_COUNT\":\"41\",\"TOTAL\":\"406\",\"STEP_CAPACITY\":\"10.1\",\"TIMESTAMP\":\"2025-03-25T11:59:59.000Z\"}]}],\r\n \"CARRIER\":[{\"MAIN_CARR_ID\":\"2F02365\",\"SUB_CARR_ID\":\"2F02365,2F70671,2F28723\",\"CARR_SEQ\":\"3\",\"CARR_USE\":\"EMPTY\",\"CURRENTPORT\":\"HFF09CNV0300_ABP3003\",\"CURRENTRACK\":null,\"MOVE_JOBID\":null,\"MOVESTATUS\":\"ARRIVED\",\"FINALTOOLID\":null,\"MOVEFLAG\":\"0\",\"PROD_ID\":null,\"FTY_NO\":null,\"WORK_TYPE\":null,\"MFG_TYPE\":null,\"PROD_DETAIL_CODE\":\"E3A\",\"STEP_ID\":null,\"NEXT_STEP_ID\":null,\"ASSIGN_LOT_QTY\":\"0\",\"FRMT_BATCH_ID\":null,\"CARR_SIZE_TYPE\":\"STACK3\",\"ABNM_VALUE\":\"0\",\"LINE_ID\":\"FM0I\",\"TIMESTAMP\":\"2025-03-25T11:59:57.000Z\",\"INPUT_QTY\":null,\"GOOD_QTY\":\"0\",\"BAD_QTY\":null,\"KOR_STEP_GROUP_NAME\":null,\"ENG_STEP_GROUP_NAME\":null,\"NEXT_KOR_STEP_GROUP_NAME\":null,\"NEXT_ENG_STEP_GROUP_NAME\":null,\"LOT_ID\":null,\"CTH_REEL_ID\":null,\"ANODE_REEL_ID\":null,\"CARR_NO\":null,\"BATCH_GUBUN\":null,\"PROC_IN_TIME\":null,\"IN_CARR_QTY\":null,\"LAST_TKIN_TIME\":null,\"VHCL_ID\":null,\"FIRST_FRMT_INPUT_TIME\":null,\"CURRENTLOCATION\":\"HFF09CNV0300\",\"JOB_ID\":null,\"FROM_PORT\":null,\"TO_PORT\":null,\"TRANSPORT_JOB_TIMESTAMP\":null},{\"MAIN_CARR_ID\":\"2F50746\",\"SUB_CARR_ID\":\"2F50746,2F10132,2F53089\",\"CARR_SEQ\":\"3\",\"CARR_USE\":\"FULL\",\"CURRENTPORT\":\"HFF11AGN0100_UOP04\",\"CURRENTRACK\":null,\"MOVE_JOBID\":\"2F50746_398_7061037573669158\",\"MOVESTATUS\":\"ARRIVED\",\"FINALTOOLID\":\"HFB11CNV0100_UOP03\",\"MOVEFLAG\":\"1\",\"PROD_ID\":\"CP7024F111A\",\"FTY_NO\":\"70B0\",\"WORK_TYPE\":\"NM\",\"MFG_TYPE\":\"PP02\",\"PROD_DETAIL_CODE\":\"E3A\",\"STEP_ID\":\"8182\",\"NEXT_STEP_ID\":\"8250\",\"ASSIGN_LOT_QTY\":\"288\",\"FRMT_BATCH_ID\":\"H1I70SA253FG21\",\"CARR_SIZE_TYPE\":\"STACK3\",\"ABNM_VALUE\":\"0\",\"LINE_ID\":\"FM0I\",\"TIMESTAMP\":\"2025-03-25T11:59:53.000Z\",\"INPUT_QTY\":\"288\",\"GOOD_QTY\":\"288\",\"BAD_QTY\":null,\"KOR_STEP_GROUP_NAME\":\"방치 IR/OCV #2\",\"ENG_STEP_GROUP_NAME\":\"Boxing IR/OCV 2\",\"NEXT_KOR_STEP_GROUP_NAME\":\"외관검사\",\"NEXT_ENG_STEP_GROUP_NAME\":\"External Inspection\",\"LOT_ID\":\"36092531465248BKDS1B\",\"CTH_REEL_ID\":\"H16CP25312A115D\",\"ANODE_REEL_ID\":\"H16AP25312A131F\",\"CARR_NO\":\"H1I70SB252A313-3119\",\"BATCH_GUBUN\":\"PP02\",\"PROC_IN_TIME\":\"2025-03-14T06:06:39.000Z\",\"IN_CARR_QTY\":\"267\",\"LAST_TKIN_TIME\":\"2025-03-24T04:57:10.000Z\",\"VHCL_ID\":null,\"FIRST_FRMT_INPUT_TIME\":\"2025-03-14T06:06:42.000Z\",\"CURRENTLOCATION\":\"HFF11AGN0100\",\"JOB_ID\":\"2F50746_398_7061037573669158\",\"FROM_PORT\":\"HFF11AGN0100,null,0102111\",\"TO_PORT\":\"HFB11CNV0100,HFB11CNV0100_UOP03,null\",\"TRANSPORT_JOB_TIMESTAMP\":\"2025-03-25T11:59:53.000Z\"},{\"MAIN_CARR_ID\":\"2F52504\",\"SUB_CARR_ID\":\"2F52504,2F11450,2F51910\",\"CARR_SEQ\":\"3\",\"CARR_USE\":\"FULL\",\"CURRENTPORT\":\"HFF09CDS0200_UBP02\",\"CURRENTRACK\":null,\"MOVE_JOBID\":null,\"MOVESTATUS\":\"ARRIVED\",\"FINALTOOLID\":null,\"MOVEFLAG\":\"0\",\"PROD_ID\":\"CP7024F111A\",\"FTY_NO\":\"70B0\",\"WORK_TYPE\":\"NM\",\"MFG_TYPE\":\"PP02\",\"PROD_DETAIL_CODE\":\"E3A\",\"STEP_ID\":\"8024\",\"NEXT_STEP_ID\":\"8025\",\"ASSIGN_LOT_QTY\":\"288\",\"FRMT_BATCH_ID\":\"H1J70SA253HO22\",\"CARR_SIZE_TYPE\":\"STACK3\",\"ABNM_VALUE\":\"0\",\"LINE_ID\":\"FM0I\",\"TIMESTAMP\":\"2025-03-25T11:59:57.000Z\",\"INPUT_QTY\":\"288\",\"GOOD_QTY\":\"288\",\"BAD_QTY\":null,\"KOR_STEP_GROUP_NAME\":\"고온 Aging3\",\"ENG_STEP_GROUP_NAME\":\"High Temp Aging3\",\"NEXT_KOR_STEP_GROUP_NAME\":\"냉각 Aging3\",\"NEXT_ENG_STEP_GROUP_NAME\":\"Cold Temp Aging3\",\"LOT_ID\":\"36092532270707BKDS1B\",\"CTH_REEL_ID\":\"H16CP25310A118D\",\"ANODE_REEL_ID\":\"H16AP25321A113E\",\"CARR_NO\":\"H1J70SA251DC13-5524\",\"BATCH_GUBUN\":\"PP02\",\"PROC_IN_TIME\":\"2025-03-22T21:57:57.000Z\",\"IN_CARR_QTY\":\"288\",\"LAST_TKIN_TIME\":\"2025-03-25T09:46:35.000Z\",\"VHCL_ID\":null,\"FIRST_FRMT_INPUT_TIME\":\"2025-03-22T21:58:00.000Z\",\"CURRENTLOCATION\":\"HFF09CDS0200\",\"JOB_ID\":null,\"FROM_PORT\":null,\"TO_PORT\":null,\"TRANSPORT_JOB_TIMESTAMP\":\"2025-03-25T11:57:14.000Z\"}]\r\n}", diff --git a/Assets/Scripts/UVC/core/Singleton.cs b/Assets/Scripts/UVC/core/Singleton.cs index 6e326082..5683400c 100644 --- a/Assets/Scripts/UVC/core/Singleton.cs +++ b/Assets/Scripts/UVC/core/Singleton.cs @@ -63,7 +63,7 @@ namespace UVC.Core /// protected override void Init() /// { /// // 초기화 코드 작성 - /// Debug.Log("AppMain 초기화 완료"); + /// ULog.Debug("AppMain 초기화 완료"); /// } /// } /// @@ -162,7 +162,7 @@ namespace UVC.Core /// protected override void Init() /// { /// // 초기화 코드 작성 - /// Debug.Log("SceneMain 초기화 완료"); + /// ULog.Debug("SceneMain 초기화 완료"); /// } /// } /// diff --git a/Assets/Scripts/UVC/util/ColorUtil.cs b/Assets/Scripts/UVC/util/ColorUtil.cs index 3e063066..39e68067 100644 --- a/Assets/Scripts/UVC/util/ColorUtil.cs +++ b/Assets/Scripts/UVC/util/ColorUtil.cs @@ -1,4 +1,5 @@ using UnityEngine; +using UVC.Log; namespace UVC.Util { @@ -22,7 +23,7 @@ namespace UVC.Util return color; } - Debug.LogError("[UnityExtension::HexColor]invalid hex code - " + hexCode); + ULog.Error($"[UnityExtension::FromHex]invalid hex code: {hexCode}", new System.Exception("$[UnityExtension::FromHex]invalid hex code: {hexCode}")); return Color.white; } @@ -46,7 +47,7 @@ namespace UVC.Util return color; } - Debug.LogError("[UnityExtension::HexColor]invalid hex code - " + hexCode); + ULog.Error($"[UnityExtension::HexColor]invalid hex code: {hexCode}", new System.Exception($"[UnityExtension::HexColor]invalid hex code: {hexCode}")); color = Color.white; color.a = alpha; return color; diff --git a/Assets/Scripts/UVC/util/XmlDataUtil.cs b/Assets/Scripts/UVC/util/XmlDataUtil.cs index 670e2d0e..75832f55 100644 --- a/Assets/Scripts/UVC/util/XmlDataUtil.cs +++ b/Assets/Scripts/UVC/util/XmlDataUtil.cs @@ -49,10 +49,7 @@ namespace UVC.Util /// public void SerializeXmlData(string filePath, object data) { - Debug.Log("SerializeXmlData"); - XmlSerializer serializer = new XmlSerializer(data.GetType()); - using (TextWriter writer = new StreamWriter(filePath)) { serializer.Serialize(writer, data); @@ -101,9 +98,6 @@ namespace UVC.Util /// public void SaveXmlData(string filePath, object data) { - //Debug.Log(filePath + " , " + data); - //Debug.Log("File.Exist: " + File.Exists(filePath)); - if (data == null) return; //파일이 있으면 기존 파일 로드 후 수정 @@ -111,12 +105,8 @@ namespace UVC.Util { StartCoroutine(LoadXml(filePath, (XDocument xdoc) => { - //Debug.Log(xdoc.ToString()); - UpdateData(xdoc, data, (isChanged, isDataError) => { - //Debug.Log("isChanged: " + isChanged + " , isDataError: " + isDataError); - if (isChanged && !isDataError) { xdoc.Save(filePath);