diff options
4 files changed, 249 insertions, 140 deletions
diff --git a/Software/Visual_Studio/FSE/Modules/Tango.FSE.Statistics/Models/StopModel.cs b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Statistics/Models/StopModel.cs index a4f2f8caf..3b3fad3f0 100644 --- a/Software/Visual_Studio/FSE/Modules/Tango.FSE.Statistics/Models/StopModel.cs +++ b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Statistics/Models/StopModel.cs @@ -69,7 +69,7 @@ namespace Tango.FSE.Statistics.Models { get { - return String.Join("\n", Output.Where(x => x.Volume > 0).Select(x => x.LiquidType.Name + ": " + Math.Round(x.Volume, 2))); + return String.Join("\n", Output.Where(x => x != null && x.Volume > 0).Select(x => x.LiquidType.Name + ": " + Math.Round(x.Volume, 2))); } } @@ -86,8 +86,8 @@ namespace Tango.FSE.Statistics.Models get { var output = new List<PresentationLiquidVolume>(); - output.AddRange(LiquidVolumes.Where(x => x.LiquidType.HasPigment && x.Volume > 0).OrderBy(x => x.LiquidType.PreferredIndex)); - output.Add(LiquidVolumes.FirstOrDefault(x => x.LiquidType.Type == LiquidTypes.Transparent)); + output.AddRange(LiquidVolumes.Where(x => x.Volume > 0).OrderBy(x => x.LiquidType.PreferredIndex)); + //output.Add(LiquidVolumes.FirstOrDefault(x => x.LiquidType.Type == LiquidTypes.Transparent)); return output; } } diff --git a/Software/Visual_Studio/FSE/Modules/Tango.FSE.Statistics/ViewModels/MainViewVM.cs b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Statistics/ViewModels/MainViewVM.cs index e3546cb92..d7a829cda 100644 --- a/Software/Visual_Studio/FSE/Modules/Tango.FSE.Statistics/ViewModels/MainViewVM.cs +++ b/Software/Visual_Studio/FSE/Modules/Tango.FSE.Statistics/ViewModels/MainViewVM.cs @@ -518,7 +518,7 @@ namespace Tango.FSE.Statistics.ViewModels Quantity = 0 }); - jobRunProperties.Add(liquidType.Type, typeof(JobRunDTO).GetProperty(liquidType.Type.ToString() + "Quantity")); + jobRunProperties.Add(liquidType.Type, typeof(JobRunDTO).GetProperty(liquidType.Type.ToString() + "Quantity",BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance)); } foreach (var stop in Stops) @@ -709,166 +709,148 @@ namespace Tango.FSE.Statistics.ViewModels } } - private void ExportToCSV(List<StopModel> stops, String filePath) + private void ExportToCSV(List<StopModel> stops, string filePath) { - CsvFile<CsvModel> csvFile = new CsvFile<CsvModel>(new CsvDestination(filePath), new CsvDefinition() - { - Columns = new List<String>() - { - "Job Index", - "Job Name", - "Job Kind", - "Thread", - "Length", - "Number Of Units", - "Start Time", - "Duration", - "Distance", - "Start Position", - "End Position", - "Status", - "Segment Index", - "Offset", - "Color Space", - "Target L", - "Target A", - "Target B", - "Measured L", - "Measured A", - "Measured B", - "Delta E", - "Approved", - "Input RGB (R)", - "Input RGB (G)", - "Input RGB (B)", - "Input LAB (L)", - "Input LAB (A)", - "Input LAB (B)", - "Input Catalog", - "Input Catalog Item", - "Input Cyan", - "Input Magenta", - "Input Yellow", - "Input Black", - "Input Blue", - "Input Orange", - "Input Rubine", - "Input Navy", - "Input Violet", - "Output Cyan", - "Output Magenta", - "Output Yellow", - "Output Black", - "Output Light Cyan", - "Output Light Magenta", - "Output Light Yellow", - "Output Blue", - "Output Light Blue", - "Output Orange", - "Output Light Orange", - "Output Rubine", - "Output Light Rubine", - "Output Navy", - "Output Violet", - "Output Transparent Ink", - "Output Lubricant", - "Failure Reason", - }, - }); + var csv = new CsvDynamicWriter(); + + // ===== Fixed column indices (preserve original order) ===== + const int IDX_JobIndex = 0; + const int IDX_JobName = 1; + const int IDX_JobKind = 2; + const int IDX_Thread = 3; + const int IDX_Length = 4; + const int IDX_NumberOfUnits = 5; + const int IDX_StartTime = 6; + const int IDX_Duration = 7; + const int IDX_Distance = 8; + const int IDX_StartPosition = 9; + const int IDX_EndPosition = 10; + const int IDX_Status = 11; + const int IDX_SegmentIndex = 12; + const int IDX_Offset = 13; + const int IDX_ColorSpace = 14; + + // Fine-tuning block (before inputs, matches original header order) + const int IDX_TargetL = 15; + const int IDX_TargetA = 16; + const int IDX_TargetB = 17; + const int IDX_MeasuredL = 18; + const int IDX_MeasuredA = 19; + const int IDX_MeasuredB = 20; + const int IDX_DeltaE = 21; + const int IDX_Approved = 22; - foreach (var stop in stops.OrderBy(x => x.JobIndex).ToList()) + // Fixed "Input *" (non-volume) columns + const int IDX_InputRgbR = 23; + const int IDX_InputRgbG = 24; + const int IDX_InputRgbB = 25; + const int IDX_InputLabL = 26; + const int IDX_InputLabA = 27; + const int IDX_InputLabB = 28; + const int IDX_InputCatalog = 29; + const int IDX_InputCatalogItem = 30; + + // Dynamic regions: + // - Input volumes start after IDX_InputCatalogItem + // - Outputs start immediately after the last input volume + var liquids = (LiquidTypes[])Enum.GetValues(typeof(LiquidTypes)); + const int INPUT_VOLUMES_BASE = 31; + int OUTPUT_BASE = INPUT_VOLUMES_BASE + liquids.Length; + int IDX_FailureReason = OUTPUT_BASE + liquids.Length; // last + + foreach (var stop in stops.OrderBy(x => x.JobIndex)) { - CsvModel model = new CsvModel(); - model.JobIndex = stop.JobIndex.ToString(); - model.JobName = stop.JobRun.JobName; - model.JobKind = ((JobDesignations)stop.JobRun.JobDesignation).ToDescription(); - model.Thread = stop.ThreadName; - model.Length = stop.LogicalLengthMeters.ToString(); - model.NumberOfUnits = stop.JobRun.NumberOfUnits.ToString(); - model.StartTime = stop.JobRun.StartDate.ToLocalTime().ToString(); - model.Duration = stop.Duration.ToStringUnlimitedHours(); - model.Distance = stop.Distance.ToString(); - model.StartPosition = stop.ActualStartPosition.ToString(); - model.EndPosition = stop.ActualEndPosition.ToString(); - model.Status = ((JobRunStatus)stop.JobRun.Status).ToString(); - model.SegmentIndex = stop.SegmentIndex.ToString(); - model.Offset = stop.StartMeters.ToString(); - model.ColorSpace = stop.ColorSpace.ToString(); + // ===== Fixed fields ===== + csv.Write(stop.JobIndex.ToString(), "Job Index", "", IDX_JobIndex); + csv.Write(stop.JobRun.JobName, "Job Name", "", IDX_JobName); + csv.Write(((JobDesignations)stop.JobRun.JobDesignation).ToDescription(), + "Job Kind", "", IDX_JobKind); + csv.Write(stop.ThreadName, "Thread", "", IDX_Thread); + csv.Write(stop.LogicalLengthMeters.ToString(), "Length", "0", IDX_Length); + csv.Write(stop.JobRun.NumberOfUnits.ToString(), "Number Of Units", "0", IDX_NumberOfUnits); + csv.Write(stop.JobRun.StartDate.ToLocalTime().ToString(), "Start Time", "", IDX_StartTime); + csv.Write(stop.Duration.ToStringUnlimitedHours(), "Duration", "", IDX_Duration); + csv.Write(stop.Distance.ToString(), "Distance", "0", IDX_Distance); + csv.Write(stop.ActualStartPosition.ToString(), "Start Position", "0", IDX_StartPosition); + csv.Write(stop.ActualEndPosition.ToString(), "End Position", "0", IDX_EndPosition); + csv.Write(((JobRunStatus)stop.JobRun.Status).ToString(), "Status", "", IDX_Status); + csv.Write(stop.SegmentIndex.ToString(), "Segment Index", "0", IDX_SegmentIndex); + csv.Write(stop.StartMeters.ToString(), "Offset", "0", IDX_Offset); + csv.Write(stop.ColorSpace.ToString(), "Color Space", "", IDX_ColorSpace); + // ===== Fine tuning (if applicable) ===== + if (stop.JobRun.JobDesignation == (int)JobDesignations.FineTuning && stop.FineTuningModel != null) + { + csv.Write(stop.FineTuningModel.FineTuningTargetL.ToString(), "Target L", "0", IDX_TargetL); + csv.Write(stop.FineTuningModel.FineTuningTargetA.ToString(), "Target A", "0", IDX_TargetA); + csv.Write(stop.FineTuningModel.FineTuningTargetB.ToString(), "Target B", "0", IDX_TargetB); + + csv.Write(stop.FineTuningModel.FineTuningMeasuredL.ToStringSafe(), "Measured L", "0", IDX_MeasuredL); + csv.Write(stop.FineTuningModel.FineTuningMeasuredA.ToStringSafe(), "Measured A", "0", IDX_MeasuredA); + csv.Write(stop.FineTuningModel.FineTuningMeasuredB.ToStringSafe(), "Measured B", "0", IDX_MeasuredB); + + csv.Write(stop.FineTuningDeltaE.ToStringSafe(), "Delta E", "0", IDX_DeltaE); + csv.Write(stop.FineTuningModel.Approved.ToStringYesNo(), "Approved", "No", IDX_Approved); + } + + // ===== Inputs (color-space specific) ===== switch (stop.ColorSpace) { case ColorSpaces.RGB: - model.InputRed = stop.Red.ToString(); - model.InputGreen = stop.Green.ToString(); - model.InputBlue = stop.Blue.ToString(); + if (stop.Red > 0) csv.Write(stop.Red.ToString(), "Input RGB (R)", "0", IDX_InputRgbR); + if (stop.Green > 0) csv.Write(stop.Green.ToString(), "Input RGB (G)", "0", IDX_InputRgbG); + if (stop.Blue > 0) csv.Write(stop.Blue.ToString(), "Input RGB (B)", "0", IDX_InputRgbB); break; + case ColorSpaces.LAB: - model.InputL = stop.L.ToString(); - model.InputA = stop.A.ToString(); - model.InputB = stop.B.ToString(); + if (stop.L > 0) csv.Write(stop.L.ToString(), "Input LAB (L)", "0", IDX_InputLabL); + if (stop.A > 0) csv.Write(stop.A.ToString(), "Input LAB (A)", "0", IDX_InputLabA); + if (stop.B > 0) csv.Write(stop.B.ToString(), "Input LAB (B)", "0", IDX_InputLabB); break; + case ColorSpaces.Catalog: - model.InputCatalog = stop.Catalog; - model.InputCatalogItem = stop.CatalogItem; + if (stop.Catalog.IsNotNullOrEmpty()) + csv.Write(stop.Catalog, "Input Catalog", "", IDX_InputCatalog); + if (stop.CatalogItem.IsNotNullOrEmpty()) + csv.Write(stop.CatalogItem, "Input Catalog Item", "", IDX_InputCatalogItem); break; - case ColorSpaces.Volume: - model.InputCyan = stop.GetLiquidVolumeInputOrZero(LiquidTypes.Cyan).ToString(); - model.InputMagenta = stop.GetLiquidVolumeInputOrZero(LiquidTypes.Magenta).ToString(); - model.InputYellow = stop.GetLiquidVolumeInputOrZero(LiquidTypes.Yellow).ToString(); - model.InputBlack = stop.GetLiquidVolumeInputOrZero(LiquidTypes.Black).ToString(); - model.InputBBlue = stop.GetLiquidVolumeInputOrZero(LiquidTypes.Blue).ToString(); - model.InputOrange = stop.GetLiquidVolumeInputOrZero(LiquidTypes.Orange).ToString(); - model.InputRubine = stop.GetLiquidVolumeInputOrZero(LiquidTypes.Rubine).ToString(); - model.InputNavy = stop.GetLiquidVolumeInputOrZero(LiquidTypes.Navy).ToString(); - model.InputViolet = stop.GetLiquidVolumeInputOrZero(LiquidTypes.Violet).ToString(); + case ColorSpaces.Volume: + // Dynamic input volumes by enum order + for (int i = 0; i < liquids.Length; i++) + { + var lt = liquids[i]; + var idx = INPUT_VOLUMES_BASE + i; + var col = "Input " + lt; // e.g., "Input Cyan" + var v = stop.GetLiquidVolumeInputOrZero(lt); + if (v > 0) csv.Write(v.ToString(), col, "0", idx); + } break; } - if (stop.JobRun.JobDesignation == (int)JobDesignations.FineTuning) + // ===== Outputs for all liquids (dynamic by enum order) ===== + for (int i = 0; i < liquids.Length; i++) { - if (stop.FineTuningModel != null) - { - model.TargetL = stop.FineTuningModel.FineTuningTargetL.ToString(); - model.TargetA = stop.FineTuningModel.FineTuningTargetA.ToString(); - model.TargetB = stop.FineTuningModel.FineTuningTargetB.ToString(); - - model.MeasuredL = stop.FineTuningModel.FineTuningMeasuredL.ToStringSafe(); - model.MeasuredA = stop.FineTuningModel.FineTuningMeasuredA.ToStringSafe(); - model.MeasuredB = stop.FineTuningModel.FineTuningMeasuredB.ToStringSafe(); - - model.DeltaE = stop.FineTuningDeltaE.ToStringSafe(); - model.Approved = stop.FineTuningModel.Approved.ToStringYesNo(); - } + var lt = liquids[i]; + var idx = OUTPUT_BASE + i; + var col = "Output " + lt; // e.g., "Output Cyan" + var v = stop.GetLiquidVolumeOutputOrZero(lt); + if (v > 0) csv.Write(v.ToString(), col, "0", idx); } - model.OutputCyan = stop.GetLiquidVolumeOutputOrZero(LiquidTypes.Cyan).ToString(); - model.OutputMagenta = stop.GetLiquidVolumeOutputOrZero(LiquidTypes.Magenta).ToString(); - model.OutputYellow = stop.GetLiquidVolumeOutputOrZero(LiquidTypes.Yellow).ToString(); - model.OutputBlack = stop.GetLiquidVolumeOutputOrZero(LiquidTypes.Black).ToString(); - model.OutputLightCyan = stop.GetLiquidVolumeOutputOrZero(LiquidTypes.LightCyan).ToString(); - model.OutputLightMagenta = stop.GetLiquidVolumeOutputOrZero(LiquidTypes.LightMagenta).ToString(); - model.OutputLightYellow = stop.GetLiquidVolumeOutputOrZero(LiquidTypes.LightYellow).ToString(); - - model.OutputBlue = stop.GetLiquidVolumeOutputOrZero(LiquidTypes.Blue).ToString(); - model.OutputLightBlue = stop.GetLiquidVolumeOutputOrZero(LiquidTypes.LightBlue).ToString(); - model.OutputOrange = stop.GetLiquidVolumeOutputOrZero(LiquidTypes.Orange).ToString(); - model.OutputLightOrange = stop.GetLiquidVolumeOutputOrZero(LiquidTypes.LightOrange).ToString(); - model.OutputRubine = stop.GetLiquidVolumeOutputOrZero(LiquidTypes.Rubine).ToString(); - model.OutputLightRubine = stop.GetLiquidVolumeOutputOrZero(LiquidTypes.LightRubine).ToString(); - model.OutputNavy = stop.GetLiquidVolumeOutputOrZero(LiquidTypes.Navy).ToString(); - model.OutputViolet = stop.GetLiquidVolumeOutputOrZero(LiquidTypes.Violet).ToString(); + // ===== Failure reason (last) ===== + if (stop.JobRun.FailedMessage.IsNotNullOrEmpty()) + csv.Write(stop.JobRun.FailedMessage, "Failure Reason", "", IDX_FailureReason); - model.OutputTransparent = stop.GetLiquidVolumeOutputOrZero(LiquidTypes.Transparent).ToString(); - model.OutputLubricant = stop.GetLiquidVolumeOutputOrZero(LiquidTypes.Lubricant).ToString(); - model.FailureReason = stop.JobRun.FailedMessage; - - csvFile.Append(model); + // Next row + csv.Next(); } - csvFile.Dispose(); + csv.Save(filePath); } + + #endregion #region Streaming diff --git a/Software/Visual_Studio/Tango.CSV/CsvDynamicWriter.cs b/Software/Visual_Studio/Tango.CSV/CsvDynamicWriter.cs new file mode 100644 index 000000000..1c460d5e3 --- /dev/null +++ b/Software/Visual_Studio/Tango.CSV/CsvDynamicWriter.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.CSV +{ + public class CsvDynamicWriter + { + private sealed class ColumnInfo + { + public string Name; + public string Default; + public int Index; + public int CreationOrder; + } + + private readonly Dictionary<string, ColumnInfo> _colMap = new Dictionary<string, ColumnInfo>(); + private readonly List<ColumnInfo> _cols = new List<ColumnInfo>(); + private int _creationCounter = 0; + + private readonly List<Dictionary<string, string>> _rows = new List<Dictionary<string, string>>(); + private Dictionary<string, string> _currentRow = new Dictionary<string, string>(); + + /// <summary> + /// Writes a value to the specified column in the current row. + /// If the column does not exist, it is created with the given default value and index. + /// Column order in the CSV header is by (Index asc, CreationOrder asc). + /// </summary> + public void Write(string value, string columnName, string defaultValue, int index) + { + ColumnInfo col; + if (!_colMap.TryGetValue(columnName, out col)) + { + col = new ColumnInfo + { + Name = columnName, + Default = defaultValue, + Index = index, + CreationOrder = _creationCounter++ + }; + _colMap[columnName] = col; + _cols.Add(col); + + // Backfill all prior rows with the column's default. + foreach (var row in _rows) + { + if (!row.ContainsKey(columnName)) + row[columnName] = defaultValue; + } + } + // If the column already exists, we keep the original index/default. + // (If you need reindexing, add an explicit method to change col.Index.) + + _currentRow[columnName] = value ?? ""; + } + + /// <summary> + /// Moves to the next row, finalizing the current one. + /// Missing cells get their column default value. + /// </summary> + public void Next() + { + if (_currentRow == null) _currentRow = new Dictionary<string, string>(); + + foreach (var col in _cols) + { + if (!_currentRow.ContainsKey(col.Name)) + _currentRow[col.Name] = col.Default ?? ""; + } + + _rows.Add(_currentRow); + _currentRow = new Dictionary<string, string>(); + } + + /// <summary> + /// Saves the CSV to disk using the column order defined by (Index, CreationOrder). + /// </summary> + public void Save(string filePath) + { + // Auto-finalize last row if it has any values. + if (_currentRow != null && _currentRow.Count > 0) + Next(); + + var orderedCols = _cols + .OrderBy(c => c.Index) + .ThenBy(c => c.CreationOrder) + .ToList(); + + var sb = new StringBuilder(); + + // Header + sb.AppendLine(string.Join(",", orderedCols.Select(c => EscapeCsv(c.Name)))); + + // Rows + foreach (var row in _rows) + { + var values = new List<string>(orderedCols.Count); + for (int i = 0; i < orderedCols.Count; i++) + { + var name = orderedCols[i].Name; + string v; + if (!row.TryGetValue(name, out v)) + { + // Shouldn't happen (Next() fills), but be defensive. + v = orderedCols[i].Default ?? ""; + } + values.Add(EscapeCsv(v)); + } + sb.AppendLine(string.Join(",", values)); + } + + File.WriteAllText(filePath, sb.ToString(), Encoding.UTF8); + } + + private static string EscapeCsv(string input) + { + if (input == null) return ""; + if (input.IndexOfAny(new[] { ',', '"', '\n', '\r' }) >= 0) + return "\"" + input.Replace("\"", "\"\"") + "\""; + return input; + } + } +} diff --git a/Software/Visual_Studio/Tango.CSV/Tango.CSV.csproj b/Software/Visual_Studio/Tango.CSV/Tango.CSV.csproj index b6743bede..0fc1948a4 100644 --- a/Software/Visual_Studio/Tango.CSV/Tango.CSV.csproj +++ b/Software/Visual_Studio/Tango.CSV/Tango.CSV.csproj @@ -81,6 +81,7 @@ </Compile> <Compile Include="CsvDefinition.cs" /> <Compile Include="CsvDestination.cs" /> + <Compile Include="CsvDynamicWriter.cs" /> <Compile Include="CsvFile.cs" /> <Compile Include="CsvFileLinqExtensions.cs" /> <Compile Include="CsvFileReader.cs" /> |
