using LiveCharts; using LiveCharts.Wpf; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Data.Entity; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Data; using System.Windows.Media; using Tango.BL; using Tango.BL.DTO; using Tango.BL.Entities; using Tango.BL.Enumerations; using Tango.BL.FineTuning; using Tango.Core; using Tango.Core.Commands; using Tango.Core.ExtensionMethods; using Tango.CSV; using Tango.FSE.Common; using Tango.FSE.Common.AutoComplete; using Tango.FSE.Common.Converters; using Tango.FSE.Common.Navigation; using Tango.FSE.Common.Notifications; using Tango.FSE.Common.Statistics; using Tango.FSE.Statistics.Dialogs; using Tango.FSE.Statistics.Models; using Tango.PPC.Shared.Statistics; using Tango.SharedUI.Components; using Tango.SharedUI.Helpers; namespace Tango.FSE.Statistics.ViewModels { public class MainViewVM : FSEViewModel { private const int NULL_JOB_INDEX = -1000; private int _currentJobIndex; private StatisticsModel _model; private String _currentFineTuningSessionID; private List _fineTuningStops; private List _liquidTypes; #region Properties private DateTime _startSelectedDate; public DateTime StartSelectedDate { get { return _startSelectedDate; } set { _startSelectedDate = value; RaisePropertyChangedAuto(); } } private DateTime _endSelectedDate; public DateTime EndSelectedDate { get { return _endSelectedDate; } set { _endSelectedDate = value; RaisePropertyChangedAuto(); } } protected Double _lengthLowerValue; public Double LengthLowerValue { get { return _lengthLowerValue; } set { _lengthLowerValue = value; RaisePropertyChangedAuto(); } } protected Double _lengthUpperValue; public Double LengthUpperValue { get { return _lengthUpperValue; } set { _lengthUpperValue = value; RaisePropertyChangedAuto(); } } private RequiredFiltersData _filtersData; public RequiredFiltersData FiltersData { get { return _filtersData; } set { _filtersData = value; RaisePropertyChangedAuto(); } } private SelectedObjectCollection _jobRunSelectedStatuses; public SelectedObjectCollection JobRunSelectedStatuses { get { return _jobRunSelectedStatuses; } set { _jobRunSelectedStatuses = value; RaisePropertyChangedAuto(); } } private SelectedObjectCollection _jobRunSelectedDesignations; public SelectedObjectCollection JobRunSelectedDesignations { get { return _jobRunSelectedDesignations; } set { _jobRunSelectedDesignations = value; RaisePropertyChangedAuto(); } } private SelectedObjectCollection _selectedThreads; public SelectedObjectCollection SelectedThreads { get { return _selectedThreads; } set { _selectedThreads = value; RaisePropertyChangedAuto(); } } private String _jobName; public String JobName { get { return _jobName; } set { _jobName = value; RaisePropertyChangedAuto(); } } private bool _includeHeadCleaning; public bool IncludeHeadCleaning { get { return _includeHeadCleaning; } set { _includeHeadCleaning = value; RaisePropertyChangedAuto(); } } private bool _isSettingsBarOpened; public bool IsSettingsBarOpened { get { return _isSettingsBarOpened; } set { _isSettingsBarOpened = value; RaisePropertyChangedAuto(); } } private bool _isFiltersAvailable; public bool IsFiltersAvailable { get { return _isFiltersAvailable; } set { _isFiltersAvailable = value; RaisePropertyChangedAuto(); InvalidateRelayCommands(); } } private SynchronizedObservableCollection _stops; public SynchronizedObservableCollection Stops { get { return _stops; } set { _stops = value; RaisePropertyChangedAuto(); RaisePropertyChanged(nameof(IsResultsAvailable)); InvalidateRelayCommands(); } } private ListCollectionView _stopView; public ListCollectionView StopsView { get { return _stopView; } set { _stopView = value; RaisePropertyChangedAuto(); } } private bool _jobNameExact; public bool JobNameExact { get { return _jobNameExact; } set { _jobNameExact = value; RaisePropertyChangedAuto(); } } private StatsModel _stats; public StatsModel Stats { get { return _stats; } set { _stats = value; RaisePropertyChangedAuto(); } } public bool IsResultsAvailable { get { return Stops != null && Stops.Count > 0 && Stops[0].JobIndex != NULL_JOB_INDEX; } } private SeriesCollection _seriesCollection; public SeriesCollection SeriesCollection { get { return _seriesCollection; } set { _seriesCollection = value; RaisePropertyChangedAuto(); } } #endregion #region Commands public RelayCommand GetStatisticsCommand { get; set; } public RelayCommand ShowFailedMessageCommand { get; set; } public RelayCommand ShowExtendedInfoCommand { get; set; } public RelayCommand CopyCommand { get; set; } public RelayCommand ExportToCsvCommand { get; set; } public RelayCommand OpenStreamingSettingsCommand { get; set; } public RelayCommand ClearCommand { get; set; } #endregion #region Suggestions Providers public AutoCompleteSource JobsAutoCompleteProvider { get; set; } #endregion #region Constructors public MainViewVM() { IsSettingsBarOpened = true; _model = new StatisticsModel(); JobRunSelectedStatuses = new SelectedObjectCollection(new ObservableCollection() { JobRunStatus.Aborted, JobRunStatus.Completed, JobRunStatus.Failed, }, new ObservableCollection() { JobRunStatus.Aborted, JobRunStatus.Completed, JobRunStatus.Failed, }); JobRunSelectedDesignations = new SelectedObjectCollection(new ObservableCollection() { JobDesignations.Default, JobDesignations.FineTuning, }, new ObservableCollection() { JobDesignations.Default, JobDesignations.FineTuning, }); JobRunSelectedStatuses.SelectionChanged -= (x, y) => RaisePropertyChanged(nameof(JobRunSelectedStatuses)); JobRunSelectedStatuses.SelectionChanged += (x, y) => RaisePropertyChanged(nameof(JobRunSelectedStatuses)); JobRunSelectedDesignations.SelectionChanged -= (x, y) => RaisePropertyChanged(nameof(JobRunSelectedDesignations)); JobRunSelectedDesignations.SelectionChanged += (x, y) => RaisePropertyChanged(nameof(JobRunSelectedDesignations)); JobsAutoCompleteProvider = new AutoCompleteSource(AutoCompleteJobs); StartSelectedDate = DateTime.Now.AddDays(-7); EndSelectedDate = DateTime.Now; LengthUpperValue = 1000000; GetStatisticsCommand = new RelayCommand(GetStatistics, () => IsFiltersAvailable); //Just for showing the columns :/ Stops = new List() { new StopModel() { JobRun = new JobRunDTO(), Job = new PresentationJob(), JobIndex = NULL_JOB_INDEX } }.ToSynchronizedObservableCollection(); var view = new ListCollectionView(Stops); view.GroupDescriptions.Add(new PropertyGroupDescription("JobIndex")); view.GroupDescriptions.Add(new PropertyGroupDescription("SegmentIndex")); StopsView = view; Stats = new StatsModel(); ShowFailedMessageCommand = new RelayCommand(ShowJobRunFailedMessage); ShowExtendedInfoCommand = new RelayCommand(ShowJobRunExtendedInfo); CopyCommand = new RelayCommand(CopyStopToClipboard); ExportToCsvCommand = new RelayCommand(ExportToCSV, () => IsResultsAvailable); OpenStreamingSettingsCommand = new RelayCommand(OpenStreamingSettings); ClearCommand = new RelayCommand(Clear); } #endregion #region Override Methods public override void OnApplicationStarted() { InvokeUI(() => { NavigationManager.MenuItems.Add(new NavigationMenuItem(() => { NavigationManager.NavigateTo(); }) { Name = "Statistics", Index = 11, Description = "Query for statistical data of a machine.", Image = ResourceHelper.GetImageFromResources("Images/statistics.png"), }); }); } public override void OnApplicationReady() { base.OnApplicationReady(); MachineProvider.MachineConnected += MachineProvider_MachineConnected; MachineProvider.MachineDisconnected += MachineProvider_MachineDisconnected; StatisticsProvider.JobRunReceived += StatisticsProvider_JobRunReceived; } #endregion #region Machine Connection / Disconnection private void MachineProvider_MachineDisconnected(object sender, Common.Connection.MachineDisconnectedEventArgs e) { } private async void MachineProvider_MachineConnected(object sender, Common.Connection.MachineConnectedEventArgs e) { if (MachineProvider.IsPPCAvailable && (e.DifferentFromPrevious || !IsFiltersAvailable)) { await LoadFiltersData(); } } #endregion #region Statistics Methods private async Task LoadFiltersData() { try { FiltersData = await StatisticsProvider.GetRequiredFiltersData(); SelectedThreads = new SelectedObjectCollection(FiltersData.Rmls.ToObservableCollection(), new ObservableCollection()); IsFiltersAvailable = true; } catch (Exception ex) { IsFiltersAvailable = false; NotificationProvider.PushErrorReportingSnackbar(ex, "Statistics Module Error", "Error loading required statistics filter data."); } } private async void GetStatistics() { try { using (var task = NotificationProvider.PushTaskItem("Getting statistics from the remote machine...", false)) { await Task.Delay(3000); Filters filters = new Filters(); filters.StartDateUTC = StartSelectedDate; filters.EndDateUTC = EndSelectedDate; filters.EndStatuses = JobRunSelectedStatuses.SynchedSource.Cast().ToList(); filters.JobDesignation = JobRunSelectedDesignations.SynchedSource.Cast().ToList(); filters.IncludeHeadCleaning = IncludeHeadCleaning; filters.JobName = JobName; filters.ExactJobName = JobNameExact; filters.MinLength = (int)LengthLowerValue; filters.MaxLength = (int)LengthUpperValue; filters.RmlGuids = SelectedThreads.SynchedSource.Select(x => x.Guid).ToList(); _model = await StatisticsProvider.GetStatistics(filters); _liquidTypes = _model.StatisticsResult.LiquidTypes.Select(x => x.ToObservable()).ToList(); List stops = new List(); _currentJobIndex = 0; _currentFineTuningSessionID = null; _fineTuningStops = new List(); foreach (var job in _model.StatisticsResult.JobRuns.OrderByDescending(x => x.JobRun.StartDate)) { stops.AddRange(CreateJobRunStops(job)); } Stops = stops.ToSynchronizedObservableCollection(); var view = new ListCollectionView(Stops); view.GroupDescriptions.Add(new PropertyGroupDescription("JobIndex")); view.GroupDescriptions.Add(new PropertyGroupDescription("SegmentIndex")); StopsView = view; UpdateSummaries(); if (Stops.Count > 0) { await Task.Delay(1000); IsSettingsBarOpened = false; await Task.Delay(1000); } } } catch (Exception ex) { NotificationProvider.PushErrorReportingSnackbar(ex, "Statistics Module Error", "Error getting statistics from the remote machine."); } } private List CreateJobRunStops(JobRunComposition job) { List stops = new List(); var rmlName = FiltersData.Rmls.FirstOrDefault(x => x.Guid == job.JobRun.RmlGuid)?.Name; VectorFineTuningRunModel fineTuningModel = null; if (job.JobRun.JobDesignation == (int)JobDesignations.FineTuning && job.JobRun.FineTuningString != null) { fineTuningModel = JsonConvert.DeserializeObject(job.JobRun.FineTuningString); if (fineTuningModel.FineTuningSessionID != _currentFineTuningSessionID) { _fineTuningStops = new List(); _currentFineTuningSessionID = fineTuningModel.FineTuningSessionID; _currentJobIndex++; } } else { _currentJobIndex++; } foreach (var jobSegment in job.Job.Segments) { foreach (var jobStop in jobSegment.Stops) { StopModel stop = new StopModel(); stop.Index = jobSegment.Stops.IndexOf(jobStop) + 1; stop.JobIndex = _currentJobIndex; stop.SegmentIndex = job.Job.Segments.IndexOf(jobSegment) + 1; stop.ThreadName = rmlName; stop.IsAdvancedMode = !BuildProvider.IsTwineRSM && CurrentUser.HasRole(Roles.FSEAdvancedTechnician); stop.FineTuningModel = fineTuningModel; stop.MachineType = MachineProvider.MachineOperator.MachineType; if (fineTuningModel != null) { stop.IsFineTuningApproved = fineTuningModel.Approved; stop.IsFineTuningSelected = fineTuningModel.Approved; _fineTuningStops.Add(stop); if (_fineTuningStops.Exists(x => x.IsFineTuningApproved)) { _fineTuningStops.ForEach(x => x.IsFineTuningApproved = true); } } stop.ColorSpace = jobStop.ColorSpace; stop.L = jobStop.L; stop.A = jobStop.A; stop.B = jobStop.B; stop.Red = jobStop.Red; stop.Green = jobStop.Green; stop.Blue = jobStop.Blue; stop.Catalog = jobStop.Catalog; stop.CatalogItem = jobStop.CatalogItem; stop.StartMeters = jobStop.StartMeters; stop.Job = job.Job; stop.JobRun = job.JobRun; stop.Segment = jobSegment; stop.LiquidVolumes = jobStop.LiquidVolumes; stop.BestMatchR = jobStop.BestMatchR; stop.BestMatchG = jobStop.BestMatchG; stop.BestMatchB = jobStop.BestMatchB; stop.ShowFailedMessageCommand = ShowFailedMessageCommand; stop.ShowExtendedInfoCommand = ShowExtendedInfoCommand; stop.CopyCommand = CopyCommand; stops.Add(stop); } } return stops; } private void UpdateSummaries() { var stats = new StatsModel(); try { stats.TotalRuns = _model.StatisticsResult.JobRuns.Count; stats.CompletedRuns = _model.StatisticsResult.JobRuns.Count(x => x.JobRun.Status == (int)JobRunStatus.Completed); stats.FailedRuns = _model.StatisticsResult.JobRuns.Count(x => x.JobRun.Status == (int)JobRunStatus.Failed); stats.AbortedRuns = _model.StatisticsResult.JobRuns.Count(x => x.JobRun.Status == (int)JobRunStatus.Aborted); stats.TotalDyeingLength = (int)_model.StatisticsResult.JobRuns.Where(x => x.JobRun.EndPosition > 0).Select(x => x.Distance).Sum(); stats.AverageDyeingLength = (int)_model.StatisticsResult.JobRuns.Where(x => x.JobRun.EndPosition > 0).Select(x => x.Distance).Average(); var timeRuns = _model.StatisticsResult.JobRuns.Where(z => z.JobRun.EndPosition > 0 && z.JobRun.EndDate != null && z.JobRun.ActualStartDate != null).ToList(); stats.TotalDyeingTime = TimeSpan.FromHours(timeRuns.Sum(x => (x.JobRun.EndDate - x.JobRun.ActualStartDate.Value).TotalHours)); stats.AverageDyeingTime = TimeSpan.FromHours(timeRuns.Average(x => (x.JobRun.EndDate - x.JobRun.ActualStartDate.Value).TotalHours)); SeriesCollection seriesCollection = new SeriesCollection(); List liquidQuantities = new List(); Dictionary jobRunProperties = new Dictionary(); foreach (var liquidType in _liquidTypes.ToList()) { liquidQuantities.Add(new LiquidQuantityModel() { LiquidType = liquidType, Quantity = 0 }); jobRunProperties.Add(liquidType.Type, typeof(JobRunDTO).GetProperty(liquidType.Type.ToString() + "Quantity",BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance)); } foreach (var stop in Stops) { foreach (var quantity in liquidQuantities) { try { quantity.Quantity += Convert.ToDouble(jobRunProperties[quantity.LiquidType.Type].GetValue(stop.JobRun)); } catch (Exception ex) { LogManager.Log(ex, $"Error on summaries for liquid type {quantity.LiquidType.Type}."); } } } var foreground = Application.Current.Resources["FSE_PrimaryForegroundBrush"] as SolidColorBrush; var border = Application.Current.Resources["FSE_BorderBrush"] as SolidColorBrush; liquidQuantities = liquidQuantities.Where(x => x.Quantity > 0).ToList(); foreach (var liquidQuantity in liquidQuantities) { PieSeries series = new PieSeries(); series.Foreground = foreground; series.Stroke = border; series.Fill = liquidQuantity.LiquidType.LiquidTypeBrush; series.FontSize = 12; series.Title = liquidQuantity.Title; series.Values = new ChartValues() { Convert.ToDouble(liquidQuantity.Quantity) }; series.DataLabels = false; series.LabelPoint = Stats.QuantityCounts; seriesCollection.Add(series); } SeriesCollection = seriesCollection; stats.LiquidQuantities = liquidQuantities; } catch (Exception ex) { LogManager.Log(ex, "Error generating statistics summaries."); } Stats = stats; } private List AutoCompleteJobs(string key) { key = key ?? String.Empty; return FiltersData.Jobs.Where(x => x != null).Where(x => x.ToLower().Contains(key.ToLower())).ToList(); } private void Clear() { Stops?.Clear(); Stats = new StatsModel(); } #endregion #region Stop Commands Handler private async void ShowJobRunFailedMessage(StopModel stop) { await NotificationProvider.ShowError($"Job Failure Message:\n{stop.JobRun.FailedMessage}"); } private async void ShowJobRunExtendedInfo(StopModel stop) { if (stop.ExtendedInfo == null) { try { using (var task = NotificationProvider.PushTaskItem($"Retrieving extended job run ({stop.JobRun.ID}) information...", false)) { await Task.Delay(1000); stop.ExtendedInfo = await StatisticsProvider.GetJobRunExtendedInfo(stop.JobRun.ID); } } catch (Exception ex) { await NotificationProvider.ShowError($"Could not retrieve extended job run information.\n{ex.Message}"); return; } } var vm = await NotificationProvider.ShowDialog(new JobRunExtendedInfoViewVM() { ExtendedInfo = stop.ExtendedInfo }); if (vm.Result == JobRunExtendedInfoViewVM.JobRunExtendedInfoDialogResult.ExportJson) { var result = await StorageProvider.SaveFile("Export job run extended information", "JSON Files|*.json", $"JobRun_{stop.JobRun.JobName}_{stop.JobRun.ID}_extended.json", ".json"); if (result) { File.WriteAllText(result.SelectedItem, vm.ExtendedInfo.ToJsonString()); NotificationProvider.PushSnackbarItem(MessageType.Success, "Job Run Extended Information Export", true, "Export completed successfully.\nTap to open the file.", null, null, async () => { await StorageProvider.ShowInExplorer(result.SelectedItem); }); } } else if (vm.Result == JobRunExtendedInfoViewVM.JobRunExtendedInfoDialogResult.ExportCSV) { var result = await StorageProvider.SaveFile("Export job run job ticket", "CSV Files|*.csv", $"JobRun_{stop.JobRun.JobName}_{stop.JobRun.ID}_ticket.csv", ".csv"); if (result) { var dispensers = vm.ExtendedInfo.JobTicket.Segments.SelectMany(x => x.BrushStops).SelectMany(x => x.Dispensers).Select(x => new JobTicketCsvModel() { DispenserIndex = x.Index.ToString(), JobName = stop.JobRun.JobName, LiquidType = x.DispenserLiquidType.ToString(), NanoliterPerCM = x.NanoliterPerCentimeter.ToString(), NanoliterPerSec = x.NanolitterPerSecond.ToString(), SegmentIndex = stop.SegmentIndex.ToString(), StopIndex = stop.Index.ToString(), Volume = x.Volume.ToString(), }).ToList(); using (CsvFile csvFile = new CsvFile(new CsvDestination(result.SelectedItem))) { foreach (var dispenser in dispensers) { csvFile.Append(dispenser); } } NotificationProvider.PushSnackbarItem(MessageType.Success, "Job Run Ticket Export", true, "Export completed successfully.\nTap to open the file.", null, null, async () => { await StorageProvider.ShowInExplorer(result.SelectedItem); }); } } } private void CopyStopToClipboard(StopModel stop) { String input = String.Empty; switch (stop.ColorSpace) { case ColorSpaces.RGB: input = $"{stop.Red}\t{stop.Green}\t{stop.Blue}"; break; case ColorSpaces.LAB: input = $"{stop.L}\t{stop.A}\t{stop.B}"; break; case ColorSpaces.Catalog: input = $"{stop.Catalog}\t{stop.CatalogItem}"; break; case ColorSpaces.Volume: input = String.Join("\t", stop.GetVolumeInputs().Where(x => x.Volume > 0).Select(x => x.Volume)); break; } Clipboard.SetText($"{stop.ColorSpace}\t{input}\t{String.Join("\t", stop.Output.Where(x => x.LiquidType.HasPigment).Where(x => x.Volume > 0).Select(x => x.Volume))}", TextDataFormat.Text); } #endregion #region CSV Export private async void ExportToCSV() { var result = await StorageProvider.SaveFile("Export To CSV File", "CSV Files|*.csv", $"{MachineProvider.Machine.SerialNumber}_JobRuns_{DateTime.Now.ToFileName()}.csv", ".csv"); if (result.Confirmed) { using (NotificationProvider.PushTaskItem("Exporting job runs to file...")) { await Task.Delay(1000); try { ExportToCSV(Stops.ToList(), result.SelectedItem); NotificationProvider.PushSnackbarItem(MessageType.Success, "Statistics File Export", true, "Export completed successfully.\nTap to open the file.", null, null, async () => { await StorageProvider.ShowInExplorer(result.SelectedItem); }); } catch (Exception ex) { LogManager.Log(ex, "Error exporting csv file."); await NotificationProvider.ShowError($"Error exporting the csv file.\n{ex.FlattenMessage()}"); } } } } private void ExportToCSV(List stops, string filePath) { 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; // 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)) { // ===== 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: 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: 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: 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: // 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; } // ===== Outputs for all liquids (dynamic by enum order) ===== for (int i = 0; i < liquids.Length; i++) { 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); } // ===== Failure reason (last) ===== if (stop.JobRun.FailedMessage.IsNotNullOrEmpty()) csv.Write(stop.JobRun.FailedMessage, "Failure Reason", "", IDX_FailureReason); // Next row csv.Next(); } csv.Save(filePath); } #endregion #region Streaming private async void OpenStreamingSettings() { await NotificationProvider.ShowDialog(); Settings.Save(); } private void StatisticsProvider_JobRunReceived(object sender, JobRunReceivedEventArgs e) { var stops = CreateJobRunStops(e.JobRunComposition); stops.Reverse(); InvokeUI(() => { try { if (Stops.Count == 1 && Stops[0].JobIndex == NULL_JOB_INDEX) { Stops.Clear(); } if (stops.First().IsFineTuning && stops.First().FineTuningModel != null) { var sessionStop = Stops.Where(x => x.IsFineTuning).FirstOrDefault(x => x.FineTuningModel.FineTuningSessionID == stops[0].FineTuningModel.FineTuningSessionID); if (sessionStop != null) { stops.First().JobIndex = sessionStop.JobIndex; stops.First().IsFineTuningApproved = sessionStop.IsFineTuningApproved; } } foreach (var stop in stops) { Stops.Insert(0, stop); } _model.StatisticsResult.JobRuns.Add(e.JobRunComposition); UpdateSummaries(); } catch (Exception ex) { LogManager.Log(ex, "Statistics streaming error updating the job runs grid"); } RaisePropertyChanged(nameof(IsResultsAvailable)); InvalidateRelayCommands(); }); if (Settings.StatisticsStreamingConfig.EnableCsvReports) { try { if (!Directory.Exists(Settings.StatisticsStreamingConfig.CsvReportsFolder)) { Directory.CreateDirectory(Settings.StatisticsStreamingConfig.CsvReportsFolder); } stops.Reverse(); String suffix = "Run"; if (stops[0].JobRun.JobDesignation == (int)JobDesignations.FineTuning) { suffix = "VFT"; } String fileName = $"{MachineProvider.Machine.SerialNumber}_{stops[0].JobRun.JobName}_{suffix}_{DateTime.Now.ToFileName()}.csv"; ExportToCSV(stops, Path.Combine(Settings.StatisticsStreamingConfig.CsvReportsFolder, fileName)); } catch (Exception ex) { LogManager.Log(ex, "Statistics streaming error exporting the job run csv report."); } } } #endregion } }