aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRoy Ben Shabat <roy.mail.net@gmail.com>2025-09-09 16:30:00 +0300
committerRoy Ben Shabat <roy.mail.net@gmail.com>2025-09-09 16:30:00 +0300
commitba37d5082917551fd9a2b194fa1489f1fd86f39b (patch)
treee60ec8ec66d7852c5231968983063bd3fe225a0f
parentf87208b19e183d309a3bcacd26ecc74c4da728d4 (diff)
downloadTango-ba37d5082917551fd9a2b194fa1489f1fd86f39b.tar.gz
Tango-ba37d5082917551fd9a2b194fa1489f1fd86f39b.zip
FSE Improved Statistics + Dynamic CSV Export !
-rw-r--r--Software/Visual_Studio/FSE/Modules/Tango.FSE.Statistics/Models/StopModel.cs6
-rw-r--r--Software/Visual_Studio/FSE/Modules/Tango.FSE.Statistics/ViewModels/MainViewVM.cs256
-rw-r--r--Software/Visual_Studio/Tango.CSV/CsvDynamicWriter.cs126
-rw-r--r--Software/Visual_Studio/Tango.CSV/Tango.CSV.csproj1
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" />