From 0a38114c74154930c7ef9594ec52d140be29ff78 Mon Sep 17 00:00:00 2001 From: James Friel Date: Mon, 9 Oct 2023 08:11:55 +0100 Subject: [PATCH 01/26] add basic colhor splitter --- .../CommandExecution/AtomicCommandFactory.cs | 2 +- .../ExecuteCommandCreateHoldoutCohort.cs | 39 +++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandCreateHoldoutCohort.cs diff --git a/Rdmp.Core/CommandExecution/AtomicCommandFactory.cs b/Rdmp.Core/CommandExecution/AtomicCommandFactory.cs index c68e3c7b35..4984a63bc4 100644 --- a/Rdmp.Core/CommandExecution/AtomicCommandFactory.cs +++ b/Rdmp.Core/CommandExecution/AtomicCommandFactory.cs @@ -469,7 +469,7 @@ public IEnumerable CreateCommands(object o) yield return new ExecuteCommandViewData(_activator, cic, ViewType.All, null, true) { Weight = -99.7f }; yield return new ExecuteCommandViewData(_activator, cic, ViewType.All, null, false) { Weight = -99.6f }; - + yield return new ExecuteCommandCreateHoldoutCohort(_activator,cic) { Weight = -99.5f }; yield return new ExecuteCommandFreezeCohortIdentificationConfiguration(_activator, cic, !cic.Frozen) { Weight = -50.5f }; diff --git a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandCreateHoldoutCohort.cs b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandCreateHoldoutCohort.cs new file mode 100644 index 0000000000..35254ad4c8 --- /dev/null +++ b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandCreateHoldoutCohort.cs @@ -0,0 +1,39 @@ +// Copyright (c) The University of Dundee 2018-2023 +// This file is part of the Research Data Management Platform (RDMP). +// RDMP is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +// RDMP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +// You should have received a copy of the GNU General Public License along with RDMP. If not, see . + +using Rdmp.Core.Curation.Data.Cohort; +using Rdmp.Core.Icons.IconProvision; +using Rdmp.Core.ReusableLibraryCode.Icons.IconProvision; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; + +namespace Rdmp.Core.CommandExecution.AtomicCommands; + +public class ExecuteCommandCreateHoldoutCohort : BasicCommandExecution +{ + private readonly CohortIdentificationConfiguration _cic; + //private readonly bool _desiredFreezeState; + + public ExecuteCommandCreateHoldoutCohort(IBasicActivateItems activator, + CohortIdentificationConfiguration cic) : base(activator) + { + _cic = cic; + } + + public override string GetCommandHelp() => "TODO"; + + + public override string GetCommandName() => "Create Holdout Cohort"; + + public override void Execute() + { + base.Execute(); + + } + + public override Image GetImage(IIconProvider iconProvider) => + iconProvider.GetImage(RDMPConcept.AllCohortsNode, OverlayKind.Add); +} \ No newline at end of file From 0546d0daa2a07d853e65a607ad53f91beafd5072 Mon Sep 17 00:00:00 2001 From: James Friel Date: Mon, 9 Oct 2023 09:47:22 +0100 Subject: [PATCH 02/26] basic reduction --- .../Pipeline/ExtractionHoldout.cs | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs diff --git a/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs b/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs new file mode 100644 index 0000000000..e94aca8653 --- /dev/null +++ b/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs @@ -0,0 +1,90 @@ +using Rdmp.Core.Curation.Data; +using Rdmp.Core.DataExport.DataExtraction.Commands; +using Rdmp.Core.DataFlowPipeline; +using Rdmp.Core.DataFlowPipeline.Requirements; +using Rdmp.Core.ReusableLibraryCode.Checks; +using Rdmp.Core.ReusableLibraryCode.Progress; +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Rdmp.Core.DataExport.DataExtraction.Pipeline; + +public class ExtractionHoldout : IPluginDataFlowComponent, IPipelineRequirement +{ + [DemandsInitialization("The % of the data you want to be kelp as holdout data")] + public int holdoutCount { get; set; } + + [DemandsInitialization("Use a % as holdout value. If unselected, the actual number will be used.")] + public bool isPercentage { get; set; } + + + public IExtractDatasetCommand Request { get; private set; } + + private int getHoldoutRowCount(DataTable toProcess, IDataLoadEventListener listener) + { + int rowCount = holdoutCount; + if (rowCount >= toProcess.Rows.Count && !isPercentage) + { + listener.OnNotify(this, new NotifyEventArgs(ProgressEventType.Warning, "More holdout data was requested than there is available data. All data will be held back")); + rowCount = toProcess.Rows.Count; + } + if (isPercentage) + { + if (holdoutCount > 100) + { + listener.OnNotify(this, new NotifyEventArgs(ProgressEventType.Warning, "Holdout percentage was >100%. Will use 100%")); + holdoutCount = 100; + } + rowCount = toProcess.Rows.Count / 100 * holdoutCount; + } + return rowCount; + } + + public void PreInitialize(IExtractCommand request, IDataLoadEventListener listener) + { + Request = request as IExtractDatasetCommand; + + // We only care about dataset extraction requests + if (Request == null) + return; + //tod need to do a bunch of error handling + + } + + public DataTable ProcessPipelineData(DataTable toProcess, IDataLoadEventListener listener, GracefulCancellationToken cancellationToken) + { + if (toProcess.Rows.Count == 0) + { + return toProcess; + } + DataTable holdoutData = toProcess.Clone(); + int holdoutCount = getHoldoutRowCount(toProcess, listener); + var rand = new Random(); + Console.WriteLine("Hello world!"); + var rowsToMove = toProcess.AsEnumerable().OrderBy(r => rand.Next()).Take(holdoutCount); + foreach (DataRow row in rowsToMove) + { + holdoutData.ImportRow(row); + row.Delete(); + } + holdoutData.AcceptChanges(); + toProcess.AcceptChanges(); + + return toProcess; + } + + public void Check(ICheckNotifier notifier) + { + //todo + } + public void Abort(IDataLoadEventListener listener) + { + } + public void Dispose(IDataLoadEventListener listener, Exception pipelineFailureExceptionIfAny) + { + } +} From 5d89d925e79db0c1096c12e86aaa163f056737da Mon Sep 17 00:00:00 2001 From: James Friel Date: Mon, 9 Oct 2023 10:57:08 +0100 Subject: [PATCH 03/26] store holdout information --- .../Pipeline/ExtractionHoldout.cs | 38 +++++++++++++++++-- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs b/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs index e94aca8653..d571e2ce76 100644 --- a/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs +++ b/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; using System.Data; +using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -21,6 +22,9 @@ public class ExtractionHoldout : IPluginDataFlowComponent, IPipelineR [DemandsInitialization("Use a % as holdout value. If unselected, the actual number will be used.")] public bool isPercentage { get; set; } + [DemandsInitialization("Write the holdout data to disk.")] + public string holdoutStorageLocation { get; set; } + public IExtractDatasetCommand Request { get; private set; } @@ -55,6 +59,23 @@ public void PreInitialize(IExtractCommand request, IDataLoadEventListener listen } + private void writeDataTabletoCSV(DataTable dt) + { + StringBuilder sb = new StringBuilder(); + + IEnumerable columnNames = dt.Columns.Cast(). + Select(column => column.ColumnName); + sb.AppendLine(string.Join(",", columnNames)); + + foreach (DataRow row in dt.Rows) + { + IEnumerable fields = row.ItemArray.Select(field => field.ToString()); + sb.AppendLine(string.Join(",", fields)); + } + String filename = Request.ToString(); + File.WriteAllText($"{holdoutStorageLocation}/holdout_{filename}.csv", sb.ToString()); + } + public DataTable ProcessPipelineData(DataTable toProcess, IDataLoadEventListener listener, GracefulCancellationToken cancellationToken) { if (toProcess.Rows.Count == 0) @@ -64,16 +85,27 @@ public DataTable ProcessPipelineData(DataTable toProcess, IDataLoadEventListener DataTable holdoutData = toProcess.Clone(); int holdoutCount = getHoldoutRowCount(toProcess, listener); var rand = new Random(); - Console.WriteLine("Hello world!"); + holdoutData.BeginLoadData(); + toProcess.BeginLoadData(); var rowsToMove = toProcess.AsEnumerable().OrderBy(r => rand.Next()).Take(holdoutCount); foreach (DataRow row in rowsToMove) { holdoutData.ImportRow(row); row.Delete(); } - holdoutData.AcceptChanges(); - toProcess.AcceptChanges(); + holdoutData.EndLoadData(); + toProcess.EndLoadData(); + //todo need to sdo something with the holdoutCount + //maybe reimport it as a catalog? + if (holdoutStorageLocation is not null && holdoutStorageLocation.Length > 0) + { + //write this data somewhere + writeDataTabletoCSV(holdoutData); + } + { + } + Catalogue x = new Catalogue(); return toProcess; } From 2896b1c9483f17bdf38757ae566a1c061a4c484d Mon Sep 17 00:00:00 2001 From: James Friel Date: Mon, 9 Oct 2023 11:17:24 +0100 Subject: [PATCH 04/26] add start of catalogue storage --- .../Pipeline/ExtractionHoldout.cs | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs b/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs index d571e2ce76..da0766c82a 100644 --- a/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs +++ b/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs @@ -1,4 +1,6 @@ -using Rdmp.Core.Curation.Data; +using Rdmp.Core.CommandExecution; +using Rdmp.Core.CommandExecution.AtomicCommands.CatalogueCreationCommands; +using Rdmp.Core.Curation.Data; using Rdmp.Core.DataExport.DataExtraction.Commands; using Rdmp.Core.DataFlowPipeline; using Rdmp.Core.DataFlowPipeline.Requirements; @@ -22,9 +24,12 @@ public class ExtractionHoldout : IPluginDataFlowComponent, IPipelineR [DemandsInitialization("Use a % as holdout value. If unselected, the actual number will be used.")] public bool isPercentage { get; set; } - [DemandsInitialization("Write the holdout data to disk.")] + [DemandsInitialization("Write the holdout data to disk. Leave blank if you don't want it exported somewhere")] public string holdoutStorageLocation { get; set; } + [DemandsInitialization("Re-Import the holdout data into RDMP as a new Catalogue.")] + public bool reImportToRDMP { get; set; } + public IExtractDatasetCommand Request { get; private set; } @@ -73,9 +78,14 @@ private void writeDataTabletoCSV(DataTable dt) sb.AppendLine(string.Join(",", fields)); } String filename = Request.ToString(); + //todo strip out extra stashes in sotrage location File.WriteAllText($"{holdoutStorageLocation}/holdout_{filename}.csv", sb.ToString()); } + private void StoreHoldoutDataAsNewCatalogue(DataTable dt) + { + } + public DataTable ProcessPipelineData(DataTable toProcess, IDataLoadEventListener listener, GracefulCancellationToken cancellationToken) { if (toProcess.Rows.Count == 0) @@ -99,13 +109,13 @@ public DataTable ProcessPipelineData(DataTable toProcess, IDataLoadEventListener //maybe reimport it as a catalog? if (holdoutStorageLocation is not null && holdoutStorageLocation.Length > 0) { - //write this data somewhere writeDataTabletoCSV(holdoutData); } - { + if (reImportToRDMP) + { + StoreHoldoutDataAsNewCatalogue(holdoutData); } - Catalogue x = new Catalogue(); return toProcess; } From 385c8ea46d5fce72742d4fcf83ad438784919f01 Mon Sep 17 00:00:00 2001 From: James Friel Date: Mon, 9 Oct 2023 11:42:35 +0100 Subject: [PATCH 05/26] tidy up --- .../Pipeline/ExtractionHoldout.cs | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs b/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs index da0766c82a..5765abdfb0 100644 --- a/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs +++ b/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs @@ -27,9 +27,20 @@ public class ExtractionHoldout : IPluginDataFlowComponent, IPipelineR [DemandsInitialization("Write the holdout data to disk. Leave blank if you don't want it exported somewhere")] public string holdoutStorageLocation { get; set; } - [DemandsInitialization("Re-Import the holdout data into RDMP as a new Catalogue.")] - public bool reImportToRDMP { get; set; } + [DemandsInitialization("Set this value to only select data for holdout that is before this date")] + public DateTime beforeDate { get; set; } + [DemandsInitialization("Set this value to only select data for holdout that is after this date")] + public DateTime afterDate { get; set; } + + [DemandsInitialization("The column that the befor and after date options use to filter holdout data")] + public String timestampColumn { get; set; } + + // We may want to automatically reimport into RDMP, but this is quite complicated. It may be worth having users reimport the catalogue themself until it is proven that this is worth building. + //Currently only support writting holdback data to a CSV + + //TODO - force date range + //TODO - force specific holdback criteria public IExtractDatasetCommand Request { get; private set; } @@ -78,14 +89,11 @@ private void writeDataTabletoCSV(DataTable dt) sb.AppendLine(string.Join(",", fields)); } String filename = Request.ToString(); - //todo strip out extra stashes in sotrage location + holdoutStorageLocation.TrimEnd('/'); + holdoutStorageLocation.TrimEnd('\\'); File.WriteAllText($"{holdoutStorageLocation}/holdout_{filename}.csv", sb.ToString()); } - private void StoreHoldoutDataAsNewCatalogue(DataTable dt) - { - } - public DataTable ProcessPipelineData(DataTable toProcess, IDataLoadEventListener listener, GracefulCancellationToken cancellationToken) { if (toProcess.Rows.Count == 0) @@ -105,17 +113,10 @@ public DataTable ProcessPipelineData(DataTable toProcess, IDataLoadEventListener } holdoutData.EndLoadData(); toProcess.EndLoadData(); - //todo need to sdo something with the holdoutCount - //maybe reimport it as a catalog? if (holdoutStorageLocation is not null && holdoutStorageLocation.Length > 0) { writeDataTabletoCSV(holdoutData); } - - if (reImportToRDMP) - { - StoreHoldoutDataAsNewCatalogue(holdoutData); - } return toProcess; } From 2bd16b2e8443df8c4baa3f9fa0a2e9884d00fcba Mon Sep 17 00:00:00 2001 From: James Friel Date: Mon, 9 Oct 2023 14:00:24 +0100 Subject: [PATCH 06/26] interim --- .../Pipeline/ExtractionHoldout.cs | 66 ++++++++++++++++--- 1 file changed, 57 insertions(+), 9 deletions(-) diff --git a/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs b/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs index 5765abdfb0..844f5ecc04 100644 --- a/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs +++ b/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs @@ -1,4 +1,5 @@ -using Rdmp.Core.CommandExecution; +using NPOI.SS.Formula.Functions; +using Rdmp.Core.CommandExecution; using Rdmp.Core.CommandExecution.AtomicCommands.CatalogueCreationCommands; using Rdmp.Core.Curation.Data; using Rdmp.Core.DataExport.DataExtraction.Commands; @@ -9,6 +10,7 @@ using System; using System.Collections.Generic; using System.Data; +using System.Globalization; using System.IO; using System.Linq; using System.Text; @@ -34,7 +36,7 @@ public class ExtractionHoldout : IPluginDataFlowComponent, IPipelineR public DateTime afterDate { get; set; } [DemandsInitialization("The column that the befor and after date options use to filter holdout data")] - public String timestampColumn { get; set; } + public String dateColumn { get; set; } // We may want to automatically reimport into RDMP, but this is quite complicated. It may be worth having users reimport the catalogue themself until it is proven that this is worth building. //Currently only support writting holdback data to a CSV @@ -44,13 +46,50 @@ public class ExtractionHoldout : IPluginDataFlowComponent, IPipelineR public IExtractDatasetCommand Request { get; private set; } + + private DataTable FilterableData { get; set; } //Rows that are valid as holdout data based on the user filters + + private bool validateIfRowShouldBeFiltered(DataRow row) + { + if (!string.IsNullOrEmpty(dateColumn)) + { + //had a data column + DateTime dateCell = DateTime.Parse(row.Field(dateColumn), CultureInfo.InvariantCulture); + if (afterDate != DateTime.MinValue) + { + //has date + if (dateCell <= afterDate) + { + return false; + } + } + if (beforeDate != DateTime.MinValue) + { + //has date + if (dateCell >= beforeDate) + { + return false; + } + } + } + + return true; + } + + private DataTable filterRowsBasedOnHoldoutDates(DataTable toProcess) + { + DataTable filteredTable = toProcess.AsEnumerable().Where(row => validateIfRowShouldBeFiltered(row)).CopyToDataTable(); + return filteredTable; + } + private int getHoldoutRowCount(DataTable toProcess, IDataLoadEventListener listener) { + int rowCount = holdoutCount; - if (rowCount >= toProcess.Rows.Count && !isPercentage) + if (rowCount >= FilterableData.Rows.Count && !isPercentage) { - listener.OnNotify(this, new NotifyEventArgs(ProgressEventType.Warning, "More holdout data was requested than there is available data. All data will be held back")); - rowCount = toProcess.Rows.Count; + listener.OnNotify(this, new NotifyEventArgs(ProgressEventType.Warning, "More holdout data was requested than there is available data. All valid data will be held back")); + rowCount = FilterableData.Rows.Count; } if (isPercentage) { @@ -59,7 +98,7 @@ private int getHoldoutRowCount(DataTable toProcess, IDataLoadEventListener liste listener.OnNotify(this, new NotifyEventArgs(ProgressEventType.Warning, "Holdout percentage was >100%. Will use 100%")); holdoutCount = 100; } - rowCount = toProcess.Rows.Count / 100 * holdoutCount; + rowCount = FilterableData.Rows.Count / 100 * holdoutCount; } return rowCount; } @@ -88,9 +127,10 @@ private void writeDataTabletoCSV(DataTable dt) IEnumerable fields = row.ItemArray.Select(field => field.ToString()); sb.AppendLine(string.Join(",", fields)); } - String filename = Request.ToString(); + string filename = Request.ToString(); holdoutStorageLocation.TrimEnd('/'); holdoutStorageLocation.TrimEnd('\\'); + //todo this isn't the correct filename File.WriteAllText($"{holdoutStorageLocation}/holdout_{filename}.csv", sb.ToString()); } @@ -100,16 +140,24 @@ public DataTable ProcessPipelineData(DataTable toProcess, IDataLoadEventListener { return toProcess; } + FilterableData = toProcess; + if (dateColumn is not null && (afterDate != DateTime.MinValue || beforeDate != DateTime.MinValue)) + { + FilterableData = filterRowsBasedOnHoldoutDates(toProcess); + } + DataTable holdoutData = toProcess.Clone(); int holdoutCount = getHoldoutRowCount(toProcess, listener); var rand = new Random(); holdoutData.BeginLoadData(); toProcess.BeginLoadData(); - var rowsToMove = toProcess.AsEnumerable().OrderBy(r => rand.Next()).Take(holdoutCount); + + var rowsToMove = FilterableData.AsEnumerable().OrderBy(r => rand.Next()).Take(holdoutCount); foreach (DataRow row in rowsToMove) { holdoutData.ImportRow(row); - row.Delete(); + toProcess.Rows.Remove(row); + //row.Delete(); } holdoutData.EndLoadData(); toProcess.EndLoadData(); From 512436fb8b5db3ec511d608a64a42a2b0c1e16ea Mon Sep 17 00:00:00 2001 From: James Friel Date: Mon, 9 Oct 2023 14:09:41 +0100 Subject: [PATCH 07/26] working date filtering --- .../Pipeline/ExtractionHoldout.cs | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs b/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs index 844f5ecc04..22e048dc42 100644 --- a/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs +++ b/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs @@ -1,4 +1,5 @@ using NPOI.SS.Formula.Functions; +using Org.BouncyCastle.Asn1.X509.Qualified; using Rdmp.Core.CommandExecution; using Rdmp.Core.CommandExecution.AtomicCommands.CatalogueCreationCommands; using Rdmp.Core.Curation.Data; @@ -47,8 +48,6 @@ public class ExtractionHoldout : IPluginDataFlowComponent, IPipelineR public IExtractDatasetCommand Request { get; private set; } - private DataTable FilterableData { get; set; } //Rows that are valid as holdout data based on the user filters - private bool validateIfRowShouldBeFiltered(DataRow row) { if (!string.IsNullOrEmpty(dateColumn)) @@ -76,20 +75,25 @@ private bool validateIfRowShouldBeFiltered(DataRow row) return true; } - private DataTable filterRowsBasedOnHoldoutDates(DataTable toProcess) + private void filterRowsBasedOnHoldoutDates(DataTable toProcess) { - DataTable filteredTable = toProcess.AsEnumerable().Where(row => validateIfRowShouldBeFiltered(row)).CopyToDataTable(); - return filteredTable; + toProcess.Columns.Add("_isValidHoldout", typeof(bool)); + foreach(DataRow row in toProcess.Rows) + { + row["_isValidHoldout"] = validateIfRowShouldBeFiltered(row); + } + // DataTable filteredTable = toProcess.AsEnumerable().Where(row => validateIfRowShouldBeFiltered(row)).CopyToDataTable(); + // return filteredTable; } private int getHoldoutRowCount(DataTable toProcess, IDataLoadEventListener listener) { int rowCount = holdoutCount; - if (rowCount >= FilterableData.Rows.Count && !isPercentage) + if (rowCount >= toProcess.Rows.Count && !isPercentage) { listener.OnNotify(this, new NotifyEventArgs(ProgressEventType.Warning, "More holdout data was requested than there is available data. All valid data will be held back")); - rowCount = FilterableData.Rows.Count; + rowCount = toProcess.Rows.Count; } if (isPercentage) { @@ -98,7 +102,7 @@ private int getHoldoutRowCount(DataTable toProcess, IDataLoadEventListener liste listener.OnNotify(this, new NotifyEventArgs(ProgressEventType.Warning, "Holdout percentage was >100%. Will use 100%")); holdoutCount = 100; } - rowCount = FilterableData.Rows.Count / 100 * holdoutCount; + rowCount = toProcess.Rows.Count / 100 * holdoutCount; } return rowCount; } @@ -140,10 +144,9 @@ public DataTable ProcessPipelineData(DataTable toProcess, IDataLoadEventListener { return toProcess; } - FilterableData = toProcess; if (dateColumn is not null && (afterDate != DateTime.MinValue || beforeDate != DateTime.MinValue)) { - FilterableData = filterRowsBasedOnHoldoutDates(toProcess); + filterRowsBasedOnHoldoutDates(toProcess); } DataTable holdoutData = toProcess.Clone(); @@ -152,12 +155,11 @@ public DataTable ProcessPipelineData(DataTable toProcess, IDataLoadEventListener holdoutData.BeginLoadData(); toProcess.BeginLoadData(); - var rowsToMove = FilterableData.AsEnumerable().OrderBy(r => rand.Next()).Take(holdoutCount); + var rowsToMove = toProcess.AsEnumerable().Where(row => row["_isValidHoldout"] is true).OrderBy(r => rand.Next()).Take(holdoutCount); foreach (DataRow row in rowsToMove) { holdoutData.ImportRow(row); toProcess.Rows.Remove(row); - //row.Delete(); } holdoutData.EndLoadData(); toProcess.EndLoadData(); @@ -165,6 +167,7 @@ public DataTable ProcessPipelineData(DataTable toProcess, IDataLoadEventListener { writeDataTabletoCSV(holdoutData); } + toProcess.Columns.Remove("_isValidHoldout"); return toProcess; } From 59f32738e5669d521c04615c64212f52f24d6050 Mon Sep 17 00:00:00 2001 From: James Friel Date: Mon, 9 Oct 2023 15:19:56 +0100 Subject: [PATCH 08/26] basic working extraction --- .../Pipeline/ExtractionHoldout.cs | 33 ++++++++++++------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs b/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs index 22e048dc42..ca3b1b8c2a 100644 --- a/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs +++ b/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs @@ -36,21 +36,23 @@ public class ExtractionHoldout : IPluginDataFlowComponent, IPipelineR [DemandsInitialization("Set this value to only select data for holdout that is after this date")] public DateTime afterDate { get; set; } - [DemandsInitialization("The column that the befor and after date options use to filter holdout data")] - public String dateColumn { get; set; } + [DemandsInitialization("The column that the before and after date options use to filter holdout data")] + public string dateColumn { get; set; } + + //can only filter on strings, not dates + [DemandsInitialization("Allows for the filtering of what data can be used as holdout data. The filter only currently supports filtering on string columns, not dates. Filter References https://learn.microsoft.com/en-us/dotnet/api/system.data.dataview.rowfilter?view=net-7.0 and https://learn.microsoft.com/en-us/dotnet/api/system.data.datacolumn.expression?view=net-7.0")] + public string whereCondition { get; set; } // We may want to automatically reimport into RDMP, but this is quite complicated. It may be worth having users reimport the catalogue themself until it is proven that this is worth building. //Currently only support writting holdback data to a CSV - //TODO - force date range - //TODO - force specific holdback criteria public IExtractDatasetCommand Request { get; private set; } - private bool validateIfRowShouldBeFiltered(DataRow row) + private bool validateIfRowShouldBeFiltered(DataRow row,DataTable toProcess) { - if (!string.IsNullOrEmpty(dateColumn)) + if (!string.IsNullOrWhiteSpace(dateColumn)) { //had a data column DateTime dateCell = DateTime.Parse(row.Field(dateColumn), CultureInfo.InvariantCulture); @@ -71,7 +73,18 @@ private bool validateIfRowShouldBeFiltered(DataRow row) } } } - + if (!string.IsNullOrWhiteSpace(whereCondition)) + { + DataTable dt = toProcess.Clone(); + dt.ImportRow(row); + DataView dv = new DataView(dt); + dv.RowFilter = whereCondition; + DataTable dt2 = dv.ToTable(); + if (dt2.Rows.Count < 1) + { + return false; + } + } return true; } @@ -80,10 +93,8 @@ private void filterRowsBasedOnHoldoutDates(DataTable toProcess) toProcess.Columns.Add("_isValidHoldout", typeof(bool)); foreach(DataRow row in toProcess.Rows) { - row["_isValidHoldout"] = validateIfRowShouldBeFiltered(row); + row["_isValidHoldout"] = validateIfRowShouldBeFiltered(row,toProcess); } - // DataTable filteredTable = toProcess.AsEnumerable().Where(row => validateIfRowShouldBeFiltered(row)).CopyToDataTable(); - // return filteredTable; } private int getHoldoutRowCount(DataTable toProcess, IDataLoadEventListener listener) @@ -134,7 +145,6 @@ private void writeDataTabletoCSV(DataTable dt) string filename = Request.ToString(); holdoutStorageLocation.TrimEnd('/'); holdoutStorageLocation.TrimEnd('\\'); - //todo this isn't the correct filename File.WriteAllText($"{holdoutStorageLocation}/holdout_{filename}.csv", sb.ToString()); } @@ -165,6 +175,7 @@ public DataTable ProcessPipelineData(DataTable toProcess, IDataLoadEventListener toProcess.EndLoadData(); if (holdoutStorageLocation is not null && holdoutStorageLocation.Length > 0) { + holdoutData.Columns.Remove("_isValidHoldout"); writeDataTabletoCSV(holdoutData); } toProcess.Columns.Remove("_isValidHoldout"); From d05fccb8e1b4186a203e5a7bfe7262ad764ce8fd Mon Sep 17 00:00:00 2001 From: James Friel Date: Tue, 10 Oct 2023 09:24:50 +0100 Subject: [PATCH 09/26] trigget ui flow --- .../WindowManagement/ActivateItems.cs | 16 ++++++++++ .../CohortCreationCommandExecution.cs | 30 +++++++++++++++++++ .../ExecuteCommandCreateHoldoutCohort.cs | 10 +++++-- .../CommandExecution/BasicActivateItems.cs | 29 ++++++++++++++++++ .../CommandExecution/IBasicActivateItems.cs | 5 ++++ 5 files changed, 87 insertions(+), 3 deletions(-) diff --git a/Application/ResearchDataManagementPlatform/WindowManagement/ActivateItems.cs b/Application/ResearchDataManagementPlatform/WindowManagement/ActivateItems.cs index 4ca7a92805..6f8e10864f 100644 --- a/Application/ResearchDataManagementPlatform/WindowManagement/ActivateItems.cs +++ b/Application/ResearchDataManagementPlatform/WindowManagement/ActivateItems.cs @@ -840,6 +840,22 @@ public override IPipelineRunner GetPipelineRunner(DialogArgs args, IPipelineUseC return configureAndExecuteDialog; } + public override CohortCreationRequest GetCohortHoldoutCreationRequest(ExternalCohortTable externalCohortTable, IProject project, string cohortInitialDescription) + { + // if on wrong Thread + if (_mainDockPanel?.InvokeRequired ?? false) + return _mainDockPanel.Invoke(() => + GetCohortHoldoutCreationRequest(externalCohortTable, project, cohortInitialDescription)); + + var ui = new CohortCreationRequestUI(this, externalCohortTable, project); + + if (!string.IsNullOrWhiteSpace(cohortInitialDescription)) + ui.CohortDescription = $"{cohortInitialDescription} ({Environment.UserName} - {DateTime.Now})"; + + return ui.ShowDialog() == DialogResult.OK ? ui.Result : null; + } + + public override CohortCreationRequest GetCohortCreationRequest(ExternalCohortTable externalCohortTable, IProject project, string cohortInitialDescription) { diff --git a/Rdmp.Core/CommandExecution/AtomicCommands/CohortCreationCommands/CohortCreationCommandExecution.cs b/Rdmp.Core/CommandExecution/AtomicCommands/CohortCreationCommands/CohortCreationCommandExecution.cs index 9ac20303ae..c066997c75 100644 --- a/Rdmp.Core/CommandExecution/AtomicCommands/CohortCreationCommands/CohortCreationCommandExecution.cs +++ b/Rdmp.Core/CommandExecution/AtomicCommands/CohortCreationCommands/CohortCreationCommandExecution.cs @@ -4,6 +4,7 @@ // RDMP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. // You should have received a copy of the GNU General Public License along with RDMP. If not, see . +using System; using System.Linq; using Rdmp.Core.CohortCommitting.Pipeline; using Rdmp.Core.CommandLine.Runners; @@ -68,6 +69,35 @@ protected CohortCreationCommandExecution(IBasicActivateItems activator, External SetImpossible("There are no cohort sources configured, you must create one in the Saved Cohort tabs"); } + protected ICohortCreationRequest GetCohortHoldoutCreationRequest(string auditLogDescription) + { + //user wants to create a new cohort + var ect = ExternalCohortTable; + + //do we know where it's going to end up? + if (ect == null) + if (!SelectOne( + GetChooseCohortDialogArgs(), + BasicActivator.RepositoryLocator.DataExportRepository, + out ect)) //not yet, get user to pick one + return null; //user didn't select one and cancelled dialog + + + //and document the request + + // if we have everything we need to create the cohort right here + if (!string.IsNullOrWhiteSpace(_explicitCohortName) && Project?.ProjectNumber != null) + return GenerateCohortCreationRequestFromNameAndProject(_explicitCohortName, auditLogDescription, ect); + // otherwise we are going to have to ask the user for it + + //Get a new request for the source they are trying to populate + var req = BasicActivator.GetCohortHoldoutCreationRequest(ect, Project, auditLogDescription); + + Project ??= req?.Project; + + return req; + } + protected ICohortCreationRequest GetCohortCreationRequest(string auditLogDescription) { //user wants to create a new cohort diff --git a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandCreateHoldoutCohort.cs b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandCreateHoldoutCohort.cs index 35254ad4c8..b357ab88ac 100644 --- a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandCreateHoldoutCohort.cs +++ b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandCreateHoldoutCohort.cs @@ -5,14 +5,17 @@ // You should have received a copy of the GNU General Public License along with RDMP. If not, see . using Rdmp.Core.Curation.Data.Cohort; +using Rdmp.Core.DataExport.Data; using Rdmp.Core.Icons.IconProvision; using Rdmp.Core.ReusableLibraryCode.Icons.IconProvision; using SixLabors.ImageSharp; using SixLabors.ImageSharp.PixelFormats; -namespace Rdmp.Core.CommandExecution.AtomicCommands; +namespace Rdmp.Core.CommandExecution.AtomicCommands.CohortCreationCommands; -public class ExecuteCommandCreateHoldoutCohort : BasicCommandExecution +//Based off of ExecuteCommandCreateNewCohortByExecutingACohortIdentificationConfiguration + +public class ExecuteCommandCreateHoldoutCohort : CohortCreationCommandExecution { private readonly CohortIdentificationConfiguration _cic; //private readonly bool _desiredFreezeState; @@ -31,7 +34,8 @@ public ExecuteCommandCreateHoldoutCohort(IBasicActivateItems activator, public override void Execute() { base.Execute(); - + var request = GetCohortHoldoutCreationRequest(ExtractableCohortAuditLogBuilder.GetDescription(_cic)); + //here } public override Image GetImage(IIconProvider iconProvider) => diff --git a/Rdmp.Core/CommandExecution/BasicActivateItems.cs b/Rdmp.Core/CommandExecution/BasicActivateItems.cs index c7917c0c79..461c311b56 100644 --- a/Rdmp.Core/CommandExecution/BasicActivateItems.cs +++ b/Rdmp.Core/CommandExecution/BasicActivateItems.cs @@ -635,6 +635,35 @@ public bool SelectObjects(string prompt, T[] available, out T[] selected, str public virtual IPipelineRunner GetPipelineRunner(DialogArgs args, IPipelineUseCase useCase, IPipeline pipeline) => new PipelineRunner(useCase, pipeline); + /// + public virtual CohortCreationRequest GetCohortHoldoutCreationRequest(ExternalCohortTable externalCohortTable, + IProject project, string cohortInitialDescription) + { + int version; + var projectNumber = project?.ProjectNumber; + + if (!TypeText("Name", "Enter name for cohort", 255, null, out var name, false)) + throw new Exception("User chose not to enter a name for the cohort and none was provided"); + + + if (projectNumber == null) + if (SelectValueType("enter project number", typeof(int), 0, out var chosen)) + projectNumber = (int)chosen; + else + throw new Exception("User chose not to enter a Project number and none was provided"); + + + if (SelectValueType("enter version number for cohort", typeof(int), 0, out var chosenVersion)) + version = (int)chosenVersion; + else + throw new Exception("User chose not to enter a version number and none was provided"); + + + return new CohortCreationRequest(project, + new CohortDefinition(null, name, version, projectNumber.Value, externalCohortTable), + RepositoryLocator.DataExportRepository, cohortInitialDescription); + } + /// public virtual CohortCreationRequest GetCohortCreationRequest(ExternalCohortTable externalCohortTable, IProject project, string cohortInitialDescription) diff --git a/Rdmp.Core/CommandExecution/IBasicActivateItems.cs b/Rdmp.Core/CommandExecution/IBasicActivateItems.cs index 08151a4cf8..111076bd35 100644 --- a/Rdmp.Core/CommandExecution/IBasicActivateItems.cs +++ b/Rdmp.Core/CommandExecution/IBasicActivateItems.cs @@ -163,6 +163,11 @@ public interface IBasicActivateItems #region Select X Modal methods + + //todo description + CohortCreationRequest GetCohortHoldoutCreationRequest(ExternalCohortTable externalCohortTable, IProject project, + string cohortInitialDescription); + /// /// Prompts the user to enter a description for a cohort they are trying to create including whether it is intended to replace an old version of another cohort. /// From 30ac20121fa2edc32965932ef2703dc4f283dd8e Mon Sep 17 00:00:00 2001 From: James Friel Date: Tue, 10 Oct 2023 11:26:02 +0100 Subject: [PATCH 10/26] start of UI --- .../WindowManagement/ActivateItems.cs | 2 +- .../CohortCreationCommandExecution.cs | 4 +- Rdmp.UI/SimpleDialogs/SelectDialog`1.resx | 982 ------------------ 3 files changed, 3 insertions(+), 985 deletions(-) delete mode 100644 Rdmp.UI/SimpleDialogs/SelectDialog`1.resx diff --git a/Application/ResearchDataManagementPlatform/WindowManagement/ActivateItems.cs b/Application/ResearchDataManagementPlatform/WindowManagement/ActivateItems.cs index 6f8e10864f..543da4aedf 100644 --- a/Application/ResearchDataManagementPlatform/WindowManagement/ActivateItems.cs +++ b/Application/ResearchDataManagementPlatform/WindowManagement/ActivateItems.cs @@ -847,7 +847,7 @@ public override CohortCreationRequest GetCohortHoldoutCreationRequest(ExternalCo return _mainDockPanel.Invoke(() => GetCohortHoldoutCreationRequest(externalCohortTable, project, cohortInitialDescription)); - var ui = new CohortCreationRequestUI(this, externalCohortTable, project); + var ui = new Rdmp.UI.CohortUI.CohortHoldout.CohortHoldoutCreationRequestUI(this, externalCohortTable, project); if (!string.IsNullOrWhiteSpace(cohortInitialDescription)) ui.CohortDescription = $"{cohortInitialDescription} ({Environment.UserName} - {DateTime.Now})"; diff --git a/Rdmp.Core/CommandExecution/AtomicCommands/CohortCreationCommands/CohortCreationCommandExecution.cs b/Rdmp.Core/CommandExecution/AtomicCommands/CohortCreationCommands/CohortCreationCommandExecution.cs index c066997c75..5c5c35ec30 100644 --- a/Rdmp.Core/CommandExecution/AtomicCommands/CohortCreationCommands/CohortCreationCommandExecution.cs +++ b/Rdmp.Core/CommandExecution/AtomicCommands/CohortCreationCommands/CohortCreationCommandExecution.cs @@ -86,8 +86,8 @@ protected ICohortCreationRequest GetCohortHoldoutCreationRequest(string auditLog //and document the request // if we have everything we need to create the cohort right here - if (!string.IsNullOrWhiteSpace(_explicitCohortName) && Project?.ProjectNumber != null) - return GenerateCohortCreationRequestFromNameAndProject(_explicitCohortName, auditLogDescription, ect); + //if (!string.IsNullOrWhiteSpace(_explicitCohortName) && Project?.ProjectNumber != null) + //return GenerateCohortCreationRequestFromNameAndProject(_explicitCohortName, auditLogDescription, ect); // otherwise we are going to have to ask the user for it //Get a new request for the source they are trying to populate diff --git a/Rdmp.UI/SimpleDialogs/SelectDialog`1.resx b/Rdmp.UI/SimpleDialogs/SelectDialog`1.resx deleted file mode 100644 index 68cc010e31..0000000000 --- a/Rdmp.UI/SimpleDialogs/SelectDialog`1.resx +++ /dev/null @@ -1,982 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - 17, 17 - - - - - R0lGODlhZABkAPcAAPhw5P7+/v78/vLc8P5K5P4g3P4K2v6a8Pym7vxE4Pw24PTE7vy68vyi8Pb29v5q - 6P484P4W2v6q8vx86PqW6vz6/Pj4+Ppq5PpM4PiO6Pz6+viC5vh45vi68PiK6Pz4/Pzr+fj2+PbE8Paa - 6PrO8vTS8PLy8vTM7vj0+Pjt9vTw8vTs9PLs8v5a5v4w4Pxg5vqM6v5x6P464v4a3PyS7Pqi7vpu5vpm - 5Piy7viA6Pau7Pbn9Pai6PTW7vyF7Pqd7PqY7Pic7PqE6PyE6vx+6vx86viW6vas6vxC4PTo8vxU5PzU - 9v4Y2vTm8v4c3P5Y5P5M5Ppu5Pps5Ppc5Pjc8/bW8PxI4vxC4vTi8P4q3vxG4Pw+4P5A4PTg8v4e3P4o - 3v4k3v4i3v4e3v4m3P4W3PTc8vjI8PbS8PzH9PbM8Pqz8PzC8vjC8P5C4vi88P5I5Py+8vy88vut8P6q - 8Pyo8P6i8Pym8Pyk8PqQ6v566vqK6vp+6PqO6vpw5viK5vpg5PqI6v5/6/6K7vye7vyK7PyI6vyA6vyC - 6vx16Pxc5PxM4vxK4PxI4Pi47va27Pig6vie6v6Q7fyM6vxv5/6c7vyO6vyO7PyQ7Pxr5/5i5vqm7vqg - 7P5W5Pxi5vyg7vqm7PxY5Pqo7v5U5Pqq7v6x8v5S5Pqs7v5Q5P669Pqw7v5O5Pqy7vxK4va87P689PbA - 7vz8/P7S9/464P4t3/444P424P404P4y4PxS4v76/v7n+vro9/ya7vxe5PyW7Pxe5vxg5Pp05vxk5Prf - 9vy48vyS7vp25vzb9/qC6P7E9f7g+P76/Pp45vbW8vq68Pp66Pz4+vrA8vr4+fz0/PrX9ffw9/Xx9Pbu - 9PzY9/ji9PbS8vjT8vbQ8vzN9fu28fzC8/jE8PjA8Py99Puw8Pyq8P6o8P649P7A9P78/P7a+P7w/Pry - +Prj9/zh+P7L9v7j+vbf8vrI8vy28vyv8vzy+vrO9P546vqk7vyJ6/jK8P6I7P4w3vrs+Pz2/Pz2+gAA - ACH5BAAFAAAAIf8LTkVUU0NBUEUyLjADAQAAACwAAAAAZABkAAAI/gADCBxIsKDBgwgTCtTgwIRDBxUU - SpxIsaLFiwJR6GnzxkO1iBhDihwZkpCMkzL4aCDJsqVLgRXaoJRBYOXLmzgphpgpA4LNnECDDnTA06fQ - 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 - PI9WCGup3FW1DOO35OjwhWKtEKq2FlwSrACCRQvsTPM6IfRILir6nBOzilYclCr1aSHWjeS+GknTKIZo - i+FeYxN6fpJ2LCko3SFX/BY1E5H2AdJ2jOS+dkWhFm0d0qbutAkAua0Fcock8Nv7dOrYEtFFclXboQr8 - lhTMU7euC5KM5K5IA9o+q4LrguaJJFvcK4LFh88a4n/r+CIlhuuJQB/ejcS+G8AVp0g6XPFfCXZV4VKp - tQ7VzWxEO9q7Mrf5RIaLWuPq/CimRS3Bfx4UaCBWsTUn+oykYQG25vwlAQEAIfkEAAUAAAAsAAAAAGQA - ZACH9nzk/v7+/vz+8t7w/kTi/iTe/gTa/pTu/Jzs/Eri/Bzc8szs/Jru+Pj4/mjm/jTg/gba/qTw/I7q - +oTo+vr6+lLi+j7e+qTu+Ijm+vj6+vj4+Gzi+GLi+LTu/Pb79ojm9sLu8vLy9pLo/hTc+fP49vL19PL0 - 9OLy9NTw9Mbu9Lbq9Kzq9KLo/Mj08ujw8uLw9erz9Oby9Njw9M7u/lTm/jLg/HLo/ELg/nTq/jrh/hLa - /Jbs+pLs+lji+Ibo+tn1+Jjq+HDk9rDs/hnc9Lzs/JTs/JLs/JDs/I7s/Izs/mTo+pLq+orq+JLq9p7q - /Fzk+obo+JDo+Ijo9try/iLc/mrm/kbi/FLi+OD09OTy/FDi/Ezi+lTi/ibe/DLc+kjg+kTg/CTc/CDc - /B7c+Mvy/hDa/gza/gra/gja+rfw9Nbw9tLy9s7w+L7w/kLi/LXx+Lzw+Lru+Lbu+rDw/Krw+qju/KXv - +qbu/KDu/qLw/qDw/pnv/J7u/Jzu/n3q/mzo/nbo/IPq/Gnm/Fjk/E7i+mLk+H3m/oTs+nvn/Dze/Cjc - /Hjo+pjs+pTq+pDq/ovs/pLu/HTo+Jrs9rLs9MTs+JTq9qbq+prq/GTm+pzs+Jzq/lvm/GDk/lTk+p7s - +KLq+qDs+KLs+KTq/qrx/C7c+Kzs/rT0/FTi/lLk/lDk/kzk9r7s/Eji/rjz/Ebi/Ebg/Pz8/Dzg/vr+ - /sn2/D7g/DTe/jDe/Frk/tX4/Nr3/izg/ize/ire/ije+mTk/uL6+mjk8uzy/Gzm+r3x+mrk/Gzo+nDm - /G7m/Of4/HTm+snz9N7w+Ov2/vr8+nLk+nLm/MPz+pbs+eT1/iDc+NTz9N7y/Pr7/Pj8+vb69vX29PT0 - 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 - +1Hq2vWuediQ3SxckGaQI7StcF6CgDAEfiaixAFoQTrFm5FWzPZmFZauwcQa2ndkhBnOOAM4gXARfEDi - EaNARg5la9lzGAwWLDYIPnr7W7GsoQJmUAAGjDsSAYxitu4VCP6WhXyQ6oYWH40NwCyH+BpczJYcYFoz - bvXH3sCKbiQ4FYkH1tHf8ao5ywa5xoAD2wop8jIA+JhtaRCiZ87GEh4ZfnQ9TrzVCKjYIJXebCxhwVtG - E3aXPrZsfCiN6IOANrT+CHRV4jFbdnwq1BH46FHbm0ssz3ayoG71QTzQ23bIWig6tSw0RIbrEgM2tH8+ - 4KAtuw5DF6TZ6Fv0WBu9wnbMdrTGETZC4tFbbv6oHrMdRfCwTZHecflH7JjtNyzCboq4ObCeRRAszOu+ - elNk19uGkTUIvVX/0kXcCSE2o2EECwlvtUEHZ/NFMEzWH1ljG3wFN70RrhB4tKMd0WYhoCs5bpxHYwTA - JudLeo+d8onclrO6bTlSNMvZfMv8b3vta5xvbpEmLWXnQQkIACH5BAAFAAAALAAAAABkAGQAh/aM5v7+ - /v78/vLe8P5M5P4e3P4M2v6c8Pyk7vwk3PSw6vy48vyi8Pb09v5s6P4+4P4O2v6s8vx66Pz6/Pr4+vp6 - 5vpY4vpE4Pqu8PqY6vz6+vj4+Phy5Pho4viY6vz4/Pz4+vj2+Pj0+Pj09vaW6PbE8Pam6Pzc9/rr9/b2 - 9vTW8PLq8vTG7vS47Pjk9fTy9PTY8PLk8P5c5v404Pxa5P587P484v4c3PyK7PqG6vpw5viC5vqh7fig - 7PTp8/TQ7vTI7vS+7v4a3PyE6vqc7PqW7P5y6fx86Pic6via6vaq6vp+6Pp66P5O5Pq88Pp85vpm5PxK - 4vpY5Pbd8vw83vjS8v4s4PpO4P4u3v4q3v4o3vww3vwm3P4g3P4U3P4S3P4Q3PzN9P4Y2v4W2v4U2v4Q - 2vbU8PbQ8vq48PzE9PzB8v5D4vy88v5K5Py08Pq28Pqy8Pys8P6q8vyo8P6k8Pym8P6e8PqT6vp25viO - 6PqM6PqA5vpu5vpe5P6H7P6A6vqM6vqC6PyS7PyI7Px+6vyA6v6M7vxr5vxY4vxC4Pw23vws3v6T7v6a - 7v5j5/5a5vyU7PyW7Pqk7Pim7PTO7vqe7PqY7Pii6vyY7Pya7Pxh5vya7vyc7vxf5Pyg7vqq7vxc5P5Y - 5vqk7vqo7v5W5Pio7P5T5PxS4/i47viw7vqs7v6y8vqu7vxO4v689Pa07PbC7vi+7vw63v7A9Pz8/P7c - +P464P4w4P444P424PxG4vxM4PrS9Pp45vxY5P76/v76/Pzx+vTa8P7I9vTe8PyQ7PjA8Prd9vLy8vyN - 7PrF8vzn+fby9P5+6vja9P7s+v586vx05/7n+v7Q9vr6+vzi+Pr2+vfs9vT09PTs9Pq/8fTk8vjW8/zV - 9vbW8PzJ9PzA9Py+8vu18vq08Pyu8Pyq8P6o8v6g8P669P7D9v78/P7g+frX9fzz+/7M9vjG8Prj9vrM - 8/zs+vfx9vjf9P7q+v7U+Pq/8vyv8vjK8vr0+Pbt9P566vzQ9f5K4gAAAAj+AAMIHEiwoMGDCBMKpLah - wYsGG2gpnEixosWLGAVag0QqVJEGEjOKHElSJCQCKAlYmlCypcuXAieESkngEUuYOHNWpECTQBNqOoMK - LRii58+hSBHSokDt5sUGRimIpAU0KcYJCwj9WeC0IlSaTaRenDDHkaNPG6xaJCejrQwMGL+mDHuRViYb - eG3wqKo2oQAHbmU4WvY06kV6a/LaaJK2r19HgWUICklRLkq6FTdIUGxjjVjHCItFltHN4gvDFUtwtiGB - MmiDLiAHhtY14WmwnxU2kMF5zbHXCWkhGM2m4u25uZWWWg3JNfCC1wAHdqCP4vHLyQ/6IMC5SfXnSp3+ - jEbg3OB1n9kLUgO0Gg543RIiO4I38TzmhN4ecHZU+33BKqPh0B9B9qU30AY6rBaPfwpRIFpkYShUoEKx - rEbIgAwO5IImkRlhYAATIsRMKL25kKFCZI3mhm2oGTQBD6sRceJE80jnlgPyIBSiQdvowpkp382YkDGj - cVIeiC0SREEOqzkB2lITHDnRBn+M9pt5SQ5UhX6KHYKhQrQI8OVBE4jDjxGb0JcRN6MN0Z9l6BkUwiGc - PcBLRtRw48AtNhQxJkHhOCCoA0bEEQxG1BQxmjIGwXnfQK+sloOUB9Hiww63WKGpFZJURAsOgw7Kjxp/ - EuSDbG4ZYU1Bjia3jyn+nLWBwlOX2LDppjR8SJAhoYbqBzuUEkQLBqORw2qS1GSwWqeZwXLKrbfS0NhE - s/Ta6wGzVmaEfNkK5ChfAUyRmGI0rDoRNcLwAS20k4Cr0AJGWDuoEW6Yq9A7o0VCWVFgOUXNE6tpM5Gl - esyw7qYzZKArbJzIO2gN34xJgR+jsTMQT2BRls9qOrjLqhJrHLxpNGriyY0fDguazAmUOjPahQIJYApN - j4Q0gSO9OZPQBkAgIrKmpORTqkIhqMFPyg4w0G1BE1QyWoQCMUITJiGFs1oxR05ADB4/W/HAJwtfNE8c - 8TpsxALpRReZsQJ9EElHmNh7AGcELD3QPncYLPL+LTjY/dIELkSC9B9h1EbLApF9M9BSDW3wmSucwUXU - K210fYgzwbZEQRVDIC1INUxT3BYOAlA0gRF5eUjQBj9o0nUTxgztUghOlC2vEXMcOpAA6ciRjuxYRcLV - 4tsskanItvAQwmu0XIMA0vyEI3twSlZiy89YBOK3Y9TQU4zn00/0ges/a8JN5mpRkE8zKSsOEzk/64JG - 2KCJII7toa6CEw4H55KBCEISlgs8YS33vQQf6+pFyQJIEGo4A1SCYkTpYPIBR+AqHuHzzwSmsYBpTBAn - WCmCNjLIwBKa8IQFocUEVsjCFrrwhTBcIfpcMoEUvOCGOMyhDneYwxTwhRb+1nCDH2pAxCIa8YhIRKIf - VkFCi3xAAlkogBSnSMUqWtGKWpBAdSbghiR68YtJ1N9QJHDFMprxihJY4RDByMYv+mGGGZlAFM9IRzNm - gQITWGMb92hEPwyFGnOsoyCpmIWIdJGPiKyBGIMygSMM8pFSrIBUuKjHRHpxiR8MigigCEk6ZqECzHAK - VTZAylKa8pSoLCVT+jIBCqTylbA0JR5RSMta2tJFtUhDN5qYkAnEYQhuoN+MqsEAQxhTDplsiTWiYIBm - UiEI0zphMFZhzGoawh44QUAzt2mATsBAmK+hQBoYYc1qpgMnveDmNsewgxXAUS206EYjymlNbMJEEur+ - 5GYWTNCAEy2jDvS0ZgRyYo0m5JObF2BBNF/TjgUEtJqMCEMyZ5eBAhy0mRDowAA8lpQJfIOcDzUEG4IU - AAHUYhi1mChCBCCNWkhDgDoAw0UNIAQAZOOdJKHFCQ4QUkNEAAWu4eIBhroAi0zDFUidBtOqsIuZGmAL - CkhBUpYRgZ7agR39scdQt7qOxU2gKa6RBlLH+tKCUCAVWHDqFX6w0Je0gw09ZcQ30vMBOmx1qEqNyTRW - sQoPDqQWY0VqOj5wEBHoQQwz/QIHYsBLpoUBpA9dwPYEkoa7DrWrAplGBDYbgbwGoB6BRWotlAIPKEBg - pgUgwQtccoJz9HQO8Pj+kjzsYNlFBmAVnI2AbYcRWlfo7iDUyEcinKqIFrS1IvLAR08b0Q2OLm4Vlj0A - YQaS280S5Bm9nUawKDAJLTjVAiqY3gTGGVJGpGF5E9mpZWfhmupGwDX26O10FSKCPXhhpl7IwdBo8Y2e - ugGoppODZc9B0gC4tysfmEVoh1EqWhyjE07FREUmQIeHMiC2Ro0u1BZ34IIANrSYpQg1YiGLi66hwRUu - ZyP6Ac5gnMOyckgOLTpMEN6FdhaEtQgFiNCFfCbCuQUJgzUZAY4CU2QW0T2BQWZc3f6INbSlwcg18PAF - bsrIIsM4ACNWAWCMSCO64ugPk3M7IHf09rcWmYD+GWgwhgTkAZwtEQB072qH7Y2ZsxwNRm/dgdOFbCAF - cG6Jeu+ahiPdebPOjW9o55uRPpfkA+awLB2MLJBDR8C5H0hHaH9nS3dEtx/BcS+Q19Hb0dLSxZaNwIcs - DWQB8Daws2gHLV0RXdCFurpA/mx2UbiM6A7v1rnNdQCOGtpnmHACEbCsHXIEJlFPpB29HYZKGdSN6A4j - c6ymyIcDa2sh1VXS9gI2nk2n6cByWkhZtmyUB+ZsilSj1EKarWXxsd92T8TVvZX1idAR3QU2G9cWwW5o - 7cmgCdD2rmyAo6Wnp+ixzuJEFL7rOdDM7iZfJMHmPhEtpnFXz3qKxhYh9Vg+Q1zwbvCV4BZZeEaq4Q53 - dPuW4t5sY2FOEZDTPCjudfTNK4Jbztp25znRLGc9DnQQ7rWvMy86majB9KTDJCAAIfkEAAUAAAAsAAAA - AGQAZACH9obm/v7+/vz+8uDw/kbk/hbc/gba/pbw/Jbu/Bra8srs/KDw+Pj4/mbo/ibe/gja/rb0/Kru - /HLo+vr6+mTk+jze+q7w+pLq+vj6+Hzk+Gzk+GLi+Fjg+MDw9pro/Pf79szw8vLy9pbo/M/1+fb49/P2 - 9vL09O709OTy9Nrw9NLw9Kro/hTa+uj39PL08ury8tbu+Nz09vD29PD09Ory9ODw8uzy/lbm/hjc/Kju - /Kbu/KTu/KLu/KDu/J7u/Jzu/Jru/E7i/nbq/i3e/hDa/Ijs+oDo+lbi+prs+Iro+G7k+Jrq9uXz9MTu - 9Kjq8tzu+pDs+KDs9Lrs/m3o/IDq/Hbo+prq+JTq+nTo+Ijo+JLo+nDm+Ibm9Oby/lTk/kjk+mzk+s/y - 9tjy/Dze+lLi+kbg/DDe/Cje/CLc9Nzw/g7a/gza/gra+NHy/jbg9tLy9tLw/Lzy+rzw+Mjw/j3h+MLw - /kzi/LXw+rrw+rbw/qby+rLw/LLx/K/w/Kvw/qbw/Kbw/p7w/n7q/nLq+ojp+mDk+HLk+nzm+nbm+lTi - +krg+pzs/ILq/Hjo/obs/Hrq/H7o/IDo+nro/Ibq/o/t/Ijq/pzu/Gbl/ETh/Dbe/Cre+LLu9sLu9Mju - 9LLq+Kzs+Kbs9Lzs+Jbq9qDo/l7m/lDk/Jzs+p7s+qDs/Fvk+lzk+qTu+qbs/Fbk+qbu+qju/FLj+q7u - /k7k/kzk/krk+Lju/rrz/Cze+Lru/vz8/EHf/vr+/vr8/sX1/D7g9sTu/t/5/DPe9NDu+mTi+JDo/Nz3 - /Eri/Jbs/iTe/Ezg/iLc+K3u/h7c/I/s+J7q+Hbk/Of4/hze/hzc/vj8/hre/hrc/Izs/vb8+pTs/tf4 - +t/2+Or2+nzo+Kjs+n7o/kri/pru/PD6/jTg/Mf0+sby/kji/kbi/q/y/pju/tL29q7s/Pv8/Pj8/NX2 - +vb69/P39fP1+vH4+OH09Oz09ur0+tf09uLy+Nbz/MDy+r7w+Mry+MTw/Lny+rry+rjwAAAACP4AAwgc - SLCgwYMIEwqcwACeC3gM1imcSLGixYsYBboDQorUoncSM4ocSVIkkBsob/QIWbKly5frSKW80YDly5s4 - LWKYiXJCzp9AEZLgecNn0KMKJ0ywWbEEUaMY1zFFWnFCHEeO4kxV6JQn1Irr+kyZYoEB1Yt8GqhtcAdj - 15lfJ67rQaAuAR1xzx4UMGhtgyktLr5NmTehvFl2Cdwwq1ehgCl+GyDYenBwT4sMqCQmIItx44ToIjeo - ZxHeU4tzNhNgRPmzQG2iGRU2aNprRXiXNs/S5lrhOlei8d0+LXeZaiCteweI19fvoHgUa8OlSM/L5hvQ - lSvMJzpCdOIJJ/5cUJ1H+0QSjCIDniid8EQxXzZPmW1+4D3RlJK3v5yQQRXVYdQ30QQIiDaCQvsVpVAH - qk2SnIABMAGZX4JgkFCC9JVAymZ2BAahbxaIxs+F4BW0DiuqofIhRSUIEdkg4SCEIUJMlLIZKSSsSNE+ - ou0gY4kDMVCJanK4to5SDx7EQBGi8UYbkAK9EV9iEtCXkFRJErQOP1Q8soCHGN0XmTVTWaZgQSRIsNkX - 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 - 9t7y9O3y8ury8uDw8tzw8szu/hDa/lDk/ize/JLs/Ejg/mfn/jDg/g7a/HDo+nro+lrk+orq+ITo+HDk - +JTq9qzq9L7s/hXb/I7s/Izs/iLc/Izq/Ibq/Hzq+obq+oro9p7q/H7o/Hzo/Gjm+I7o9OXy/Gbm/GTm - +mzm+dHz+Hzm/k7k/kLi+lzi9Njw/Djg/DLe+k7g/iLe/Cre/Crc+Mnw9Mru/L3y+rrx/jng98Du/j7g - /Kfw+LTu+qru+LLu+LDu+qju+qbu/KDu+qTu/pXu/J7u/Jzu/nDq+o7q/Grm/Ijq/ITq/ILo/Gzo/G7m - /G7o/nbp/oDs+oPo+mbk+Ibo+nTm+l7i/IDq/IDo+nzo/ojs/pDs+KLs+Jbo9rbs+J7q+Jjq+JTo+nLm - +pDs+pbq+prs+mTk+p7s/FPj/EDg/Dze/DDe/lbl/kzk+qDs+qLs/rTy+Kjs+qTs+Krs/FHj+LLs/E7i - /kri/Pz8/kji/ub6/kbi/kTk/kTi/D7g/Drg/DTg/r70/ire/PH5/ije/ibe/sD0/iTe/sL0/sT2+mjk - /ETg/sb0/sb2/sj2/Ebg/sz2/Nr49PT0+N70/tT29ur0/Hbo/iDc+Jrq+sHy/LLx/vr8/Mn0/rDz/Pr7 - +vb5/Nb2+fL59ub08/Dz9Ojy+df09Nzw+Mvy9NLu/ML0+r3y+MLw/Kzw+Lbu+q3u/KLu/qDw/rn0/vz8 - /PX8/N749vL2+eL2/tb49u31+sfy/Ljy/Mz1+Lru+qzw/KTu/nDo/HLo/hzc9tHw/N7399Ty/OX4/t74 - 9OLy+MTw/K7w+rLw/LDw+rTw+rbw/q7y+PD3+Nry/jzg/pzu/Fvk/FbkAAAACP4AAwgcSLCgwYMIEwpE - lqEBrwYZRCmcSLGixYsYBSZjQ4PGhRASM4ocSVKkNgcoHaBDVrKly5cCRdFI6eAOS5g4c1akQBPlTZ1A - gw4M0dPBT6FIDVJAFvJig6IURIo6mrSiKFhBDgxrWvFpz6gXRRFTp4dehqoWycVZG4ccRq80wVptA6Mu - jDNy0SIUYIRtHD0onEK9CM6SXRgO8uo1KMDvWjZcFcJNqTghhSKHYViqvJjgG8dxdlmcjJLzwWiZYdCJ - 3JngO9CEqCIknbjrncyWwLVOKMoa6GldB08UhSY15N0J8/X1a2QZRdqmCf5ykNlBK+QKh4Ge91y4ZUep - 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 - EwpQFq82qDbLd0KLykZB5DP3jwo2YAMbVKBrOdcJBXhhggHUdOO7CQgAIfkEAAUAAAAsAAAAAGQAZACH - +Gzk/v7+/vz+8PDw/krk/hrc/gra/prw/Kbu/Izs/Cze7u7u/MD0/KTw9vb2/mro/ire/hba/qry/KTu - /HLo/Pr8+Pj4+nDm+lTi+kzg+pDq/Pr6+Hbk+G7k+Lbw+JTq/Pj8/Pj6+Pb4+PT49sbw9p7q/N34+sfy - 9NTw9L7s9LDq9vD19PT09Nrw9PL09PD0/lrm/hzc/KDu/J7u/Jru/Jbu/JTu/Fjk/m/o/jDf/hbc/Irq - +oLo+mjm+Iro+pbs+H7m+Jzs9qjq9Mju/Jjs/JLs/I7s+pzq9+Hz/IDq/Hrp+pLq+Jrq9qLq/Hbo/HTo - +njm/hja+nLm/lrk/kzk/ELi+lri+lji/D7g/Djg/DLe/NP299by9tby/jri/Mz09tDy+Mby9s7w9srw - /Mb0+Mbw/ML0+7fx/j/g+Lrw/kbi+6/w/Krw/Kjw/qjw/qHw/Kbw/nrq+orq+m7k+JDp+Ibm+nro+mTk - +qbu/ITq/H7o+oXp/oDr/oru+nrm/Ibq/pHt/Gzm/Ezi/ETg/Drg/DTg/p7u+mzm+pvs9rru9rDq9M7u - +Kzs/mHm/lbk/GPl+LDu/F3k+qjs/lTk+qju+q7u/rHy/lLk/lDk/rr0/k7k+rLu+LTu/Efh+Lbu+L7u - /ELg9r7u/r/0/Pz89sDu/uL5+qXt+vD4/Izq/ibe/E7i+tz2/iLe/iLc/sb2/FDi/iDc/FLk/h7e/h7c - /FTk/O759N7w/vr8/sr2+qDs9N7y9OLy+tDy/sz2+KLs9Oby/s72/Ob5/jrg/jjg/kTi/pzu/tD2+vj6 - /OL4+sry9/D3+OH2/Nj3+Nj0/M/2+Mry/Mj0/MT0/Lzy+MDw+rLw+qzw+qrw/qbw/rj0/sP1/vz8/ur6 - +vL6+uT2/sj2/PT6+tf09Oj0/Or6/tD4/LLw/LTw+rbw/LDy/njq/jbg+J7q9ur0+rzx/ors/pru+uH2 - +On2+r7y+ub39Ory/tj4+sz0+M7y/LTy+sLy9O7y+8Py8vLy/MTy9O30/nTqAAAACP4AAwgcSLCgwYMI - EwqsYMGBQwujFEqcSLGixYsCQ8Dx5w+BiIgYQ4ocGfLag5MP2FQgybKly4U4UD7wt/KlzZsUj8k8WROn - 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 - B9nADXSbggPCEbPjxbBtUX0QcMiisIP4Mo3IfFUAbmXTCKmAHXRbAnvepAJWpXOKWQRshMADAoVVwDrI - B+i8UkwhpjYArRGSC90CwdguuWxeGQ2cZiNEBFUorCy4YCJgYPZK0TE3Qj6h20e82ScCwKwxmPJiZz1i - 0uAmySiSbVP0xtvIFVkFLMaag4CT5L82DfDBO6trdR1YqEu4t09GUQpc4GKgoOl3TkeYIAhBqGPKlWQ2 - wi0iSAdo3IDqpXjKjRJzwIJ65jexgA46GwFE4zwkFphDZynw8p+Hpx8A6OoFRmB0n2yABQOIOgtorJqA - AAAh+QQABQAAACwAAAAAZABkAIf2guT+/v7+/P7u7u7+ROL+JN7+BNr+lO78kuz8RuL8GNryyOz8lu74 - +Pj+aOb+NOD+Btr+pPD8iOr6dub6+vr6TOD6Qt76ou74lur6+Pr4fOb4ZuL4sO74lOj89vr2jOby8vL2 - luj+FNz47/f27vX08PT07vT01vD00PD0sOr0puj08vTy0O78xvTw8PD06PLy5PDy2u7+VOb+MuD8cuj8 - QOD+dOr+OuD+Etr8kOz6kOr6XuT4gub4qOz4cOT4lOr0tOz+Gtz8juz4puz4nur+ZOj8jur8jOr6gOj5 - 1vT20vL2our+Itz8XOT6eOb+SuL8VOL8TuL8SuL8SOL6VOL6UuL6TuL+Jt78Nt703PDy4PD8Mt78Ktz8 - JNz+Dtr+DNr+Ctr+CNr02PD6uvD2xu/+QuL8rvD2v+74tO76svD6rO78qvD6qu78p+/6pu78nO7+ovD+ - oPD+mO/8mu78mO7+e+r+a+j+cur6auT4i+j6VuL8ger6luz6huj8ZOX6gOb8VuT8UOL+hOz8eOj+iuz6 - iOr+ku78dOj6dOb8ON78Lt76jOr6jur2rOz0uur4ouz6ZuT6mOr+Xeb+WOT6mOz6muz6nOz8YOX6nuz8 - Xub6oOz6ouz+qfH+VOT+tPT2tOz+TuT8RuD+TOT+tvP8/Pz++vz0wOz+yPb8Ot78POD6XOL+MN78WOT+ - 0fb+Lt78WuT82vf+LOD+LN7+Kt7+KN7+2vj6aub8aOb6buT6cOb6k+r+5Pr8aub6cuT8bub32/P84vj4 - 5PX6xvL8t/H+5vr80vb+INz24/L++Pz8+vv8+Pz58vn29fb09PT8yvT07PTy6vD53fX31vP04PD6v/L4 - zPD8svD4wvD4uO76tPD6rPD8rPD8qPD8nu7+nvD+sPL+wPT+/Pz+zPb+1fj83/j+3/r42vT86Pr55/b6 - y/P8v/L81/b25/T4uu78oO78ovD+uvP85vj+xPX87vny7vL03vL20PD8sfL6tvD6sPD8pPD4yfL6uPD8 - 9vz88vwAAAAI/gADCBxIsKDBgwgTCkzWoAGzhqQUSpxIsaLFiwIzbKtTx0yDiBhDihwZkpONkzbMgCTJ - sqVLAXVQ2qiz0qXNmxUzyDyZDKfPnwgb7LTRE6hRhcmS1aQodGdRjE+PWiTV4o03Z0slNpUZdSIpeHsE - jaEg1SI4QWgFqbu4FWVXiWaKyC2S5m3Zg0LSCiI0z2JbnhaDOZhb5A7ZuxL1ot3md6jdgxQQEC5i5zDi - hJwUCyJXkZnjikomF2FwWSI6zXIeF/TslCKzOpMdoCstcZhmZ64/Txwjeg1ticvy6hXiYSJrrhNJ3Jl8 - p/hvhek0wzOuO2GyC6LXPlfYYI7mcxKP/ruVGE30HtXbBUbT3CYrQfGAEzY4InpdeonJ1miGpRA+Ueii - zeHefQSdptgBqvmXIGyEOQAegQqRQo1mLSSkIEKkZCNaPhBOtIwhihHiD0IXHsSOHZPZkEGHE0Wn2D0k - VkcQBW+IVuFlpCQlUgNxaBbPQSUWZI5oEgwYoQACXCRAC97IMUpfGAGjGRxZ/fVfQQ1IIBpnFyUTjQQy - FLFGkhS1csCZB8gBjnMVJXOPZsUYZOVbwojWzUWkkFCJDHzyOUxFnKCJpjfFGGnQCIQoZogyWMoYwAg2 - TGYHlK6hUUSffdZhmUKBCoomJ8BYNKFi2g00J0HJqCHan0xVQwOm/phqWqannj7J1AEhUhrAqQMFgyJh - dayI3zFgwoopPugRJI4ctKIpRwtsJpSEZmYQZOVKyfAhWjoSkTLCBZMY2+ckaiRb0DzDNItmHOsYSkEE - moW60FArJSGaEOgxw5u4fQoxW0ikANOGumeu8SBCwWj2BkgwyVTHQMnsIZq8kCkxCL982lAoSxQU4w3B - B8Az4kGk2KYYfwJ9I1O1AjkjWhsYspMDxmGOddMy6jCrrhzOMGrQMonqdWMAypjBkRnCBvANc8scREIP - ktDszgg/kYLOPSDDAUtWLSg2zkCkUNBQA0+dMhk1cl5jB80SBGMoS8nAEgHIZhwsECnwohUB/pkKCSDE - XDlERcEvddBsRzpv25SBMzqrq060AowDzjiJL9TCNi08lScfNEuSRtKX4QyyN61UblEpaUSNMSW60ma1 - GSB/Y/pEpRSO8R7RzG4UBUnAQXAtN6mDsQPCmFtaAy00Lqg8N8UhriRqRMuiQCPAQyvwNoFjLAJ2T09Q - wJ0e8A3fLSmzR5+DtOt93+SIU85PybSwBlbr12///S6Rov/+/Pfv//+XYQgzBkjAAhrwgAUkG6pOwYkI - OPCBEIygBCXIiVOQzyfIQAAoCMDBDnrwgyAEISgQ4JxTTPCEKJzgKY6CgBC68IUhRIBSGpjCGqKQE0Yh - xQZhyMMXgoIs/jS0oRAhiEOgJGOHPUyiB0HRgACYcIhQjIA4cthCJVqRAAggiwDEEcQonpAT4rggTjKg - wSvycIQjiEoyKMDGNrrxjXBso1IQs8ax2fGOeMzj2ChgPPz58Y/1I0YtdvGTUsCDDuroY4fmIQ9PONKC - N1GGAx5AySbso4l+9IA4HMlJTxDSJtagpCgf4IR6bGp6pajFKDrJya/ZRAijFOUNMGCC9e0CHqzs5Cdd - koZYjvIJaGDGIhuZS06u8CYZ4IUvR+kHe2DyOf1oRTE5CQ8uQawlGWhDGZYpSiQcQ5H5U+U0HVmMaC3j - DbnohvQSkgEzSMAMpSDICCTATUrWgAgk/rgMMcAxTk+conUNqIEBBkqAeEokA00ogEIzATpSJOEW9XxA - IM7wTKD44xT9BEc8lkIKRgz0o4wJACka0AwQNKMBlkmDQld6gaVQgBpRiKgtTlBRmyBDmuMcBSzsEgwR - fHSgErjbCBDBBCYMQlikEMJKFfoAdhwkA5jYZj37wA7dJUQA5MDlOFuxzoVA4qcDTcNCBiGCsopgEAMZ - w1IVqoGaDgQdSIgoAXqwgpacg5/jlEfrClINCIA1EYdJBhPMKgImFCUDUVjrFVBgnWJcIqKAIINbKeKP - TY4THMBAT0DBagBTDIQChC3rM/ex1gI0QZgJoQAanhDRQ/xidgIQ/uc0dWquHnBWFc9sQGhFgNoAJMMX - pQXC25bBhxvU8wZzECNCatFPcXT1UAUAqxdiYK3dNoMgwYjFWstQAooEYxARhRlFiJlLvVaOFDzgLAA2 - pdvQguB7OigtBoyXjHSwgpuXqBx5qUmO2UXDC2DtAjSwtNv3EmQZqVjrKqZhkVQRwJeXMB45OjmKcfis - IhTYAWdVsJT2EtbABOFAaScw2YSMwAjGFaUaLFILeIxCHCO7yDU4a4HrEti9BqEAIEpbDYyQwhyHuEEC - MHHKozQAC2CFwAIO4mGzgpggSigtFHrb4IYU+SiY4OwGJtvksj4ZbBMobSOs+pwRBAGsItBC/lAKjBB0 - rGKtqXjGH0nhBM5+4Mq7YjPJIlHaPOCZReb4Ali5QA/56PkgyyDAWmWRBfxRAAqcTQF3Dn0Qta4VDyWG - EDY4WwEqy4nSOd5Bac9gvwYoAqxgYOykcayQJFxhrZ2u3w8464NMC6TLIviyQUixh9IugcxHQYdPf6oA - GEwE17o+1APWigWnsigZuOBsCMyFbIpcoLQ8AOdRlBAGsG6hrscGdVCesFZaSKNDyYgCZx1RkWpT5Bql - 5QWwccKMbv9UD7auLqsnkoxbrDUW87YJKZgx7IF6QQx+EXdCojGLpd4g4PlrQHo/2ofZuZsipIjvSikB - cZsw4weoSAUdS8B5cYpQgAhQgMIj/vybkWo7z/vGMEQAyZTdeprmPsF1vnFOkgYYg7BMYDnPe44Iwg6i - 40OHjAmIyoQJNC3pP6EAM0AwAJPu/C4BAQAh+QQABQAAACwAAAAAZABkAIf2hub+/v7+/P7y4PD+TOT+ - HNz+FNz+DNr+nPD8INzy0O78xvT8pPD29Pb+bOj+Htz+Gtz+Dtr+rPL8eOj8+vz6+Pr6cub6SOD6sPD4 - quz8+vr4+Pj4eOb4bOT4YuL4mOr8+Pz8+Pr49vj49Pj2xvD2muj89vz88fr61/b29vb02PDw8PD0vOz0 - tOz25fT09PT04PDy7PLy5vD08vT08PT08PL+XOb+Ftz8WOT+fOz+LOD8pO78ou78nu78nO78mO78lu78 - jOz6guj6YuT6oO74huj4duT2kOb4ouz07vT2ruz0wu7y2u782/b8gur6muz6luz4muz+cun8e+r4pur4 - mur2qur8fOj8euj6euj6eOb6dub+UuT8ROL6WOT6UuD8NuD41PL8Mt7+NN7+Lt7+Kt7+KN7+JN7+MeD8 - Ktz+Etz+ENz03PL+Ftr8z/X6vPD2z/D+POL8yvT8yPT4vvD+Q+L+SuT7tfD6tPD8sPD6svD8rvD8qvD+ - qvL+pPD6kOr6bOb4juj6fOj6eub6XOL6VOL+hOz+gur6h+j8lO76hOj8hez8fuj+jO78fur8iOz8aeb8 - TuL8PuD8Pt78LNz8iuz+k+7+mu7+Y+f+VuT8luz8YuX6pu74puz2tuz6nOz6mOz8mOz6ru76oOz8XOT6 - qO72xO7+VOT6qu74su7+svL6sO7+vPT8SeL4tu78RuL0yuz4vO7+v/X4wO78/Pz+2Pj8OuD8QN78QuD8 - ROD6cOT8UuT6cOb8VOL69Pj++vz++Pz67Pf+5vr8k+z6w/H6fub+y/b8juz6fuj64vb05PL85Pj6rO73 - 8PX+fur6zPL+6vry8vL6+vr89Pz63/b36vb83fj42fT81fb7vvL4zPL8zPT4xPD8tPL4uPD8svD8rPD+ - qPL+uPT+xfb+/Pz+3vj69vr68Pn+6Pr7xfP+0ff65vf85/n48ff60fT+9vz07PT4xvD8sPL6o+z+eur2 - 2vH+OOD+SuL+jOz8cOf33vT8v/P40PL7t/L6x/QAAAAI/gADCBxIsKDBgwgTCqRQYUOKDRVoKZxIsaLF - ixgFVshjyFA3ERIzihxJUqSqHChzdAtZsqXLlwIMpcxhiOXLmzgtUpiJkkLOn0ARVuCZw2fQowopULBZ - UQRRoxmZIq1IYRsDP26kKnTKE2pFAawaNdpXYerFBWLFLsDIdaZXit0cyHXQ761ZhJbSNrJ04mLblHYT - rpMy1wE9aXcp5tWryu9Ti9J+FHYgBXFihar0il1m8W9Pi2EmO/BxeWIwzY38BDbouWjTRZOlsCs9MRtq - N00fU8wmOg/tib6AaAZigmLr1QSb0ZtMz9fviW5QyzKuOyEFPqLTPZ9YwQ9qaBOP/k+kRrhwJeTbBVJD - LUHrQPEJK1gS3ST9RAp5UNdPCB/hM9E9uGffQMEslhYCyPXHmhOxBTPgRLTsg5ox/FVXEC3eiNbNgxT5 - colmlkyDkIIEVVPeXM4Ux+FE2KDGyogWDiQNA6LJQdtSI0njnWazsRajQE2Idgx6CNEigAAYCeCGHwiw - 0ldGyaDWh1QkBlBBEKJRkxEF1ASBiQN7IFkRNgiUWeYCKurUDWoo+NiVQfGIxgBGtFRDBCY25GlDPxap - YqaZfmBDJEEFanYJCAWR2M4UsZ1zUQMZ6qmnE4MG4OefZkrAmUVoabYWQQrSoodo2VhUAT8TSCqpE5ax - iCmm/k42hQCIT2pkoYmTHVLWfS4co6qqb1QagBuvYmoMohOhgNqGAw3FE0sU9CCaOxA2wwmev+aJCQbC - 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 - /Gjm/Gbm+mjk+Hzm/lbk/kjk+mLi+lzi+krg9OLy/Dbe+kjg/Cre/CLc9tTw/CDc+sXx/hLa/hDa/g7a - /gza/gra+Mzy/jbg/MHz9s7w+Mbw/j7h/kzi+MLw/Lby+rzw+L7w+rTw/qby/LPw+rLw/K7w/Krw/qbw - /p3w/KTw/KPu/Ibq/HTo/Gzm/J3t/Jjt+qft+pns+oXp/JXt/n7q/nTo/JTs+n7m+I3p+Irn+nLm+lDi - /IPq/obs/IDq+nzo/Hzq+ojo/ozt/pru/HLo+mzk+K7u9rbs9Mzu+Jzq9qTo+pzs/l/m/FLk/lTk/Dze - /DLe/Cje+J7s+KDs+qju+qru+qzu/lDk+q7u/E3i/k7k+Lju+rLu/kzk/Eri/krk+rbu/rjz9rru+qXt - +Lru/vr89NLu+L7u+qLs+qDu+lLi9sbu/Crc/Dje/Gzo+pLr/DDe/Hbo/DTe/Hbq/Hjo/uT6/Hro/uL6 - /D7g/PL6/tj4/iTe/ELg/iDc9rLs/Ebg/h7c/Irr/hze/hzc/Ejg/Izs/hre+tf1/Nv3+Krs/rr0/I7s - /r70/JDs+Kbs/sj1+O73/jTg+Nby/Mz0/kji/kbi/q7x+n7o/pju/Fzk/Ob4+oDo/Pr7/Nb2+fT59vb2 - 9vP2+uf39u30+OT09tny+s70+NHy/MT0+Mjy/Lvy+r3y+rfw/LLyAAAACP4AAwgcSLCgwYMIEwp0x6Ah - gwkKI0qcSLGixYEe4FCi1A/DxY8gQ4ac9ajko33uRKpcyXIhJZOPvqVsSbPmxAkwS860ybPnQAw5H+30 - SdSgu6EUGQRFSpFp0YgCPqBD98FpQqU5rSIUoObGATVanxYsd6DsgXIWscIMa3BWjrc5UIqVqMfsgUrL - Kqo1yZbgPENwczyCOFdhJbsHZuldSnHCrsA5DPUVOwvxAXZJGU/MBjnHgsIRxVnGM3mvzokMrnXGDFph - PssfUGuOKKZzndYR4dW1q8eDRNNCJYoDHPhRXtwKP1jO93s2QncWOqNFrnACHsvHrzo/WK/zjcm4Yf5Y - hhMReNgJlTproy7RXWXEMBSaV3iv8x7wyMUdtktH63yESkFmiDjsURQObNpl9Rw/ne1TIEUYLICYHtkV - 9J9Bw0H2iEcPbmaZGgBut1AEnd3T2lEgTYCOZaxZKGIAMHSmDn4ECSCARVGhQ4caFVI0j2X+MHXhQBMU - 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 - 7vai6vaY6PTI7vS+7Pry+Pfi9PT09PLy8vTy9P4Q2vrX9fTw9PTo8v5Q5P4u4PyS7PyO7PyM7PyK7PyG - 7PxM4v5l5v444P4O2vxy6Pp+6PpY4vig7Ph65vhq5PiW6vaw6vae6v4Y2v4i3PyG6vyA6vx+6vqM6vqG - 6vam6vPf8P4g3Pxq5vqI6PiQ6Pxo5vpo5PbC7v5O5P5C4vh25Pw84PpO4vwy3vpQ4PfM8f4i3vww3vwq - 3vTU8P4c3P4a3P4Y3P4W3P4U3P4S3PrA8fy78vLO7v484Piy7vqx8Pa+7vyn7/qs7vqm7vyi7vqk7v6V - 7vye7vyc7v5w6vyW7vqQ7Pxt5vyI7PyJ6vxu6Pxw5v526f6A7PqD6PiC5viA5vpw5PpW4vqA6P6G6/6O - 7Pio7PiY6va27PqO6vqQ6vqS6vqY7Pxg5Pw+4Pw44Pia6vqc7Ppg5Pqe7P5W5f5M5Pqg7P608/xX5Piu - 7PxP4v5K5P5K4vz8/P76/v7E9P5G4v5E5P5E4v7c+P4s3vw64P4q3vzy+v4o3v4m3vxA4PTm8v4k3vxC - 4PxE4Pil7PxG4v72/PxK4vii7Pzb9vrn9vfD7vx66PbY8vrN8/p45v7O9vbw9fzF9Pyy8P6w8vz6+/r3 - +fzW9/r0+vjn9vrg9vTt8/Lq8PfQ8vTa8PrE8vy+8/TO7vi67vq48Pyr8Pqu7vqo7vym7v6g8Pyg7vya - 7v649P78/P7J9v7k+vz1/P76/Pzj+Prt+PjA8Pfb8/rV9P7W+Pfy9/zL9fy38vqs8Pyi8P6u8v5w6Px2 - 6Pzf+P6x8/zo+fTk8vTc8vq68Pyu8PjG8Pq+8PbK8Pq88vbo9Prb9fx06Pq28P6e7v638/74/Pbs9vrj - 9/699AAAAAj+AAMIHEiwoMGDCBMK7ERBg0MKCiNKnEixosWBHszEibMM4sWPIEOC9Heg5IFlnUSqXMly - YRyTB+KkbEmz5kRgMEvOtMmz58BgOQ/s9EnUYKehFIHmREqRadGIAoRduiTMaUKlMK0iFNCrWrVeHp9K - 3DWt7LQPFrGa1HpwWZ+3fVCKlRjB7LQIpyqq1VmxFty3M4DNjWi3rD+9QdkSBMbmbx9Cggcn9Fd4GjuK - GhJTZOG4TwTJCrtVXqdYYOalEzXE6XwZdMJelcOl1ixRWWczrhUOq2s3gjaJp7NKNEHD8YxhuRUKq4wN - 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 - N4yoAT3QxAtLMMAwjVf2Ak1oAjTCXRSGRPnZEnGav99oEECfm+YqAbSOcQ4SCuCgsGhgOc895YfConXo - PAEGL4KKhiIUFek1AUYDUDAAL+78KQEBACH5BAAFAAAALAAAAABkAGQAh/aI5v7+/v78/vLq8v5K5P4a - 3P4K2v6a8Pyo7vyO7Pwu3vTC7vy+9Pyk8Pb29v5q6P4q3v4W2v6q8vym7vx26Pz6/Pr4+fpu5vpO4Pqs - 8PqU6vz6+vj1+Ph45vhs5Pia6vz4/Pz4+vaS6Pai6vac6vzl+Prw+PTk8vTW8PTK7vfk9PT09PTa8PTy - 9P5a5v4c3Pyi7vyg7vye7vyc7vya7vyY7vyW7vyS7vxa5P5w6P4v4P4Y3PyG7PqI6Ppm5Pqg7viG6Phw - 5Piq7Paq6vTQ7vTu8vyW7PyU7PyS7PyQ7PqY7PqW7Pic6vx+6vqW6vam6vvB8vx66Px46Pp+5vTo8v5W - 5v4Y2vpw5vh+5vTm9P5M5PxK4vpY4vxE4PpW4vw04PzS9vwy3vbT8v4W3P464vzK9PnA8PzE9PzC9P4/ - 4fzA9Py28f5G4vqy8Pyq8Pqw8P6q8P6h8Pyo8Pym8PqM6vpo5PiQ6vqE6Pp05vpg5P566vqI6vqo7vyC - 6vyA6P6A6/6K7vp+6PyE6vyG6vyI6vxu5/xW4vxG4Pw44P6Q7f6c7v5i5vxi5fi47vbA7va07Pqe7Pig - 7P5Y5Pxc5Pik7P5W5P5U5PxT4vqq7v6x8v5S5Pqs7v5Q5P669Pqw7v5O5PxM4vqy7vi87vxE4v699Pz8 - /P7X+Pw64PbE7vqk7vxI4vpu5Pa87vrY9f4m3viU6P4k3v76/v7p+v4i3P4g3Pi07vp45v4e3v4e3Piy - 7vxY5PTe8PyN6/7F9fTg8Pz2+vfv9frK8vTk8Pzs+vri9v76/P464Pbe8v7g+f72/P7K9vLy8vr6+vj4 - +Pzn+vr2+vfp9vrE8vza9/jU9PzO9vjI8PzG9Pu88vq28Pys8P6o8P629P7B9P78/P7b+Prd9v7s+v7H - 9vz2/Pfx9/rS9Pzx+vrm9/jb9P7k+v74/P7Q9vjM8Pq68Pyu8PjM8vyw8Pyy8v424PzW9vjE8P5E4v6K - 7P6a7vze9/bK8PrG8vq+8vy+8vrA8vy89Pr2+AAAAAj+AAMIHEiwoMGDCBMKLMXMQjMLFRRKnEixosWL - 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 - efNU41wQa1j2DlPmUDRW64MlUznM0ImCZddxvGdwIsgoqI6fE/INLWT1AZbuTipW24NI9xfT6qmBZUMh - VJ6YgAxNJQMVdjPqhJigCmZGc4NKcYfVPgG+vU5IGyyrBDv3pBV5luclUr2lZPfnAVn9BF/rU4oHrBYV - k7E2QoJh2T4IyALRhsAVMitqC7etD1nVRK1ZUopm4LqhOjhGXfxLEWI816IumPdKsAfehiohtPymSCpu - WkgBNYMSOHABDZGS8IlU4A05yIGOOcQQYSo6thipgAUzXO3GolW85DN5rWRJjnKVMIMedGWDs1u+PDrQ - lRc030kFxhFTNvBirDm3nQOQGEUU/yUgACH5BAAFAAAALAAAAABkAGQAh/Z+5P7+/v78/vLY7v5E4v4i - 3P4E2v6U7vyU7vww3Pwe3PLO7PyY7vj4+P5m5v404P4G2v6k8Px26PqM6vr6+vo+3vqk7viW6vr4+vr4 - +Ph+5vhu4vhk4viu7vaU6Pz4/PzR9vaK5vbA7vLy8v4U3Pjv9/b29vT09PTm8vTM7vS46vSk6PbY8vLq - 8PLk8PLe8PTy9PTu9PTW8PLu8v5U5v4y4Pxc5Pp65v506v464f4S2vyO7PqU6/pW4vig7PiE5vh25va2 - 7P4X3PS+7PSu6vyB6vbO8P5k6Px66vqQ6vna9Pii6via6viW6Px46P4g3Ppu5v5o5v5I4vxI4vpw5Pw2 - 4PpS4v4k3vpE4P4i3vw43vwy3vwk3Pwi3P4Q2v4O2v4M2v4K2v4I2vq28PfO8fTS7vy+8vbG7v5C4vjC - 8PbE7vi87vi27vyu8Pqw7/qr7vyp8Pye7vqo7vqm7v6i8P6g8P6a7vyc7vya7v576v5r6P5y6vpe4viK - 6PpU4vpK4PqH6fpy5Pwo3PyY7PyI6vx86vx66P6E7PyA6Pp+6Pp86Pp85v6L7P6S7vxu5vxO4vxC4vw6 - 3vw23vyK7PyM6vqa7PqS6vyM7Pik7PTG7Pau6vxk5f5b5v5a5Pqc7Pxf5fqe7Pqg7Pqi7Ppa4v6p8f5Y - 5Pim7P609Pio7Piq7P5O5P5M5P5M4vxM4v629Pz8/P76/v7G9vw74P7Q9v4r3vpo5PxE4P7c+Pzu+fiQ - 6vxQ4vxS4vxU5PrK8vxW4vyU7PTs8v7m+vxW5Pxa5Pbu9fyS7PTq8vzZ9/Tc8vp05vp25vjj9P76/PTg - 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 - hBoOUCsnnlERbGqTlYewUSgwSYsfGCN20lDEX4NgEAIX1sACycZif+Hcg1DDHvXsZDZwyIMc0EIPLCjx - Qdrx102AOHntvWYhFquNj2QnCjgYQ+xeQeQ6SsMGfy3DQTxc1hsHQAmLRYJ5tUWBhzjGDX/9AV+ZTFYn - v+IXi/WGIEsgha+iAQUI4TL+AZwcAFxwQq0OoKr5ePDXJZxRzWwOgBsWawGaygcb//3pLmBwvRznRw9q - 7cQyzJfYv4pAIXhWSC8WO4Epd6cbf4XClCOdEGkwVK2fHV8DShFdFkSE0wnBxmJxkOfoyOGvlT61oRHy - ijssVsjIwwUBvkoAd0gE1Xc5ApybqqBXTOCvqdAQsBMyhsWC4ps0UUKgQzqMVg9k2QjBwB4WizD5vMIR - fz0DRbCNkHYsdhL/acC0DSoBS1971kKZhIKhfaNdhzQH1WAXvBUSjzer9Aj0XkkD0BvSoOq7wBZ5r0o1 - mW45fGITbxAfuTsdDiQgYQx+TlwdJ97phggyNLt198ctQ0KBkI98J9IgbFlVkfGTrygJhf2Fy3UijXcQ - VRWW0OfMVRLDE4zgBCn6T0AAACH5BAAFAAAALAAAAABkAGQAh/aO5v7+/v78/vLe8P5M5P4c3P4a3P4M - 2v6c8Pwm3PLY7vzE9Pyi8Pb29v5s6P4e3P4O2v6x8vxy5vz6/Pr4+vpu5vpG4Pqs8PqY7Pz6+vj4+PiE - 5vh45vhq4via6vz4/Pz4+vj2+PbC8PaY6Pz2/Pzy+vrh9vTU8PLq8vTA7vSw6vfq9fTw9PTW8PLk8P5c - 5vxW5P587P4s4Pyo7vyk7vyi7vyg7vye7vya7vyI7PqC6Ppc5Pqk7vqe7PiE6Phy5Pik7PTs8vao7PTI - 7vS67Pza9/qa7P5y6fx86vx76fie6vao6vx26Pxy6Pp66Pp85vp05vjV8/Tm8v5Q5PxC4Ppa4vpQ4Pw6 - 4PbO8Pww3v403v4u3v4q3v4o3v4y4Pwo3Pu98f4g3P4U3P4S3P4Y2v4W2v4Q2vzO9fi/8P484vzL9PbK - 8PzG9P5B4v5K4vq68Pq08Pyy8Pqy8Pyv8P6o8fyq8Pym8P6g8Pyk8PqS6/pm5PiL6Pp86Ppk4vpY4v6G - 7P6C6vqK6fyW7fx+6vyC6Px66Px06PyA6vyC6v6M7vyG6vyI6vxr5vxK4vxA4Pw43vws3P6S7f6a7v5i - 5v5W5PyY7Pxi5fqq7vqg7Pim7Pay7PyY7vq07vpg5Pxb5Pqk7Pi67viu7P5U5Pqs7vqw7v689PbG7vi+ - 7vxK4Pa67vxE4vTQ7v7A9Pw+4Pz8/P7V+Pw83vps5viS6PxM4v76/v7l+vxO4vyU7fvE8vxQ4vyR7PxU - 4vTa8vyO7PxU5Pqy7vTe8vr0+PqA6PrM9P7K9v74/PTi8vrr+Pzn+P6A6vjc9P5+6vbS8PjI8PLy8vqo - 7vrQ9P649Pr6+vz0/Prl9/jx+PT09PXq9Pzg+Pja9Pbk9PzA8/zU9vjC8PzM9vzI9Pu28vq48Pyw8v6s - 8vys8Pyo8P6k8P7D9v78/P7f+f7p+vrJ8/r2+vrO9P7R9v76/Prw+Pzr+vji9Pbg9PjM8vrX9fi68Pbw - 9fqm7v566v444P5I4v7b+Pj0+Pf09gAAAAj+AAMIHEiwoMGDCBMOnCCt4QSFECNKnEix4sIzESKceWix - o8ePHs+MGznOHciTKFMSjEByXASVMGNSnNBypMybOA3SrJmzJ86dLV35HEqwFjFi2CwCJSm04quM3DgS - hZiulNVSSWfWHNd0ojcEYBGc6ToV4bmrpViR0FqTLER4d8IioOO2bEFWaEuZnLh0ZF2ErkrJRXDnr12B - r/KWijeRwlbDBrENRlDqcMISiokJkOi4Ledxg++UsJywiOJanz1H5DaZGOmEH86iPSc1YeegEYOZG0zn - w+uEtRTzi3ibKURXCyYX+Z1wAjHFoxUW9wvx2OQIm5kjjKd4r+3HCif+gJuMTLtCd4rVSQefUPLgyuYT - TsOLVvN31Qcp0AkdPT5Cfoqlcx9uCJ0xmTf+hSfbVef4lh97BsHDAG+1DQUZRMHl9QpC03F1kCusKGcX - BQ00IE1HrjyXV38EdfgXMtddGBMFPjwghh/MVBgRZnm5phOEA0kTwWSMVeTKMREkIggbFOVxwJMHjAHF - ChYllpd6BfXlYUFFTLaARdWUksiYY54hkTSOQAllGB6EQBE79DGY3UJABqDfYAxMQxEFwwhCJpl0RTRB - mmpCecUpJ0qETYBZ1qnGZN5BNIEJd/z5px06HlRJoYXuoIyMAUywoFVqEaRlQXANNk6mBR0pjqX+lo4l - 0QSBiMEplGIIU01EyCi2IZ08DRTYZFkpFMw2kcBKZiTfgCpsCzvcCmUCQFCgkCtnQAdsSwRRM1k0EFEA - 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 - WOT+SuT6auT8Ot76UuL6ROD6zPP8NN78Mt78Jtz8JNz+ENr+Dtr+DNr+Ctr02PD20PD+NuD4zvL2zPD6 - wPL4xvD8uPL+PeH4wPD+SOL6uPD4vvD+pvL8tfH6tvD8r/D8pvD+pvD8ou78luz8juz8iOr+nPD8pPD8 - n+78nO7+fur+duj8lO76re76ien6XuT4jOj4eub8gur8euj8cuj6gOj6dOb6VuL6TOD8iuz+huz8fOj8 - gOj6fub6muz+je3+mu78ZuX6mOz2tOz0yOz4ouz4mOr2tOr8QuD8ON78LN7+X+b+VOT4qOz4oOr6nOz6 - nuz8V+T8U+P4quz8UOL+UuT4rOz6sO74sO7+TuT+TOT4su74tO72vO74vu7+uPT+/Pz+x/X8QOD2xO76 - qe76pe7++vz0yu7+2vj00u76YOL6YuT6ZuT8ROD83/f4juj8RuL8SOL+JN78SuL+Itz+4vr+INz+Htz+ - HN7+HNz+Gtz6eub04/H87Pn++Pz62vX58fj8gOr81vb+NOD45fX8wfP+RuL+rvH8lu7+mO7+1Pj8nez+ - tvT8+vv8+Pz69vr39fb29Pb66vj8zvb28Pb06vT43PT24vP60fT21vL40/P6yPL4yvD4w/D6u/H8tvL8 - svD8q/D+pPD+vfT+0fcAAAAI/gADCBxIsKDBgwgTDtR1bsI5XQojSpxIsaLFgQLI9evH7dzFjyBDhiSX - q2QuciJTqlxJ0GTJfixjyqwowGXJmThzGjxnM5fOnwnPqWPA4CJPm0CTEmQwpEsXKkUrHnX5UVhJYRCV - RtSFwIBXAxmyTpxq8qI/O2jtoNSq8ByNrwbKwJPa0+K1tGjDiWW70wxcAz8mUCR7k6KuXXjR7uVLUBer - vwY4DK5LcUViO7sYJ1Rz5m8YEWMpS5xw57IHzQgnaIDMKTTSidwu+0Od0AS1v9RGSCTsU6KHcInDoaON - 8FwIyFg8thWt0MVlYcQTlgjz9wy9iLwlsrsMQUD0hCgg/ueIWpz5wXP9Ll/7nlBdJMi1lr9OiOxyZvYJ - FfiFSwp0+fnnmWMafgkxsAhkiiw2UHYJxZbYbAQm5EQCfxXgzX9UIfRbcMOxpeBEunQAGQXKFcSgQYdd - hgxfEwwl2EUkVPDXA/acZ95A2yUGE1sYIOKANInMUyJFREAmCnkL3hjAORCo99E15hwAiAsVcVLAlQVI - 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 - 9PL09Mru8O7w9MDs9Lzs+fH49tjx8/Dy8uzy8ujw8uDw8tzu8PDw+tf1/lDk/i7g/JDs/Ibq/Ezi/mfn - /jjg/g7a+pTq+mzm+mTk+lTi+Yrq/hLa+H7m+Izo9qzq9OXy9MTs/iLc/Hbo+ojq9qDq/iDc/HDo/Gzm - +oTo+Jbo+Ijo+Ibo/GTm/GDm/k7k/kLi+nDk+mrk+l7k/EDg+l7i9Nbu/Drg+lLg/iLe/C7e/Cze/h7c - /hrc/hjc/hbc/hTc/hLc/gza/gra/gja+rzy9sjv9NLu9Mzu/jzi/LXx+Lfu+rDw+LDu/KLu+qzu+K7u - +qju/pbu+qTu/KDu/Jzu/nDq+prs+pLq/Gbm/Gjm/I7r/H7q/HTo/G7o/njp/Irr/oDs/Ijs+nvn+IDm - +mLi/ofs/Hrq/Hro/o7s+JTq9q7s+obo+ojo/HLo+m7m+Jzq+pzs+KLs+qPs/Fnj/ELg/Dzg/Dre/lbl - /kzk+qjs+KTs+Krs/rjz/FDj/E7i9rbs/kri/kjk/kji/kbi/kTi/Pz8/vr+/vr8/uL6+pTs/jDe/N33 - +o7q/ize+orq/ire/ije/ibe/r70/iTe/HTm99/z/sH0/sj2/ETg/Ov6+un2/Ejg+sLy/vj+9uTy/sr2 - /Eji/Eri/tT4+uP2/Lzy/qDw/IDq+sny/rDy/Pr7+vb5/Pj8/ND2+vT6+Nj08/Hz8u7w+t329Ozy9Nrw - +sDy987x/Lny9sDu+rnw+LLu/KLw+q7u+qru/pzu/KDw/J7u/rz0/vz8/uT6/OT4+N/1/sX2/PP6+u34 - +Ob2/tr4+uX4/MHz/qLw+tDz+L/w+LTu/q7y/M31/nDo/KXu+OT0/IPq/rPz/Nb2/K3w9u319N/wAAAA - CP4AAwgcSLCgwYMIEwrUMA3GABgkKCicSLGixYsYBabgUaZMBWsSM4ocSTJjqgkGUhrYEbKky5cwKZhR - aeBHS5g4c1psQNMABA06gwpFuKLnz6FIFTZroOFmRWpGGySdWpCCjiuYmgC9CJUmBKkY29nS9SoV1Yqp - jmxYu8GHWYtdVX7FuI+ZXWbtzlJsVoTthjDSuEa9qE7fXWag3upF2EyM3w1WnCaMm3Iu2l2HmelbrDDV - pccb5Fk0Mbiiu8zMdnFWmG3M40zOKpL2CnZiM1uZ9albnZACB9CUZJee+Aq1Md4Kh3B5zMUXxdlyayd0 - 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 - ullDRAEKP9V/Fon3RbSBV3NmvA1GEIQ2bCorkAMXG0MEhr2TsueMqJxHIEzlXgAsdBWad+JFF0kzXjvZ - G+w86UqJ7GTjAHWhpCIEd5hrHLJXdapRlQRJClFAAAAh+QQABQAAACwAAAAAZABkAIf4buT+/v7+/P7y - 7PL+SuT+Gtz+Ctr+mvD8qO78iur8MN7u7u78wPT8pPD29vb+auj+Kt7+DNr+qvL8pu78buj8+vz68vj6 - cub6ZOT6SuD6lOr8+vr4hOb4eub4wvD4uvD4kur8+Pz8+Pr49vj2nOj87Pn0zvDw8PD0xu70vuz49Pj4 - 8Pb09PTz8fL27PXy7vD+GNr+Wub+HNz8pO78ou78oO78nu78lu78WOT6wvL+cOj+L9/+FNz8hOr6fuj6 - bub6YOT6muz4hOj4qOz2sOr06vL0zO78juz8jOz8iuz6luz43fT4nOr8jOr8eOr6lur4mur2pur8dej8 - cuj6dub+Vub+TOT81Pb6dOb22vL8SuL6buT6bOT03vD8QuL6UuL8POD8NOD+Ftr40vL21PL01PD8zfX+ - OuL2zvL4xvD8xvT2yPD8xPT4xPD8wvT+QuH7tvD+RuT7rPD8qPD+pvD8pvD+n/D8nO78kOz+eur6jOn6 - cOb6YuT4juj6euj6ou38fOr8euj6huj+f+v8gOj+iu76gOj8fur8gOr8gur+kO3+mu78bOb8VOT8SOL8 - QuD4ru72wu72suz02O74oOz+Yub+VuT8YuT8XOT6p+74pOr6qu7+VOT+sfL+Uub+UuT8UeL6rO7+UOT+ - uvT6sO7+TuT4su72uu74tO74tu74uO74vO7+vPT8/Pz+0Pf8iOr4kuj64vb8ROD++Pz+KN7+Jtz+JN7+ - It7+IN7+INz+Ht7+Htz6zvL8VuT+3fj8hur6fOb6oOz8WuL8lOz83Pb+OuD+OOD45PX+RuL+iuz+wvT8 - luz59fn89vz48vj29Pb08fT28fby7vL8xPL06/T44PX81/f23PL05PD41/T80fb4zPL8yfT8uvL7sPD+ - pPD+uPT+v/T+/Pz+2Pj66fj++v761/X+4/n84fj46/b+yPb04vL6svD8rvL89Pr+NuD+iOz65vb++P76 - 0vT67fj62/b6xvL25vL8vPL8sPL6yvL24vT6vvL6yfT8vvMAAAAI/gADCBxIsKDBgwgTChzB4sSCEyw2 - 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 - bAH6qrUQAmZQkv1qiiIOOiEMOG034mzLDgd1CvHdcYITEgJFnJgcDmzFETjLZw5tOiGITWwD3FyeeXA2 - Afd99GZscNp6MLACUuBslk094onE47SKSHRqgOpXGrBa1gphx2ntUT8/+/UBbVUIshPijh4kFh5zlU8r - asDZDzh42glRw2nhQOmkoIOzTsg0QsC9FE+rdRAuAlArkMDZJlH41BSpx2ldDKANcBYPrK4bvifSigac - mEEI/moljnERdidkvmqFB8IJHFRvYMThCcGHWrGJcDgcYhDdQKlyQwdOkY3ewMuwTQqcRYLxzXBQkyW/ - rrBhLhLr9jbgNK85aAmrg3LnvOQICO0cfi6UVqhgAmSdw1mJflNmLLIpKd9JQAAAIfkEAAUAAAAsAAAA - AGQAZACH9nrk/v7+/vz+7u7u/kTi/iTe/gTa/pTu/JTu/FDi/B7c8srs/Jru+Pj4/mbm/jTg/gba/qTw - /JLs+obq+vr6+mDi+kTg+jre+qDu+Iro+vj6+Gri+Fbg+LLu+ITm/Pj8/Mz19rbu8PDw9oTm/hTc+PH3 - 9u709PT09N7w9Nbw9MDu9LTq9Krq8u7w8uLw8tzw9PL09PD09O/y/lTm/jLg/Hbo/Eji/nTq/jrg/hLa - /Jrs+prs+mzm+lLi+HTk+Jjq9rLs9qDo/hvc9Nrw9NTw9MLs8ujy/Jbs+pLq+tf1/mTo+pDq+o7q+ozq - +JDq9qrq9qDq9OTy/Gbm/iLc/Fzk+mrm9o7m/kji/Fjk+m7k99by/FLi/DTe+mLi/ibe+lDg+kjg/Cjc - /CTc/CDc/hDa/g7a/gza/gra/gja9s3w+rrx9M7u/Lvy9sfu/kLi9r7u+rLw+Lju/K3w+qru/Kjv/Kbu - +qbu/qLw/p7w/J7u/pju/Jzu/nvq/mvo/nLq/Izr/HTm/GHl/Frk/ITq/oTs+n3n/Drg/Czc+nbm+lTi - +H/m+nLk/Hvo+njo+KTq9rrs+JLo/ovs+p7s+pTs/pLu+nHm/G/n+JTq/Gjm+pjs/lvm/lrk/F7l+qDs - +qTs/qvx/ljk+Kbs/DLe+Kjs/rT0+LDs/E3i/k7k/kzk/kzi/D7e/rfz/Pz8/EDg/vr8/sv2/Ebi/Dbe - /jDe/GTm/OL4/tr4/izg/ize+lrk/ire/ije/uL6+J/q/HTo+svz+Oj2/PH6+pzs/vj8/NX2/h7e+OL2 - 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 - MqcG+qxBNEQVADhoFg7oUa9up5LWxH6zIrRQrTzJeN9uKiEOarGIP74KVj60VcCfnYoGKJFYtl5EAMMY - LjUZTBFrCBMUGDBBa3HLAM2upiAYJuxYEKvWeuT0d9M85jAsIgA5KEETfyjGjQWSDs3+YSwxHuxYWMGN - 0lorI+NYhR7wAIIV47Z5hNDsPg6SZPYepLeJxQN/l4LU0khDsxiITv6XO/ON0ppDlMkwL1jRi5A1I8S9 - iaUEXTfoY7/eY012Rog8SvsOK0vnHBQeKh8izOX1kuaPHTYuAFlxAM3arM6OVgiNlyqHIZdmGJqVwG4C - PZ6XJvZEAKQAH/zqAG7hJdOoLa03PL0WNmjWDr4idULYUNqH2Q8YfvCrH9ra6AFTBB3vVWsk9nyjOmj2 - HZuC9URAUFo2qI8WDvDrIESla7wwoLT+MJIENDucaBu7IukoLYkbRIFsg/UIOe22Qj7R4RtRINhDpTNV - pE2RceTZ3v4d6v72fW6LgEOtB84mrwkxjZamiN8UYcU68EBlQ2OF1mWEeMRLOEqyaLzjMekyxiRBfiTP - EpYPFid5xOXwWcOq/J8NkENZ5fDRl5eEKQ2wpJVQFBAAIfkEAAUAAAAsAAAAAGQAZACH9ojm/v7+/vz+ - 8PDw/kzk/iDe/gza/pzw/KDu/Ebg/Cbc9Kzq/Lz0/Kbw+Pb4/mzo/jzi/g7a/qzy/I7s+pbq/Pr8+uf3 - +lzi+kLg+I7o+H7k+HDk+Gbi+Mby+Lzw/Pj8/Pj68vLy9qTq9pLm/Pb8/PL6+PT49PT09ODw9NTw9MTu - +PL29PD09PDy8ury8uLw8tzw9t/z+OL09O708u7y/lzm/izg/G7m+oDo/nzs/kPi/hzc/hrc/Jzu/Jju - /JXs+qHt+nLm+lrk+KTs+Hrm9rbs9qLo9O7y9Mzu/h7c/Nj2/JDs+pzs+pbs+Jzq9qzs9Ozy/nLp+I7q - 9Oby/D7g/GDk+nDm/lDk/Fjk/E7i+mjk9OTy/Eji+rnw+k7g/Ezg/DTe/i7e/ize/ire/ije/ibe/iLe - /DDe/Cze9tDw/hTc/hLc/hDc+Nbz/hja/hba/hTa/hDa9Njw/Mz0+Mry/MT0+MLw/MTy/MDy/kzi/L70 - +L7w+7Pw/Kzw+qzw/qry/qTw/Krw/Kjw/p7w+o7q+nzo/Dze/oXs/oDq/Hvp/GTm/Fbk/Ezi+nbm+Ijo - +Ibo+lbi+oPo/ozu/Hbo/pLt/G7o/Jru/Jbu+LLu9rju9NDu/JLs+J7s+Jbo/pru/mPn/lbk+qbu+qDs - +pjs/Jjs/GLl+KLs/Dzg+qju/lTk+qru/FLi+q7u/rPy+rLu/rz0+MDu9r7u/ELg/r/0/Pz8/jPg9sTu - /uL59sru+s30/Gbm/Gjm/Grm+Krs/sn2/vj8+Pj4+n7o/vb8+tT0/Ob49vL0+vP4+Ob2/n7q/H/p+njm - /s72+77x+nrm+u74/PT89vX2+PL49PL09un0+OP2/N349Or0+rzw9tzw+Nv0/NH2+NDy/Mf09sjw/ML0 - +rbw+rDw/qrw/rj0/sP2/vz8/ur6+tL0/sz2+vn6+tz1/Ov69vD2+vT5+Oz2/tP3+sXz+rry9tjy/Lbw - /LDw/LTy/LDy/krk/kri+qTu/nrq9tjw/jzg/jrg/Ibr+nroAAAACP4AAwgcSLCgwYMIEwpEdyLEgBDQ - KiicSLGixYsYBUZrNGYMERYSM4ocSTIjrUk7Uu4Ihq6ky5cwBVYgo3JHmJYxc+qsaKLmjgI4dwodOrCY - T6BEkyKkBQxYUIssjgITWQEdLaUY0QHBgmXIU4pRaxaYehHdplNUKDjAarGJjbc2KGAMq3LsxQqRDOg1 - 4AjYVbYJK0CAa6MWtYt0U9q1mELNXgNmoP0FfLBCLcI2goQEK9UiNEiPDbxZSznhDcw2Olhs0blikdAG - hGwubTDeZcKpSE9kLZbsRBZgQqtpQzvwI9SoKvKu6zvwCNjNZhcvOG0wYQjtKC5X3BzhiyShzf5kn75U - BGpHkxFu/9ndILANsDmRV3giFeZa2Xa3ToiETWhDus13kC2oafFVQesthhA0X8Bmh4AKAWMFaswolGB7 - A9GyAGxVHAghQdjcBhcBASK4n0EzhBHaG+l8qBA6UqAmSkIXIoQOALBFkp6LBkEhC2b8WKDeiQTB4EZo - YzjD40RPoIbDjgLVaJADHMDWS2noOACMdBVBIwRqwxwkZUEqRBBaAhgOhQ4TBOiQSDZQToQLah0alBh7 - Bp2AQWgR0CFSCe7MMs4tF6ECwaEQ6LCEkhcBowxq39hJJC0iwKYFlwp9oMQsr3T6CqEUVaAIoogSwEqJ - CqEg4ls6GFPQnf4KCuSCAqHxcJhFFdwyjqeenlMRLbqQSuondGBqUAWdoNbEqyeiowFsGcR5kDq+8Mqr - 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 - 9Pba8v5m6Px+6vqQ6viW6viU6vaW6PTq8vyA6PqA6Pp45vpu5viG5v5c5P5I5PxC4vpu5PpQ4vw23vpC - 4Pwu3vrN8/wk3P4Q2v4O2v4M2v4K2vjO8vbU8f424PbO8Py+8vjJ8PbK8Pq/8f494fjE8P5I4vi88Pq6 - 8Py48vq28P6m8vy08Pyw8Pys8Pyo8P6m8Pym8P6d8P596v5u6P526PqH6Ppg4viO6PiC5vp86Pp05vpS - 4vpK4P6G7Pqm7fyW7PyE7PyC6vxu5/xN4vw83vww3vwm3Pp66PyI7P6P7fyK6v6a7viz7va67PTI7vTA - 7Piq7PqU6viY6vam6vxk5fig7Pqc7Pic6v5e5vxg5f5a5Pqe7Ppd4vqg7Pxa5Pqq7v5U5P5S5Pis7Pqs - 7v5Q5Pqw7v5O5P5M5Pwy3v5K5Pi27v648/xF4f78/Pa67v76/v76/PbA7vw84PpU4vw63vTO7vxU5Ppi - 4vpm5Pzu+v7a+PxW4vpq5P4k3vuf7f4i3Pqa7Paw7P4g3P4e3P4c3PyZ7v4a3v4a3Pin7P669Pzg9/6+ - 9PyS7Piw7P7K9fLq8vrb9fzK9Pbs9Pp+5vjv9/404Pjn9fbq9P5G4v6t8fx36P6Y7vz2+/z7/PzW9vr2 - +vb29vXy9PTw8vro+PTm8vjf9Pbg9PrU9PjX8/bV8vbS8PzA9PjE8vrF8vi+8Pq78fy68vq48Pyy8v6k - 8AAAAAj+AAMIHEiwoMGDCBMKZFCixYAW69ApnEixosWLGAWSIAQOXBB2EjOKHEkyIzoJDlI66DGhpMuX - MAVOgKTSAZqWMXPqrEiipgMdDHYKHVqQnU+gRJMiRMeAAc6LJo4GNTkhpFKLExKRIpXoacWoNZFeRKfo - 1jgh665aDIWmLZpQGMGqFFsRHaACeAtcYGBV7UF0BNyicQMDqtSLZ5zlLQC0r9+C6NwIRlPOcUK5KelO - ZHBqcYFmUx8jzDEZDRiLIg5XxOS5wCfLogcqkSwYS2iFqcPevtzL8zMlsROiu1DaVcXcc3f/jdJ6Cuzg - ArsFFkzgG0XkmZUblOHAsw7r0Jf+bip94flA7D+1E2SwoTWS8ArZYZnsZttE9JoPAovm+RYG+AqFUJoV - XhmEn3oClWBIa/gAqBAD5ZTWYEIHCsdMa8MU6GBBM9DmFg3qUKjaQSZ04Vkz8Gyo0ARQlJaIiLohNIEg - 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 - - - \ No newline at end of file From 18d22700ee46da4c15bcf72a6d6b630ec07756be Mon Sep 17 00:00:00 2001 From: James Friel Date: Wed, 11 Oct 2023 14:42:01 +0100 Subject: [PATCH 11/26] interim commit --- .../WindowManagement/ActivateItems.cs | 14 +- .../Pipeline/CohortHoldoutCreationRequest.cs | 271 ++++++++++++++ .../Pipeline/ICohortHoldoutCreationRequest.cs | 32 ++ .../CohortCreationCommandExecution.cs | 7 +- .../ExecuteCommandCreateHoldoutCohort.cs | 73 +++- .../CommandExecution/BasicActivateItems.cs | 12 +- .../CommandExecution/IBasicActivateItems.cs | 5 +- .../Pipeline/ExtractionHoldout.cs | 3 + ...CohortHoldoutCreationRequestUI.Designer.cs | 343 ++++++++++++++++++ .../CohortHoldoutCreationRequestUI.cs | 212 +++++++++++ .../CohortHoldoutCreationRequestUI.resx | 120 ++++++ 11 files changed, 1071 insertions(+), 21 deletions(-) create mode 100644 Rdmp.Core/CohortCommitting/Pipeline/CohortHoldoutCreationRequest.cs create mode 100644 Rdmp.Core/CohortCommitting/Pipeline/ICohortHoldoutCreationRequest.cs create mode 100644 Rdmp.UI/CohortUI/CohortHoldout/CohortHoldoutCreationRequestUI.Designer.cs create mode 100644 Rdmp.UI/CohortUI/CohortHoldout/CohortHoldoutCreationRequestUI.cs create mode 100644 Rdmp.UI/CohortUI/CohortHoldout/CohortHoldoutCreationRequestUI.resx diff --git a/Application/ResearchDataManagementPlatform/WindowManagement/ActivateItems.cs b/Application/ResearchDataManagementPlatform/WindowManagement/ActivateItems.cs index 543da4aedf..fdfa457323 100644 --- a/Application/ResearchDataManagementPlatform/WindowManagement/ActivateItems.cs +++ b/Application/ResearchDataManagementPlatform/WindowManagement/ActivateItems.cs @@ -840,19 +840,19 @@ public override IPipelineRunner GetPipelineRunner(DialogArgs args, IPipelineUseC return configureAndExecuteDialog; } - public override CohortCreationRequest GetCohortHoldoutCreationRequest(ExternalCohortTable externalCohortTable, IProject project, string cohortInitialDescription) + public override CohortHoldoutCreationRequest GetCohortHoldoutCreationRequest(ExternalCohortTable externalCohortTable, IProject project, CohortIdentificationConfiguration cic) { // if on wrong Thread if (_mainDockPanel?.InvokeRequired ?? false) return _mainDockPanel.Invoke(() => - GetCohortHoldoutCreationRequest(externalCohortTable, project, cohortInitialDescription)); + GetCohortHoldoutCreationRequest(externalCohortTable, project, cic)); - var ui = new Rdmp.UI.CohortUI.CohortHoldout.CohortHoldoutCreationRequestUI(this, externalCohortTable, project); + var ui = new Rdmp.UI.CohortUI.CohortHoldout.CohortHoldoutCreationRequestUI(this, externalCohortTable, project,cic); - if (!string.IsNullOrWhiteSpace(cohortInitialDescription)) - ui.CohortDescription = $"{cohortInitialDescription} ({Environment.UserName} - {DateTime.Now})"; - - return ui.ShowDialog() == DialogResult.OK ? ui.Result : null; + if (!string.IsNullOrWhiteSpace(cic.Description)) + ui.CohortDescription = $"{cic.Description} ({Environment.UserName} - {DateTime.Now})"; + //todo the ui portion does not work currently + return ui.ShowDialog() == DialogResult.OK ? ui.Result : ui.Result; } diff --git a/Rdmp.Core/CohortCommitting/Pipeline/CohortHoldoutCreationRequest.cs b/Rdmp.Core/CohortCommitting/Pipeline/CohortHoldoutCreationRequest.cs new file mode 100644 index 0000000000..ae5697832a --- /dev/null +++ b/Rdmp.Core/CohortCommitting/Pipeline/CohortHoldoutCreationRequest.cs @@ -0,0 +1,271 @@ +// Copyright (c) The University of Dundee 2018-2019 +// This file is part of the Research Data Management Platform (RDMP). +// RDMP is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +// RDMP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +// You should have received a copy of the GNU General Public License along with RDMP. If not, see . + +using System; +using System.Data; +using System.Linq; +using FAnsi.Connections; +using Rdmp.Core.Curation.Data; +using Rdmp.Core.Curation.Data.Cohort; +using Rdmp.Core.Curation.Data.Pipelines; +using Rdmp.Core.DataExport; +using Rdmp.Core.DataExport.Data; +using Rdmp.Core.DataFlowPipeline; +using Rdmp.Core.DataFlowPipeline.Requirements; +using Rdmp.Core.MapsDirectlyToDatabaseTable; +using Rdmp.Core.Repositories; +using Rdmp.Core.ReusableLibraryCode.Checks; + +namespace Rdmp.Core.CohortCommitting.Pipeline; + +/// +/// All metadata details nessesary to create a cohort including which project it goes into, its name, version etc. There are no identifiers for the cohort. +/// Also functions as the use case for cohort creation (to which it passes itself as an input object). +/// +public sealed class CohortHoldoutCreationRequest : PipelineUseCase, ICanBeSummarised,ICohortHoldoutCreationRequest +{ + //private readonly IDataExportRepository _repository; + + //for pipeline editing initialization when no known cohort is available + + #region Things that can be turned into cohorts + + private FlatFileToLoad _fileToLoad; + private ExtractionInformation _extractionIdentifierColumn; + private CohortIdentificationConfiguration _cohortIdentificationConfiguration; + + public FlatFileToLoad FileToLoad + { + get => _fileToLoad; + set + { + //remove old value if it had one + Pop(_fileToLoad); + + _fileToLoad = value; + + //add the new one + Push(value); + } + } + + public CohortIdentificationConfiguration CohortIdentificationConfiguration + { + get => _cohortIdentificationConfiguration; + set + { + Pop(_cohortIdentificationConfiguration); + _cohortIdentificationConfiguration = value; + Push(value); + } + } + + public ExtractionInformation ExtractionIdentifierColumn + { + get => _extractionIdentifierColumn; + set + { + Pop(_extractionIdentifierColumn); + _extractionIdentifierColumn = value; + Push(_extractionIdentifierColumn); + } + } + + private void Pop(object oldValue) + { + if (oldValue != null && InitializationObjects.Contains(oldValue)) + InitializationObjects.Remove(oldValue); + } + + private void Push(object newValue) + { + AddInitializationObject(newValue); + } + + #endregion + + public CohortIdentificationConfiguration CIC { get; set; } + public int Count { get; set; } + public bool IsPercent { get; set; } + + public string Name { get; set; } + + //public IProject Project { get; private set; } + //public ICohortDefinition NewCohortDefinition { get; set; } + + //public ExtractableCohort CohortCreatedIfAny { get; set; } + + public string DescriptionForAuditLog { get; set; } + + public CohortHoldoutCreationRequest(CohortIdentificationConfiguration cic, string name,int count, bool isPercent, string descriptionForAuditLog) + { + //_repository = repository; + //Project = project; + //NewCohortDefinition = newCohortDefinition; + CIC = cic; + Name = name; + Count = count; + IsPercent = isPercent; + DescriptionForAuditLog = descriptionForAuditLog; + + //AddInitializationObject(Project); + AddInitializationObject(this); + + GenerateContext(); + } + + /// + /// For refreshing the current extraction configuration CohortIdentificationConfiguration ONLY. The ExtractionConfiguration must have a cic and a refresh pipeline configured on it. + /// + /// + public CohortHoldoutCreationRequest(ExtractionConfiguration configuration) + { + //_repository = (IDataExportRepository)configuration.Repository; + + //if (configuration.CohortIdentificationConfiguration_ID == null) + // throw new NotSupportedException( + // $"Configuration '{configuration}' does not have an associated CohortIdentificationConfiguration for cohort refreshing"); + + //var origCohort = configuration.Cohort; + //var origCohortData = origCohort.GetExternalData(); + //CohortIdentificationConfiguration = configuration.CohortIdentificationConfiguration; + //Project = configuration.Project; + + //if (Project.ProjectNumber == null) + // throw new ProjectNumberException($"Project '{Project}' does not have a ProjectNumber"); + + //var definition = new CohortDefinition(null, origCohortData.ExternalDescription, + // origCohortData.ExternalVersion + 1, (int)Project.ProjectNumber, origCohort.ExternalCohortTable) + //{ + // CohortReplacedIfAny = origCohort + //}; + + //NewCohortDefinition = definition; + //DescriptionForAuditLog = "Cohort Refresh"; + + //AddInitializationObject(Project); + //AddInitializationObject(CohortIdentificationConfiguration); + //AddInitializationObject(FileToLoad); + //AddInitializationObject(ExtractionIdentifierColumn); + //AddInitializationObject(this); + + //GenerateContext(); + } + + protected override IDataFlowPipelineContext GenerateContextImpl() => + new DataFlowPipelineContext + { + MustHaveDestination = typeof(ICohortPipelineDestination), + MustHaveSource = typeof(IDataFlowSource) + }; + + + public void Check(ICheckNotifier notifier) + { + //NewCohortDefinition.LocationOfCohort.Check(notifier); + + //if (NewCohortDefinition.ID != null) + // notifier.OnCheckPerformed( + // new CheckEventArgs( + // $"Expected the cohort definition {NewCohortDefinition} to have a null ID - we are trying to create this, why would it already exist?", + // CheckResult.Fail)); + //else + // notifier.OnCheckPerformed(new CheckEventArgs("Confirmed that cohort ID is null", CheckResult.Success)); + + //if (Project.ProjectNumber == null) + // notifier.OnCheckPerformed(new CheckEventArgs( + // $"Project {Project} does not have a ProjectNumber specified, it should have the same number as the CohortHoldoutCreationRequest ({NewCohortDefinition.ProjectNumber})", + // CheckResult.Fail)); + //else if (Project.ProjectNumber != NewCohortDefinition.ProjectNumber) + // notifier.OnCheckPerformed( + // new CheckEventArgs( + // $"Project {Project} has ProjectNumber={Project.ProjectNumber} but the CohortHoldoutCreationRequest.ProjectNumber is {NewCohortDefinition.ProjectNumber}", + // CheckResult.Fail)); + + + //if (!NewCohortDefinition.IsAcceptableAsNewCohort(out var matchDescription)) + // notifier.OnCheckPerformed(new CheckEventArgs($"Cohort failed novelness check:{matchDescription}", + // CheckResult.Fail)); + //else + // notifier.OnCheckPerformed(new CheckEventArgs( + // $"Confirmed that cohort {NewCohortDefinition} does not already exist", + // CheckResult.Success)); + + //if (string.IsNullOrWhiteSpace(DescriptionForAuditLog)) + // notifier.OnCheckPerformed( + // new CheckEventArgs("User did not provide a description of the cohort for the AuditLog", + // CheckResult.Fail)); + } + + //public void PushToServer(IManagedConnection connection) + //{ + // if (!NewCohortDefinition.IsAcceptableAsNewCohort(out var reason)) + // throw new Exception(reason); + + // NewCohortDefinition.LocationOfCohort.PushToServer(NewCohortDefinition, connection); + //} + + //public int ImportAsExtractableCohort(bool deprecateOldCohortOnSuccess, bool migrateUsages) + //{ + // if (NewCohortDefinition.ID == null) + // throw new NotSupportedException( + // "CohortHoldoutCreationRequest cannot be imported because its ID is null, it is likely that it has not been pushed to the server yet"); + + // var cohort = new ExtractableCohort(_repository, (ExternalCohortTable)NewCohortDefinition.LocationOfCohort, + // (int)NewCohortDefinition.ID); + // cohort.AppendToAuditLog(DescriptionForAuditLog); + + // CohortCreatedIfAny = cohort; + + // if (deprecateOldCohortOnSuccess && NewCohortDefinition.CohortReplacedIfAny != null) + // { + // NewCohortDefinition.CohortReplacedIfAny.IsDeprecated = true; + // NewCohortDefinition.CohortReplacedIfAny.SaveToDatabase(); + // } + + // if (migrateUsages && NewCohortDefinition.CohortReplacedIfAny != null) + // { + // var oldId = NewCohortDefinition.CohortReplacedIfAny.ID; + // var newId = cohort.ID; + + // // ExtractionConfigurations that use the old (replaced) cohort + // var liveUsers = _repository.GetAllObjects() + // .Where(ec => ec.Cohort_ID == oldId && ec.IsReleased == false); + + // foreach (var ec in liveUsers) + // { + // ec.Cohort_ID = newId; + // ec.SaveToDatabase(); + // } + // } + + // return cohort.ID; + //} + + + /// + /// Design time types + /// + private CohortHoldoutCreationRequest() : base(new Type[] + { + typeof(FlatFileToLoad), + typeof(CohortIdentificationConfiguration), + typeof(Project), + typeof(ExtractionInformation), + typeof(ICohortCreationRequest) + }) + { + GenerateContext(); + } + + public static PipelineUseCase DesignTime() => new CohortHoldoutCreationRequest(); + + public override string ToString() => ""; + //NewCohortDefinition == null ? base.ToString() : NewCohortDefinition.Description; + + public string GetSummary(bool includeName, bool includeId) => ""; + //$"External Cohort Table: {NewCohortDefinition?.LocationOfCohort}"; +} \ No newline at end of file diff --git a/Rdmp.Core/CohortCommitting/Pipeline/ICohortHoldoutCreationRequest.cs b/Rdmp.Core/CohortCommitting/Pipeline/ICohortHoldoutCreationRequest.cs new file mode 100644 index 0000000000..8683b5f0d4 --- /dev/null +++ b/Rdmp.Core/CohortCommitting/Pipeline/ICohortHoldoutCreationRequest.cs @@ -0,0 +1,32 @@ +// Copyright (c) The University of Dundee 2018-2019 +// This file is part of the Research Data Management Platform (RDMP). +// RDMP is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +// RDMP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +// You should have received a copy of the GNU General Public License along with RDMP. If not, see . + +using FAnsi.Connections; +using Rdmp.Core.Curation.Data; +using Rdmp.Core.Curation.Data.Cohort; +using Rdmp.Core.Curation.Data.Pipelines; +using Rdmp.Core.DataExport.Data; +using Rdmp.Core.DataFlowPipeline.Requirements; +using Rdmp.Core.ReusableLibraryCode.Checks; + +namespace Rdmp.Core.CohortCommitting.Pipeline; + +/// +/// See CohortCreationRequest +/// +public interface ICohortHoldoutCreationRequest : ICheckable, IHasDesignTimeMode, IPipelineUseCase +{ + // IProject Project { get; } + // ICohortDefinition NewCohortDefinition { get; set; } + // ExtractionInformation ExtractionIdentifierColumn { get; set; } + // CohortIdentificationConfiguration CohortIdentificationConfiguration { get; set; } + // ExtractableCohort CohortCreatedIfAny { get; } + // FlatFileToLoad FileToLoad { get; set; } + + // int ImportAsExtractableCohort(bool deprecateOldCohortOnSuccess, bool migrateUsages); + // void PushToServer(IManagedConnection transaction); + +} \ No newline at end of file diff --git a/Rdmp.Core/CommandExecution/AtomicCommands/CohortCreationCommands/CohortCreationCommandExecution.cs b/Rdmp.Core/CommandExecution/AtomicCommands/CohortCreationCommands/CohortCreationCommandExecution.cs index 5c5c35ec30..6f1dd7e0ac 100644 --- a/Rdmp.Core/CommandExecution/AtomicCommands/CohortCreationCommands/CohortCreationCommandExecution.cs +++ b/Rdmp.Core/CommandExecution/AtomicCommands/CohortCreationCommands/CohortCreationCommandExecution.cs @@ -9,6 +9,7 @@ using Rdmp.Core.CohortCommitting.Pipeline; using Rdmp.Core.CommandLine.Runners; using Rdmp.Core.Curation.Data; +using Rdmp.Core.Curation.Data.Cohort; using Rdmp.Core.Curation.Data.Defaults; using Rdmp.Core.Curation.Data.Pipelines; using Rdmp.Core.DataExport.Data; @@ -69,7 +70,7 @@ protected CohortCreationCommandExecution(IBasicActivateItems activator, External SetImpossible("There are no cohort sources configured, you must create one in the Saved Cohort tabs"); } - protected ICohortCreationRequest GetCohortHoldoutCreationRequest(string auditLogDescription) + protected CohortHoldoutCreationRequest GetCohortHoldoutCreationRequest(CohortIdentificationConfiguration cic) { //user wants to create a new cohort var ect = ExternalCohortTable; @@ -91,9 +92,9 @@ protected ICohortCreationRequest GetCohortHoldoutCreationRequest(string auditLog // otherwise we are going to have to ask the user for it //Get a new request for the source they are trying to populate - var req = BasicActivator.GetCohortHoldoutCreationRequest(ect, Project, auditLogDescription); + var req = BasicActivator.GetCohortHoldoutCreationRequest(ect, Project, cic); - Project ??= req?.Project; + //Project ??= req?.Project; return req; } diff --git a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandCreateHoldoutCohort.cs b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandCreateHoldoutCohort.cs index b357ab88ac..09e8529746 100644 --- a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandCreateHoldoutCohort.cs +++ b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandCreateHoldoutCohort.cs @@ -4,12 +4,23 @@ // RDMP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. // You should have received a copy of the GNU General Public License along with RDMP. If not, see . +using NPOI.OpenXmlFormats.Dml.Diagram; +using NPOI.XWPF.UserModel; +using Rdmp.Core.CohortCommitting.Pipeline; +using Rdmp.Core.Curation.Data; +using Rdmp.Core.Curation.Data.Aggregation; using Rdmp.Core.Curation.Data.Cohort; using Rdmp.Core.DataExport.Data; using Rdmp.Core.Icons.IconProvision; +using Rdmp.Core.Repositories; +using Rdmp.Core.ReusableLibraryCode.Checks; using Rdmp.Core.ReusableLibraryCode.Icons.IconProvision; +using SixLabors.Fonts; using SixLabors.ImageSharp; using SixLabors.ImageSharp.PixelFormats; +using System; +using System.ComponentModel; +using Terminal.Gui; namespace Rdmp.Core.CommandExecution.AtomicCommands.CohortCreationCommands; @@ -18,11 +29,14 @@ namespace Rdmp.Core.CommandExecution.AtomicCommands.CohortCreationCommands; public class ExecuteCommandCreateHoldoutCohort : CohortCreationCommandExecution { private readonly CohortIdentificationConfiguration _cic; - //private readonly bool _desiredFreezeState; + private IBasicActivateItems _activator; + //private CohortIdentificationConfiguration CloneCreatedIfAny; + private CohortIdentificationConfiguration HoldoutConfiguration; public ExecuteCommandCreateHoldoutCohort(IBasicActivateItems activator, CohortIdentificationConfiguration cic) : base(activator) { + _activator = activator; _cic = cic; } @@ -34,8 +48,61 @@ public ExecuteCommandCreateHoldoutCohort(IBasicActivateItems activator, public override void Execute() { base.Execute(); - var request = GetCohortHoldoutCreationRequest(ExtractableCohortAuditLogBuilder.GetDescription(_cic)); - //here + //this should give use the suer configuration for their hold out + //we can fake it for now + CohortHoldoutCreationRequest request = GetCohortHoldoutCreationRequest(_cic); + + if(request is null) + { + return; + } + + + //SELECT distinct top request.count percent + //extraction_identifier, Rand() + //FROM + //data + //where request.sql + //group by extraction_identifier + //ORDER BY RAND() desc + + + //copy cohort + HoldoutConfiguration = _cic.CreateClone(ThrowImmediatelyCheckNotifier.Quiet); + CohortAggregateContainer container = HoldoutConfiguration.RootCohortAggregateContainer; + + + CohortAggregateContainer subcontainer = new CohortAggregateContainer(_activator.RepositoryLocator.CatalogueRepository, SetOperation.INTERSECT); + subcontainer.SaveToDatabase(); + container.AddChild(subcontainer); + + container.SaveToDatabase(); + //AggregateConfiguration[] aggregateConfirgurations = container.GetAggregateConfigurations(); + //foreach(AggregateConfiguration aggregate in aggregateConfirgurations) + //{ + // AggregateFilterContainer filterContainer = new AggregateFilterContainer(_activator.RepositoryLocator.CatalogueRepository, FilterContainerOperation.AND); + // var filter = new AggregateFilter(_activator.RepositoryLocator.CatalogueRepository, "name", filterContainer) + // { + // WhereSQL = "" + // }; + // filter.SaveToDatabase(); + // filterContainer.SaveToDatabase(); + //} + //inject where + //RootFilterContainer + //var container = new AggregateFilterContainer(_activator.RepositoryLocator.CatalogueRepository, FilterContainerOperation.AND); + //var filter = new AggregateFilter(_activator.RepositoryLocator.CatalogueRepository,"name",container ) + //{ + // WhereSQL = "" + //}; + //filter.SaveToDatabase(); + + HoldoutConfiguration.SaveToDatabase(); + //todo add container ro aggregate configuration + //inject top x % + //savecohort + //add cohhort as new excpet exisitng cohort builder query + } public override Image GetImage(IIconProvider iconProvider) => diff --git a/Rdmp.Core/CommandExecution/BasicActivateItems.cs b/Rdmp.Core/CommandExecution/BasicActivateItems.cs index 461c311b56..da877933c4 100644 --- a/Rdmp.Core/CommandExecution/BasicActivateItems.cs +++ b/Rdmp.Core/CommandExecution/BasicActivateItems.cs @@ -636,8 +636,8 @@ public virtual IPipelineRunner GetPipelineRunner(DialogArgs args, IPipelineUseCa new PipelineRunner(useCase, pipeline); /// - public virtual CohortCreationRequest GetCohortHoldoutCreationRequest(ExternalCohortTable externalCohortTable, - IProject project, string cohortInitialDescription) + public virtual CohortHoldoutCreationRequest GetCohortHoldoutCreationRequest(ExternalCohortTable externalCohortTable, + IProject project, CohortIdentificationConfiguration cic) { int version; var projectNumber = project?.ProjectNumber; @@ -658,10 +658,10 @@ public virtual CohortCreationRequest GetCohortHoldoutCreationRequest(ExternalCoh else throw new Exception("User chose not to enter a version number and none was provided"); - - return new CohortCreationRequest(project, - new CohortDefinition(null, name, version, projectNumber.Value, externalCohortTable), - RepositoryLocator.DataExportRepository, cohortInitialDescription); + return new CohortHoldoutCreationRequest(cic,"", 1, false, ""); + //return new CohortCreationRequest(project, + // new CohortDefinition(null, name, version, projectNumber.Value, externalCohortTable), + // RepositoryLocator.DataExportRepository, cic.Description); } /// diff --git a/Rdmp.Core/CommandExecution/IBasicActivateItems.cs b/Rdmp.Core/CommandExecution/IBasicActivateItems.cs index 111076bd35..713eba0aba 100644 --- a/Rdmp.Core/CommandExecution/IBasicActivateItems.cs +++ b/Rdmp.Core/CommandExecution/IBasicActivateItems.cs @@ -1,4 +1,5 @@ // Copyright (c) The University of Dundee 2018-2019 +// Copyright (c) The University of Dundee 2018-2019 // This file is part of the Research Data Management Platform (RDMP). // RDMP is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. // RDMP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. @@ -165,8 +166,8 @@ public interface IBasicActivateItems //todo description - CohortCreationRequest GetCohortHoldoutCreationRequest(ExternalCohortTable externalCohortTable, IProject project, - string cohortInitialDescription); + CohortHoldoutCreationRequest GetCohortHoldoutCreationRequest(ExternalCohortTable externalCohortTable, IProject project, + CohortIdentificationConfiguration cic); /// /// Prompts the user to enter a description for a cohort they are trying to create including whether it is intended to replace an old version of another cohort. diff --git a/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs b/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs index ca3b1b8c2a..acb707afd6 100644 --- a/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs +++ b/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs @@ -43,6 +43,9 @@ public class ExtractionHoldout : IPluginDataFlowComponent, IPipelineR [DemandsInitialization("Allows for the filtering of what data can be used as holdout data. The filter only currently supports filtering on string columns, not dates. Filter References https://learn.microsoft.com/en-us/dotnet/api/system.data.dataview.rowfilter?view=net-7.0 and https://learn.microsoft.com/en-us/dotnet/api/system.data.datacolumn.expression?view=net-7.0")] public string whereCondition { get; set; } + //todo want to be able to override or append to the output file + + // We may want to automatically reimport into RDMP, but this is quite complicated. It may be worth having users reimport the catalogue themself until it is proven that this is worth building. //Currently only support writting holdback data to a CSV diff --git a/Rdmp.UI/CohortUI/CohortHoldout/CohortHoldoutCreationRequestUI.Designer.cs b/Rdmp.UI/CohortUI/CohortHoldout/CohortHoldoutCreationRequestUI.Designer.cs new file mode 100644 index 0000000000..e9c237aa53 --- /dev/null +++ b/Rdmp.UI/CohortUI/CohortHoldout/CohortHoldoutCreationRequestUI.Designer.cs @@ -0,0 +1,343 @@ +using Rdmp.UI.ChecksUI; +using Rdmp.UI.SimpleControls; +using System.Collections.Generic; + +namespace Rdmp.UI.CohortUI.CohortHoldout +{ + partial class CohortHoldoutCreationRequestUI + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + gbNewCohort = new System.Windows.Forms.GroupBox(); + label7 = new System.Windows.Forms.Label(); + tbName = new System.Windows.Forms.TextBox(); + gbChooseCohortType = new System.Windows.Forms.GroupBox(); + label1 = new System.Windows.Forms.Label(); + comboBox1 = new System.Windows.Forms.ComboBox(); + numericUpDown1 = new System.Windows.Forms.NumericUpDown(); + groupBox3 = new System.Windows.Forms.GroupBox(); + label9 = new System.Windows.Forms.Label(); + tbDescription = new System.Windows.Forms.TextBox(); + btnClearProject = new System.Windows.Forms.Button(); + btnOk = new System.Windows.Forms.Button(); + ragSmiley1 = new RAGSmiley(); + gbDescription = new System.Windows.Forms.GroupBox(); + taskDescriptionLabel1 = new SimpleDialogs.TaskDescriptionLabel(); + panel1 = new System.Windows.Forms.Panel(); + panel2 = new System.Windows.Forms.Panel(); + groupBox1 = new System.Windows.Forms.GroupBox(); + scintilla1 = new ScintillaNET.Scintilla(); + gbNewCohort.SuspendLayout(); + gbChooseCohortType.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)numericUpDown1).BeginInit(); + groupBox3.SuspendLayout(); + gbDescription.SuspendLayout(); + panel1.SuspendLayout(); + panel2.SuspendLayout(); + groupBox1.SuspendLayout(); + SuspendLayout(); + // + // gbNewCohort + // + gbNewCohort.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + gbNewCohort.Controls.Add(label7); + gbNewCohort.Controls.Add(tbName); + gbNewCohort.Location = new System.Drawing.Point(13, 24); + gbNewCohort.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + gbNewCohort.Name = "gbNewCohort"; + gbNewCohort.Padding = new System.Windows.Forms.Padding(4, 3, 4, 3); + gbNewCohort.Size = new System.Drawing.Size(1005, 54); + gbNewCohort.TabIndex = 0; + gbNewCohort.TabStop = false; + gbNewCohort.Text = "Holdout Cohort Name"; + // + // label7 + // + label7.AutoSize = true; + label7.Location = new System.Drawing.Point(7, 25); + label7.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + label7.Name = "label7"; + label7.Size = new System.Drawing.Size(42, 15); + label7.TabIndex = 0; + label7.Text = "Name:"; + // + // tbName + // + tbName.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + tbName.Location = new System.Drawing.Point(58, 22); + tbName.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + tbName.Name = "tbName"; + tbName.Size = new System.Drawing.Size(939, 23); + tbName.TabIndex = 1; + tbName.TextChanged += tbName_TextChanged; + // + // gbChooseCohortType + // + gbChooseCohortType.Controls.Add(label1); + gbChooseCohortType.Controls.Add(comboBox1); + gbChooseCohortType.Controls.Add(numericUpDown1); + gbChooseCohortType.Dock = System.Windows.Forms.DockStyle.Top; + gbChooseCohortType.Location = new System.Drawing.Point(0, 0); + gbChooseCohortType.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + gbChooseCohortType.Name = "gbChooseCohortType"; + gbChooseCohortType.Padding = new System.Windows.Forms.Padding(4, 3, 4, 3); + gbChooseCohortType.Size = new System.Drawing.Size(1048, 217); + gbChooseCohortType.TabIndex = 9; + gbChooseCohortType.TabStop = false; + gbChooseCohortType.Text = "1. Define Holdout settings"; + gbChooseCohortType.Enter += gbChooseCohortType_Enter; + // + // label1 + // + label1.AutoSize = true; + label1.Location = new System.Drawing.Point(171, 20); + label1.Name = "label1"; + label1.Size = new System.Drawing.Size(110, 15); + label1.TabIndex = 5; + label1.Text = "of people in Cohort"; + label1.Click += label1_Click; + // + // comboBox1 + // + comboBox1.AllowDrop = true; + comboBox1.FormattingEnabled = true; + comboBox1.Items.AddRange(new object[] { "%", "#" }); + comboBox1.Location = new System.Drawing.Point(122, 17); + comboBox1.Name = "comboBox1"; + comboBox1.Size = new System.Drawing.Size(43, 23); + comboBox1.TabIndex = 4; + // + // numericUpDown1 + // + numericUpDown1.Location = new System.Drawing.Point(20, 18); + numericUpDown1.Name = "numericUpDown1"; + numericUpDown1.Size = new System.Drawing.Size(87, 23); + numericUpDown1.TabIndex = 3; + // + // groupBox3 + // + groupBox3.Controls.Add(gbNewCohort); + groupBox3.Dock = System.Windows.Forms.DockStyle.Top; + groupBox3.Location = new System.Drawing.Point(0, 217); + groupBox3.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + groupBox3.Name = "groupBox3"; + groupBox3.Padding = new System.Windows.Forms.Padding(4, 3, 4, 3); + groupBox3.Size = new System.Drawing.Size(1048, 95); + groupBox3.TabIndex = 10; + groupBox3.TabStop = false; + groupBox3.Text = "3. Configure Cohort (doesn't exist yet, next screen will actually create it)"; + // + // label9 + // + label9.AutoSize = true; + label9.Location = new System.Drawing.Point(7, 24); + label9.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + label9.Name = "label9"; + label9.Size = new System.Drawing.Size(64, 15); + label9.TabIndex = 2; + label9.Text = "Comment:"; + // + // tbDescription + // + tbDescription.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; + tbDescription.Location = new System.Drawing.Point(71, 24); + tbDescription.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + tbDescription.Multiline = true; + tbDescription.Name = "tbDescription"; + tbDescription.Size = new System.Drawing.Size(969, 67); + tbDescription.TabIndex = 3; + // + // btnClearProject + // + btnClearProject.Anchor = System.Windows.Forms.AnchorStyles.Top; + btnClearProject.Location = new System.Drawing.Point(528, 7); + btnClearProject.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + btnClearProject.Name = "btnClearProject"; + btnClearProject.Size = new System.Drawing.Size(141, 27); + btnClearProject.TabIndex = 14; + btnClearProject.Text = "Cancel"; + btnClearProject.UseVisualStyleBackColor = true; + btnClearProject.Click += btnCancel_Click; + // + // btnOk + // + btnOk.Anchor = System.Windows.Forms.AnchorStyles.Top; + btnOk.Location = new System.Drawing.Point(372, 7); + btnOk.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + btnOk.Name = "btnOk"; + btnOk.Size = new System.Drawing.Size(149, 27); + btnOk.TabIndex = 13; + btnOk.Text = "Ok"; + btnOk.UseVisualStyleBackColor = true; + btnOk.Click += btnOk_Click; + // + // ragSmiley1 + // + ragSmiley1.AlwaysShowHandCursor = false; + ragSmiley1.Anchor = System.Windows.Forms.AnchorStyles.Top; + ragSmiley1.BackColor = System.Drawing.Color.Transparent; + ragSmiley1.Location = new System.Drawing.Point(335, 4); + ragSmiley1.Margin = new System.Windows.Forms.Padding(5, 3, 5, 3); + ragSmiley1.Name = "ragSmiley1"; + ragSmiley1.Size = new System.Drawing.Size(30, 30); + ragSmiley1.TabIndex = 12; + // + // gbDescription + // + gbDescription.Controls.Add(label9); + gbDescription.Controls.Add(tbDescription); + gbDescription.Dock = System.Windows.Forms.DockStyle.Top; + gbDescription.Location = new System.Drawing.Point(0, 312); + gbDescription.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + gbDescription.Name = "gbDescription"; + gbDescription.Padding = new System.Windows.Forms.Padding(4, 3, 4, 3); + gbDescription.Size = new System.Drawing.Size(1048, 99); + gbDescription.TabIndex = 11; + gbDescription.TabStop = false; + gbDescription.Text = "4. Enter Description Of Holdout"; + // + // taskDescriptionLabel1 + // + taskDescriptionLabel1.AutoSize = true; + taskDescriptionLabel1.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; + taskDescriptionLabel1.Dock = System.Windows.Forms.DockStyle.Top; + taskDescriptionLabel1.Location = new System.Drawing.Point(0, 0); + taskDescriptionLabel1.Name = "taskDescriptionLabel1"; + taskDescriptionLabel1.Size = new System.Drawing.Size(1048, 42); + taskDescriptionLabel1.TabIndex = 19; + // + // panel1 + // + panel1.Controls.Add(panel2); + panel1.Controls.Add(gbDescription); + panel1.Controls.Add(groupBox1); + panel1.Controls.Add(groupBox3); + panel1.Controls.Add(gbChooseCohortType); + panel1.Dock = System.Windows.Forms.DockStyle.Fill; + panel1.Location = new System.Drawing.Point(0, 42); + panel1.Name = "panel1"; + panel1.Size = new System.Drawing.Size(1048, 509); + panel1.TabIndex = 20; + // + // panel2 + // + panel2.Controls.Add(btnOk); + panel2.Controls.Add(ragSmiley1); + panel2.Controls.Add(btnClearProject); + panel2.Dock = System.Windows.Forms.DockStyle.Top; + panel2.Location = new System.Drawing.Point(0, 411); + panel2.Name = "panel2"; + panel2.Size = new System.Drawing.Size(1048, 55); + panel2.TabIndex = 20; + // + // groupBox1 + // + groupBox1.Controls.Add(scintilla1); + groupBox1.Location = new System.Drawing.Point(13, 66); + groupBox1.Name = "groupBox1"; + groupBox1.Size = new System.Drawing.Size(997, 139); + groupBox1.TabIndex = 6; + groupBox1.TabStop = false; + groupBox1.Text = "2. Define Holdout Criteria"; + // + // scintilla1 + // + scintilla1.AutoCMaxHeight = 9; + scintilla1.BiDirectionality = ScintillaNET.BiDirectionalDisplayType.Disabled; + scintilla1.CaretLineBackColor = System.Drawing.Color.Black; + scintilla1.LexerName = null; + scintilla1.Location = new System.Drawing.Point(19, 22); + scintilla1.Name = "scintilla1"; + scintilla1.ScrollWidth = 49; + scintilla1.Size = new System.Drawing.Size(958, 100); + scintilla1.TabIndents = true; + scintilla1.TabIndex = 0; + scintilla1.Text = "scintilla1"; + scintilla1.UseRightToLeftReadingLayout = false; + scintilla1.WrapMode = ScintillaNET.WrapMode.None; + // + // CohortHoldoutCreationRequestUI + // + AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); + AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + ClientSize = new System.Drawing.Size(1048, 551); + Controls.Add(panel1); + Controls.Add(taskDescriptionLabel1); + Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + Name = "CohortHoldoutCreationRequestUI"; + StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; + Text = "Create Cohort"; + Load += CohortHoldoutCreationRequestUI_Load; + gbNewCohort.ResumeLayout(false); + gbNewCohort.PerformLayout(); + gbChooseCohortType.ResumeLayout(false); + gbChooseCohortType.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)numericUpDown1).EndInit(); + groupBox3.ResumeLayout(false); + gbDescription.ResumeLayout(false); + gbDescription.PerformLayout(); + panel1.ResumeLayout(false); + panel2.ResumeLayout(false); + groupBox1.ResumeLayout(false); + ResumeLayout(false); + PerformLayout(); + } + + #endregion + //private System.Windows.Forms.Label label3; + //private System.Windows.Forms.Label lblExternalCohortTable; + private System.Windows.Forms.GroupBox gbNewCohort; + private System.Windows.Forms.Label label7; + private System.Windows.Forms.TextBox tbName; + //private System.Windows.Forms.Button btnNewProject; + private System.Windows.Forms.GroupBox gbChooseCohortType; + private System.Windows.Forms.GroupBox groupBox3; + private System.Windows.Forms.Label label9; + private System.Windows.Forms.TextBox tbDescription; + //private System.Windows.Forms.Label lblProject; + //private System.Windows.Forms.Label label11; + private System.Windows.Forms.Button btnClearProject; + private System.Windows.Forms.Button btnOk; + private RAGSmiley ragSmiley1; + private System.Windows.Forms.GroupBox gbDescription; + //private System.Windows.Forms.PictureBox pbProject; + //private System.Windows.Forms.PictureBox pbCohortSource; + //private System.Windows.Forms.Button btnExisting; + //private System.Windows.Forms.Label lblErrorNoProjectNumber; + //private System.Windows.Forms.TextBox tbSetProjectNumber; + //private System.Windows.Forms.Button btnClear; + private SimpleDialogs.TaskDescriptionLabel taskDescriptionLabel1; + private System.Windows.Forms.Panel panel1; + private System.Windows.Forms.Panel panel2; + private System.Windows.Forms.GroupBox groupBox1; + //private System.Windows.Forms.CheckBox checkBox1; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.ComboBox comboBox1; + private System.Windows.Forms.NumericUpDown numericUpDown1; + private ScintillaNET.Scintilla scintilla1; + } +} \ No newline at end of file diff --git a/Rdmp.UI/CohortUI/CohortHoldout/CohortHoldoutCreationRequestUI.cs b/Rdmp.UI/CohortUI/CohortHoldout/CohortHoldoutCreationRequestUI.cs new file mode 100644 index 0000000000..b0d9f593d9 --- /dev/null +++ b/Rdmp.UI/CohortUI/CohortHoldout/CohortHoldoutCreationRequestUI.cs @@ -0,0 +1,212 @@ +// Copyright (c) The University of Dundee 2018-2019 +// This file is part of the Research Data Management Platform (RDMP). +// RDMP is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +// RDMP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +// You should have received a copy of the GNU General Public License along with RDMP. If not, see . + +using System; +using System.Drawing; +using System.Linq; +using System.Windows.Forms; +using Rdmp.Core.CohortCommitting.Pipeline; +using Rdmp.Core.CommandExecution; +using Rdmp.Core.Curation.Data.Cohort; +using Rdmp.Core.DataExport.Data; +using Rdmp.Core.Icons.IconProvision; +using Rdmp.Core.Providers; +using Rdmp.Core.Repositories; +using Rdmp.Core.ReusableLibraryCode.Checks; +using Rdmp.UI.ItemActivation; +using Rdmp.UI.Refreshing; +using Rdmp.UI.SimpleDialogs; +using Rdmp.UI.TestsAndSetup.ServicePropogation; +using Point = System.Drawing.Point; + +namespace Rdmp.UI.CohortUI.CohortHoldout; + +/// +/// this is incorrect +/// Once you have created a cohort database, this dialog lets you upload a new cohort into it. You will already have selected a file which contains the private patient identifiers of +/// those you wish to be in the cohort. Next you must create or choose an existing Project for which the cohort belongs. +/// +/// Once you have chosen the project you can choose to either create a new cohort for use with the project (use this if you have multiple cohorts in the project e.g. 'Cases' and +/// 'Controls'). Or 'Revised version of existing cohort' for if you made a mistake with your first version of a cohort or if you are doing a refresh of the cohort (e.g. after 5 years +/// it is likely there will be different patients that match the research study criteria so a new version of the cohort is appropriate). +/// +public partial class CohortHoldoutCreationRequestUI : RDMPForm +{ + private readonly IExternalCohortTable _target; + private IDataExportRepository _repository; + private readonly CohortIdentificationConfiguration _cic; + + public string CohortDescription + { + get => tbDescription.Text; + set => tbDescription.Text = value; + } + + public CohortHoldoutCreationRequestUI(IActivateItems activator, IExternalCohortTable target, IProject project = null, CohortIdentificationConfiguration cic = null) : + base(activator) + { + _target = target; + + InitializeComponent(); + + if (_target == null) + return; + + _repository = (IDataExportRepository)_target.Repository; + _cic = cic; + SetProject(project); + tbName.Text = $"holdout_{cic.Name}"; //todo this should be the existing cohorts name + + taskDescriptionLabel1.SetupFor(new DialogArgs + { + TaskDescription = + "todo. maybe remove?" + }); + } + + + public CohortHoldoutCreationRequest Result { get; set; } + public IProject Project { get; set; } + + private void btnOk_Click(object sender, EventArgs e) + { + string name; + name = tbName.Text; + Result = new CohortHoldoutCreationRequest(_cic, tbName.Text, Decimal.ToInt32(numericUpDown1.Value), comboBox1.Text =="%","" ); + //see if it is passing checks + var notifier = new ToMemoryCheckNotifier(); + Result.Check(notifier); + //if (notifier.GetWorst() <= CheckResult.Warning) + //{ + DialogResult = DialogResult.OK; + Close(); +// } +// else +// { +// var bads = notifier.Messages.Where(c => c.Result == CheckResult.Fail); + +// WideMessageBox.Show("Checks Failed", +// $@"Checks must pass before continuing: +//- {string.Join($"{Environment.NewLine}- ", bads.Select(b => b.Message))}"); + +// //if it is not passing checks display the results of the failing checking +// ragSmiley1.Reset(); +// Result.Check(ragSmiley1); +// } + } + + private void btnCancel_Click(object sender, EventArgs e) + { + Result = null; + DialogResult = DialogResult.Cancel; + Close(); + } + + + private void CohortHoldoutCreationRequestUI_Load(object sender, EventArgs e) + { + _target.Check(ragSmiley1); + } + + + private void btnNewProject_Click(object sender, EventArgs e) + { + try + { + var p = new ProjectUI.ProjectUI(); + var dialog = new RDMPForm(Activator); + + p.SwitchToCutDownUIMode(); + + var ok = new Button(); + ok.Click += (s, ev) => + { + dialog.Close(); + dialog.DialogResult = DialogResult.OK; + }; + ok.Location = new Point(0, p.Height + 10); + ok.Width = p.Width / 2; + ok.Height = 30; + ok.Text = "Ok"; + + var cancel = new Button(); + cancel.Click += (s, ev) => + { + dialog.Close(); + dialog.DialogResult = DialogResult.Cancel; + }; + cancel.Location = new Point(p.Width / 2, p.Height + 10); + cancel.Width = p.Width / 2; + cancel.Height = 30; + cancel.Text = "Cancel"; + + dialog.Controls.Add(ok); + dialog.Controls.Add(cancel); + + dialog.Height = p.Height + 80; + dialog.Width = p.Width + 10; + dialog.Controls.Add(p); + + ok.Anchor = AnchorStyles.Bottom; + cancel.Anchor = AnchorStyles.Bottom; + + var project = new Project(_repository, "New Project"); + p.SetDatabaseObject(Activator, project); + var result = dialog.ShowDialog(); + result = DialogResult.OK; //temp + if (result == DialogResult.OK) + { + //project.SaveToDatabase(); + //SetProject(project); + Activator.RefreshBus.Publish(this, new RefreshObjectEventArgs(project)); + } + else + { + //project.DeleteInDatabase(); + } + } + catch (Exception exception) + { + ExceptionViewer.Show(exception); + } + } + + private void SetProject(IProject project) + { + Project = project; + + gbChooseCohortType.Enabled = true; + } + + private void tbName_TextChanged(object sender, EventArgs e) + { + } + + private void btnExisting_Click(object sender, EventArgs e) + { + if (Activator.SelectObject(new DialogArgs + { + TaskDescription = + "Choose a Project which this cohort will be associated with. This will set the cohorts ProjectNumber. A cohort can only be extracted from a Project whose ProjectNumber matches the cohort (multiple Projects are allowed to have the same ProjectNumber)" + }, Activator.RepositoryLocator.DataExportRepository.GetAllObjects(), out var proj)) + SetProject(proj); + } + + private void btnClear_Click(object sender, EventArgs e) + { + SetProject(null); + } + + private void gbChooseCohortType_Enter(object sender, EventArgs e) + { + + } + + private void label1_Click(object sender, EventArgs e) + { + + } +} \ No newline at end of file diff --git a/Rdmp.UI/CohortUI/CohortHoldout/CohortHoldoutCreationRequestUI.resx b/Rdmp.UI/CohortUI/CohortHoldout/CohortHoldoutCreationRequestUI.resx new file mode 100644 index 0000000000..af32865ec1 --- /dev/null +++ b/Rdmp.UI/CohortUI/CohortHoldout/CohortHoldoutCreationRequestUI.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file From fedbaea5a9db1c2425bae3a4d3a56647c1a92648 Mon Sep 17 00:00:00 2001 From: James Friel Date: Thu, 12 Oct 2023 10:09:32 +0100 Subject: [PATCH 12/26] add cohort top option --- .../QueryBuilding/Options/AggregateBuilderCohortOptions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rdmp.Core/QueryBuilding/Options/AggregateBuilderCohortOptions.cs b/Rdmp.Core/QueryBuilding/Options/AggregateBuilderCohortOptions.cs index c041e4abcb..95ea58cdb1 100644 --- a/Rdmp.Core/QueryBuilding/Options/AggregateBuilderCohortOptions.cs +++ b/Rdmp.Core/QueryBuilding/Options/AggregateBuilderCohortOptions.cs @@ -83,7 +83,7 @@ public bool ShouldBeEnabled(AggregateEditorSection section, AggregateConfigurati return section switch { AggregateEditorSection.Extractable => false, - AggregateEditorSection.TOPX => false, + AggregateEditorSection.TOPX => true, AggregateEditorSection.PIVOT => false, AggregateEditorSection.AXIS => false, _ => throw new ArgumentOutOfRangeException(nameof(section)) From 93d927e061213b282bafc3fa975a1cf12117b033 Mon Sep 17 00:00:00 2001 From: James Friel Date: Thu, 12 Oct 2023 10:40:30 +0100 Subject: [PATCH 13/26] add overwrite protection for holdout --- .../Pipeline/ExtractionHoldout.cs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs b/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs index acb707afd6..08fe761c21 100644 --- a/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs +++ b/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs @@ -45,6 +45,9 @@ public class ExtractionHoldout : IPluginDataFlowComponent, IPipelineR //todo want to be able to override or append to the output file + [DemandsInitialization("Overrides any data in the holdout file with new data")] + public bool overrideFile { get; set; } + // We may want to automatically reimport into RDMP, but this is quite complicated. It may be worth having users reimport the catalogue themself until it is proven that this is worth building. //Currently only support writting holdback data to a CSV @@ -148,7 +151,20 @@ private void writeDataTabletoCSV(DataTable dt) string filename = Request.ToString(); holdoutStorageLocation.TrimEnd('/'); holdoutStorageLocation.TrimEnd('\\'); - File.WriteAllText($"{holdoutStorageLocation}/holdout_{filename}.csv", sb.ToString()); + string path = $"{holdoutStorageLocation}/holdout_{filename}.csv"; + if (File.Exists(path)) + { + if(!overrideFile) + { + using(StreamWriter sw = File.AppendText(path)) + { + sw.WriteLine(sb.ToString()); + } + return; + } + } + + File.WriteAllText(path, sb.ToString()); } public DataTable ProcessPipelineData(DataTable toProcess, IDataLoadEventListener listener, GracefulCancellationToken cancellationToken) From dfc4716bf1d310b0e18edadb4c2d39757cf973fd Mon Sep 17 00:00:00 2001 From: James Friel Date: Thu, 12 Oct 2023 11:22:40 +0100 Subject: [PATCH 14/26] add notes --- Rdmp.Core/MapsDirectlyToDatabaseTable/TableRepository.cs | 2 +- Rdmp.UI/AggregationUIs/Advanced/AggregateTopXUI.cs | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/Rdmp.Core/MapsDirectlyToDatabaseTable/TableRepository.cs b/Rdmp.Core/MapsDirectlyToDatabaseTable/TableRepository.cs index 5bf06aa420..42c37c03e6 100644 --- a/Rdmp.Core/MapsDirectlyToDatabaseTable/TableRepository.cs +++ b/Rdmp.Core/MapsDirectlyToDatabaseTable/TableRepository.cs @@ -174,7 +174,7 @@ protected static void PopulateUpdateCommandValuesWithCurrentState(DbCommand cmd, //if it is a complex type but IConvertible e.g. CatalogueFolder if (!prop.PropertyType.IsValueType && propValue is IConvertible c && c.GetTypeCode() == TypeCode.String) propValue = c.ToString(CultureInfo.CurrentCulture); - + //need to add a dimention first SetParameterToValue(p, propValue); } diff --git a/Rdmp.UI/AggregationUIs/Advanced/AggregateTopXUI.cs b/Rdmp.UI/AggregationUIs/Advanced/AggregateTopXUI.cs index a301d4fcac..f1108c1ab5 100644 --- a/Rdmp.UI/AggregationUIs/Advanced/AggregateTopXUI.cs +++ b/Rdmp.UI/AggregationUIs/Advanced/AggregateTopXUI.cs @@ -6,6 +6,7 @@ using System; using System.Drawing; +using Rdmp.Core.CommandExecution; using Rdmp.Core.Curation.Data; using Rdmp.Core.Curation.Data.Aggregation; using Rdmp.Core.QueryBuilding.Options; @@ -28,6 +29,7 @@ public partial class AggregateTopXUI : RDMPUserControl private AggregateConfiguration _aggregate; private const string CountColumn = "Count Column"; + private const string RandomOrder = "Random"; public AggregateTopXUI() { @@ -63,6 +65,7 @@ private void RefreshUIFromDatabase() bLoading = true; ddOrderByDimension.Items.Clear(); ddOrderByDimension.Items.Add(CountColumn); + ddOrderByDimension.Items.Add(RandomOrder); ddOrderByDimension.Items.AddRange(_aggregate.AggregateDimensions); if (_topX != null) @@ -143,6 +146,12 @@ private void ddOrderByDimension_SelectedIndexChanged(object sender, EventArgs e) if (ddOrderByDimension.SelectedItem is AggregateDimension dimension) _topX.OrderByDimensionIfAny_ID = dimension.ID; + //ddOrderByDimension.SelectedItem.GetType().Name is "String" and + else if (ddOrderByDimension.SelectedItem.ToString() is RandomOrder) + { + //_topX.OrderByDimensionIfAny_ID = x.ID; //means use random + //todo need to add random to the query + } else _topX.OrderByDimensionIfAny_ID = null; //means use count column From fcff80d3d8cbf84f913b0be8e1d213a9295fac9e Mon Sep 17 00:00:00 2001 From: James Friel Date: Fri, 13 Oct 2023 09:13:30 +0100 Subject: [PATCH 15/26] tidy up exctraction holdout --- .../Pipeline/ExtractionHoldout.cs | 72 +++++++++++++------ 1 file changed, 51 insertions(+), 21 deletions(-) diff --git a/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs b/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs index 08fe761c21..fa4467d089 100644 --- a/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs +++ b/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs @@ -43,16 +43,18 @@ public class ExtractionHoldout : IPluginDataFlowComponent, IPipelineR [DemandsInitialization("Allows for the filtering of what data can be used as holdout data. The filter only currently supports filtering on string columns, not dates. Filter References https://learn.microsoft.com/en-us/dotnet/api/system.data.dataview.rowfilter?view=net-7.0 and https://learn.microsoft.com/en-us/dotnet/api/system.data.datacolumn.expression?view=net-7.0")] public string whereCondition { get; set; } - //todo want to be able to override or append to the output file - [DemandsInitialization("Overrides any data in the holdout file with new data")] public bool overrideFile { get; set; } - // We may want to automatically reimport into RDMP, but this is quite complicated. It may be worth having users reimport the catalogue themself until it is proven that this is worth building. + // We may want to automatically reimport into RDMP, but this is quite complicated. + // It may be worth having users reimport the catalogue themself until it is proven that this is worth building. //Currently only support writting holdback data to a CSV + private string holdoutColumnName = "_isValidHoldout"; + + public IExtractDatasetCommand Request { get; private set; } @@ -61,7 +63,16 @@ private bool validateIfRowShouldBeFiltered(DataRow row,DataTable toProcess) if (!string.IsNullOrWhiteSpace(dateColumn)) { //had a data column - DateTime dateCell = DateTime.Parse(row.Field(dateColumn), CultureInfo.InvariantCulture); + DateTime dateCell; + try + { + dateCell = row.Field(dateColumn); + } + catch (Exception) + { + dateCell = DateTime.Parse(row.Field(dateColumn), CultureInfo.InvariantCulture); + } + if (afterDate != DateTime.MinValue) { //has date @@ -96,17 +107,17 @@ private bool validateIfRowShouldBeFiltered(DataRow row,DataTable toProcess) private void filterRowsBasedOnHoldoutDates(DataTable toProcess) { - toProcess.Columns.Add("_isValidHoldout", typeof(bool)); - foreach(DataRow row in toProcess.Rows) + toProcess.Columns.Add(holdoutColumnName, typeof(bool)); + foreach (DataRow row in toProcess.Rows) { - row["_isValidHoldout"] = validateIfRowShouldBeFiltered(row,toProcess); + row[holdoutColumnName] = validateIfRowShouldBeFiltered(row, toProcess); } } private int getHoldoutRowCount(DataTable toProcess, IDataLoadEventListener listener) { - int rowCount = holdoutCount; + float rowCount = (float)holdoutCount; if (rowCount >= toProcess.Rows.Count && !isPercentage) { listener.OnNotify(this, new NotifyEventArgs(ProgressEventType.Warning, "More holdout data was requested than there is available data. All valid data will be held back")); @@ -119,29 +130,28 @@ private int getHoldoutRowCount(DataTable toProcess, IDataLoadEventListener liste listener.OnNotify(this, new NotifyEventArgs(ProgressEventType.Warning, "Holdout percentage was >100%. Will use 100%")); holdoutCount = 100; } - rowCount = toProcess.Rows.Count / 100 * holdoutCount; + rowCount = (float)toProcess.Rows.Count / 100 * holdoutCount; } - return rowCount; + return (int)Math.Ceiling(rowCount); } public void PreInitialize(IExtractCommand request, IDataLoadEventListener listener) { Request = request as IExtractDatasetCommand; - // We only care about dataset extraction requests if (Request == null) return; - //tod need to do a bunch of error handling - } private void writeDataTabletoCSV(DataTable dt) { - StringBuilder sb = new StringBuilder(); + StringBuilder sb = new(); - IEnumerable columnNames = dt.Columns.Cast(). - Select(column => column.ColumnName); - sb.AppendLine(string.Join(",", columnNames)); + IEnumerable columnNames = dt.Columns.Cast().Select(column => column.ColumnName); + if (overrideFile) + { + sb.AppendLine(string.Join(",", columnNames)); + } foreach (DataRow row in dt.Rows) { @@ -173,9 +183,11 @@ public DataTable ProcessPipelineData(DataTable toProcess, IDataLoadEventListener { return toProcess; } + bool toProcessDTModified = false; if (dateColumn is not null && (afterDate != DateTime.MinValue || beforeDate != DateTime.MinValue)) { filterRowsBasedOnHoldoutDates(toProcess); + toProcessDTModified = true; } DataTable holdoutData = toProcess.Clone(); @@ -184,7 +196,11 @@ public DataTable ProcessPipelineData(DataTable toProcess, IDataLoadEventListener holdoutData.BeginLoadData(); toProcess.BeginLoadData(); - var rowsToMove = toProcess.AsEnumerable().Where(row => row["_isValidHoldout"] is true).OrderBy(r => rand.Next()).Take(holdoutCount); + var rowsToMove = toProcess.AsEnumerable().Where(row => row[holdoutColumnName] is true).OrderBy(r => rand.Next()).Take(holdoutCount); + if(rowsToMove.Count() <1) { + listener.OnNotify(this, new NotifyEventArgs(ProgressEventType.Warning, "No valid holdout rows were found. Please check your settings.")); + + } foreach (DataRow row in rowsToMove) { holdoutData.ImportRow(row); @@ -194,16 +210,30 @@ public DataTable ProcessPipelineData(DataTable toProcess, IDataLoadEventListener toProcess.EndLoadData(); if (holdoutStorageLocation is not null && holdoutStorageLocation.Length > 0) { - holdoutData.Columns.Remove("_isValidHoldout"); + holdoutData.Columns.Remove(holdoutColumnName); writeDataTabletoCSV(holdoutData); } - toProcess.Columns.Remove("_isValidHoldout"); + if (toProcessDTModified) + { + toProcess.Columns.Remove(holdoutColumnName); + } return toProcess; } public void Check(ICheckNotifier notifier) { - //todo + if(Request is null) + { + return; + } + if (string.IsNullOrWhiteSpace(holdoutStorageLocation)) + { + notifier.OnCheckPerformed(new CheckEventArgs($"No holdout file location set.", CheckResult.Fail)); + } + if (holdoutCount is 0) + { + notifier.OnCheckPerformed(new CheckEventArgs($"No data holdout count set. This will result in no holdout data being generated.", CheckResult.Warning)); + } } public void Abort(IDataLoadEventListener listener) { From e4a62f1d5a87775bd3c83fbde76a01ea3a7a9170 Mon Sep 17 00:00:00 2001 From: James Friel Date: Fri, 13 Oct 2023 10:30:37 +0100 Subject: [PATCH 16/26] add extraction tests --- .../DataExtraction/ExtractionHoldoutTests.cs | 303 ++++++++++++++++++ .../Pipeline/ExtractionHoldout.cs | 36 ++- 2 files changed, 323 insertions(+), 16 deletions(-) create mode 100644 Rdmp.Core.Tests/DataExport/DataExtraction/ExtractionHoldoutTests.cs diff --git a/Rdmp.Core.Tests/DataExport/DataExtraction/ExtractionHoldoutTests.cs b/Rdmp.Core.Tests/DataExport/DataExtraction/ExtractionHoldoutTests.cs new file mode 100644 index 0000000000..60a080a385 --- /dev/null +++ b/Rdmp.Core.Tests/DataExport/DataExtraction/ExtractionHoldoutTests.cs @@ -0,0 +1,303 @@ +// Copyright (c) The University of Dundee 2018-2023 +// This file is part of the Research Data Management Platform (RDMP). +// RDMP is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +// RDMP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +// You should have received a copy of the GNU General Public License along with RDMP. If not, see . + +using NUnit.Framework; +using Rdmp.Core.DataExport.DataExtraction.Pipeline; +using Rdmp.Core.DataFlowPipeline; +using System; +using System.IO; +using System.Linq; +using Rdmp.Core.ReusableLibraryCode.Checks; +using Rdmp.Core.ReusableLibraryCode.Progress; +using System.Data; +using NUnit.Framework.Internal; +using Rdmp.Core.CohortCommitting.Pipeline; +using Rdmp.Core.DataExport.Data; +using Rdmp.Core.Repositories; +using Terminal.Gui; +using Rdmp.Core.DataExport.DataExtraction.Commands; +using Rdmp.Core.DataExport.DataExtraction.UserPicks; +using Tests.Common.Scenarios; + +namespace Rdmp.Core.Tests.DataExport.DataExtraction; + +internal class ExtractionHoldoutTests: TestsRequiringAnExtractionConfiguration +{ + private SimpleFileExtractor _extractor; + private DirectoryInfo _inDir; + private DirectoryInfo _outDir; + private DirectoryInfo _inDirSub1; + private DirectoryInfo _inDirSub2; + + private ExtractionHoldout _holdout; + private DirectoryInfo _holdoutDir; + private DataTable _toProcess; + private IExtractCommand _ExtractionStub; + + [SetUp] + protected override void SetUp() + { + base.SetUp(); + _holdout = new ExtractionHoldout(); + _holdoutDir = new DirectoryInfo(Path.Combine(TestContext.CurrentContext.WorkDirectory, "Holdout")); + Console.WriteLine(Path.Combine(TestContext.CurrentContext.WorkDirectory)); + if(_holdoutDir.Exists) + { + _holdoutDir.Delete(true); + } + _holdoutDir.Create(); + _toProcess = new DataTable(); + _toProcess.Columns.Add("FAKE_CHI"); + _toProcess.Rows.Add(1); + _toProcess.Rows.Add(2); + _toProcess.Rows.Add(3); + _toProcess.Rows.Add(4); + _toProcess.Rows.Add(5); + _toProcess.Rows.Add(6); + } + + [Test] + public void NoConfiguration() + { + var ex = Assert.Throws(() => _holdout.Check(ThrowImmediatelyCheckNotifier.Quiet)); + Assert.IsTrue(ex.Message.Contains("No holdout file location set.")); + } + + [Test] + public void LocationSet() + { + _holdout.holdoutStorageLocation = _holdoutDir.FullName; + var ex = Assert.Throws(() => _holdout.Check(ThrowImmediatelyCheckNotifier.Quiet)); + Assert.IsTrue(ex.Message.Contains("No data holdout count set.")); + } + + [Test] + public void LocationAndCountSet() + { + _holdout.holdoutStorageLocation = _holdoutDir.FullName; + _holdout.holdoutCount = 1; + _holdout.Check(ThrowImmediatelyCheckNotifier.Quiet); + Console.WriteLine(_holdoutDir.FullName); + FileAssert.DoesNotExist(Path.Combine(_holdoutDir.FullName, "holdout_TestTable.csv"));//todo use the correct name + } + + [Test] + public void ExtractionHoldoutSingle() + { + _holdout.holdoutStorageLocation = _holdoutDir.FullName; + _holdout.holdoutCount = 1; + _ExtractionStub = new ExtractDatasetCommand(_configuration, new ExtractableDatasetBundle(_extractableDataSet)); + _holdout.PreInitialize(_ExtractionStub, ThrowImmediatelyDataLoadEventListener.Quiet); + _holdout.ProcessPipelineData(_toProcess, ThrowImmediatelyDataLoadEventListener.Quiet, new GracefulCancellationToken()); + _holdout.Dispose(ThrowImmediatelyDataLoadEventListener.Quiet, null); + Assert.IsTrue(File.Exists(Path.Combine(_holdoutDir.FullName, "holdout_TestTable.csv"))); + String expectedOutput = File.ReadAllText(Path.Combine(_holdoutDir.FullName, "holdout_TestTable.csv")); + Assert.That(expectedOutput, Does.Match("FAKE_CHI\r\n[1-6]\r\n")); + + } + + [Test] + public void ExtractionHoldoutPercentage() + { + _holdout.holdoutStorageLocation = _holdoutDir.FullName; + _holdout.holdoutCount = 33; + _holdout.isPercentage = true; + _ExtractionStub = new ExtractDatasetCommand(_configuration, new ExtractableDatasetBundle(_extractableDataSet)); + _holdout.PreInitialize(_ExtractionStub, ThrowImmediatelyDataLoadEventListener.Quiet); + _holdout.ProcessPipelineData(_toProcess, ThrowImmediatelyDataLoadEventListener.Quiet, new GracefulCancellationToken()); + _holdout.Dispose(ThrowImmediatelyDataLoadEventListener.Quiet, null); + Assert.IsTrue(File.Exists(Path.Combine(_holdoutDir.FullName, "holdout_TestTable.csv"))); + String expectedOutput = File.ReadAllText(Path.Combine(_holdoutDir.FullName, "holdout_TestTable.csv")); + Assert.That(expectedOutput, Does.Match("FAKE_CHI\r\n[1-6]\r\n[1-6]\r\n")); + + } + [Test] + public void ExtractionHoldoutPercentageAppend() + { + _holdout.holdoutStorageLocation = _holdoutDir.FullName; + _holdout.holdoutCount = 33; + _holdout.isPercentage = true; + _holdout.overrideFile = false; + _ExtractionStub = new ExtractDatasetCommand(_configuration, new ExtractableDatasetBundle(_extractableDataSet)); + _holdout.PreInitialize(_ExtractionStub, ThrowImmediatelyDataLoadEventListener.Quiet); + _holdout.ProcessPipelineData(_toProcess, ThrowImmediatelyDataLoadEventListener.Quiet, new GracefulCancellationToken()); + _holdout.Dispose(ThrowImmediatelyDataLoadEventListener.Quiet, null); + Assert.IsTrue(File.Exists(Path.Combine(_holdoutDir.FullName, "holdout_TestTable.csv"))); + String expectedOutput = File.ReadAllText(Path.Combine(_holdoutDir.FullName, "holdout_TestTable.csv")); + Assert.That(expectedOutput, Does.Match("FAKE_CHI\r\n[1-6]\r\n[1-6]\r\n")); + + _holdout.PreInitialize(_ExtractionStub, ThrowImmediatelyDataLoadEventListener.Quiet); + _holdout.ProcessPipelineData(_toProcess, ThrowImmediatelyDataLoadEventListener.Quiet, new GracefulCancellationToken()); + _holdout.Dispose(ThrowImmediatelyDataLoadEventListener.Quiet, null); + Assert.IsTrue(File.Exists(Path.Combine(_holdoutDir.FullName, "holdout_TestTable.csv"))); + expectedOutput = File.ReadAllText(Path.Combine(_holdoutDir.FullName, "holdout_TestTable.csv")); + Assert.That(expectedOutput, Does.Match("FAKE_CHI\r\n[1-6]\r\n[1-6]\r\n[1-6]\r\n[1-6]\r\n")); + + } + + //[Test] + //public void OneFile() + //{ + // _extractor.Directories = false; + // _extractor.Pattern = "blah.*"; + // _extractor.OutputDirectoryName = _outDir.FullName; + // _extractor.Check(ThrowImmediatelyCheckNotifier.Quiet); + + // FileAssert.DoesNotExist(Path.Combine(_outDir.FullName, "blah.txt")); + // FileAssert.DoesNotExist(Path.Combine(_outDir.FullName, "blah2.txt")); + + // _extractor.MoveAll(_outDir, ThrowImmediatelyDataLoadEventListener.Quiet, new GracefulCancellationToken()); + + // FileAssert.Exists(Path.Combine(_outDir.FullName, "blah.txt")); + // FileAssert.DoesNotExist(Path.Combine(_outDir.FullName, "blah2.txt")); + // DirectoryAssert.DoesNotExist(Path.Combine(_outDir.FullName, "Sub1")); + // DirectoryAssert.DoesNotExist(Path.Combine(_outDir.FullName, "Sub2")); + //} + + //[Test] + //public void AllDirs() + //{ + // _extractor.Directories = true; + // _extractor.Pattern = "*"; + // _extractor.OutputDirectoryName = _outDir.FullName; + // _extractor.Check(ThrowImmediatelyCheckNotifier.Quiet); + + // FileAssert.DoesNotExist(Path.Combine(_outDir.FullName, "blah.txt")); + // FileAssert.DoesNotExist(Path.Combine(_outDir.FullName, "blah2.txt")); + + // _extractor.MoveAll(_outDir, ThrowImmediatelyDataLoadEventListener.Quiet, new GracefulCancellationToken()); + + // FileAssert.DoesNotExist(Path.Combine(_outDir.FullName, "blah.txt")); + // FileAssert.DoesNotExist(Path.Combine(_outDir.FullName, "blah2.txt")); + // DirectoryAssert.Exists(Path.Combine(_outDir.FullName, "Sub1")); + // DirectoryAssert.Exists(Path.Combine(_outDir.FullName, "Sub2")); + //} + + //[Test] + //public void OneDir() + //{ + // _extractor.Directories = true; + // _extractor.Pattern = "*1"; + // _extractor.OutputDirectoryName = _outDir.FullName; + // _extractor.Check(ThrowImmediatelyCheckNotifier.Quiet); + + // FileAssert.DoesNotExist(Path.Combine(_outDir.FullName, "blah.txt")); + // FileAssert.DoesNotExist(Path.Combine(_outDir.FullName, "blah2.txt")); + + // _extractor.MoveAll(_outDir, ThrowImmediatelyDataLoadEventListener.Quiet, new GracefulCancellationToken()); + + // FileAssert.DoesNotExist(Path.Combine(_outDir.FullName, "blah.txt")); + // FileAssert.DoesNotExist(Path.Combine(_outDir.FullName, "blah2.txt")); + // DirectoryAssert.Exists(Path.Combine(_outDir.FullName, "Sub1")); + // DirectoryAssert.DoesNotExist(Path.Combine(_outDir.FullName, "Sub2")); + //} + + //[Test] + //public void PatientFiles() + //{ + // _extractor.PerPatient = true; + // _extractor.Directories = false; + // _extractor.Pattern = "$p.txt"; + // _extractor.OutputDirectoryName = _outDir.FullName; + // _extractor.Check(ThrowImmediatelyCheckNotifier.Quiet); + + // FileAssert.DoesNotExist(Path.Combine(_outDir.FullName, "blah.txt")); + // FileAssert.DoesNotExist(Path.Combine(_outDir.FullName, "blah2.txt")); + + // _extractor.MovePatient("Pat1", "Rel1", _outDir, ThrowImmediatelyDataLoadEventListener.QuietPicky, + // new GracefulCancellationToken()); + + // FileAssert.DoesNotExist(Path.Combine(_outDir.FullName, "blah.txt")); + // FileAssert.DoesNotExist(Path.Combine(_outDir.FullName, "blah2.txt")); + // FileAssert.Exists(Path.Combine(_outDir.FullName, "Rel1.txt")); + //} + + //[Test] + //public void PatientFileMissingOne() + //{ + // _extractor.PerPatient = true; + // _extractor.Directories = false; + // _extractor.Pattern = "$p.txt"; + // _extractor.OutputDirectoryName = _outDir.FullName; + // _extractor.Check(ThrowImmediatelyCheckNotifier.Quiet); + + // FileAssert.DoesNotExist(Path.Combine(_outDir.FullName, "blah.txt")); + // FileAssert.DoesNotExist(Path.Combine(_outDir.FullName, "blah2.txt")); + + // var mem = new ToMemoryDataLoadEventListener(true); + + // _extractor.MovePatient("Pat1", "Rel1", _outDir, mem, new GracefulCancellationToken()); + // _extractor.MovePatient("Pat2", "Rel2", _outDir, mem, new GracefulCancellationToken()); + + // FileAssert.DoesNotExist(Path.Combine(_outDir.FullName, "blah.txt")); + // FileAssert.DoesNotExist(Path.Combine(_outDir.FullName, "blah2.txt")); + // FileAssert.Exists(Path.Combine(_outDir.FullName, "Rel1.txt")); + + // Assert.AreEqual(ProgressEventType.Warning, mem.GetWorst()); + + // StringAssert.StartsWith("No Files were found matching Pattern Pat2.txt in ", + // mem.GetAllMessagesByProgressEventType()[ProgressEventType.Warning].Single().Message); + //} + + //[Test] + //public void PatientDirs() + //{ + // _extractor.PerPatient = true; + // _extractor.Directories = true; + // _extractor.Pattern = "$p"; + // _extractor.OutputDirectoryName = _outDir.FullName; + // _extractor.Check(ThrowImmediatelyCheckNotifier.Quiet); + + // FileAssert.DoesNotExist(Path.Combine(_outDir.FullName, "blah.txt")); + // FileAssert.DoesNotExist(Path.Combine(_outDir.FullName, "blah2.txt")); + + // _extractor.MovePatient("Sub1", "Rel1", _outDir, ThrowImmediatelyDataLoadEventListener.QuietPicky, + // new GracefulCancellationToken()); + + // FileAssert.DoesNotExist(Path.Combine(_outDir.FullName, "blah.txt")); + // FileAssert.DoesNotExist(Path.Combine(_outDir.FullName, "blah2.txt")); + // DirectoryAssert.Exists(Path.Combine(_outDir.FullName, "Rel1")); + // DirectoryAssert.DoesNotExist(Path.Combine(_outDir.FullName, "Rel2")); + // DirectoryAssert.DoesNotExist(Path.Combine(_outDir.FullName, "Sub1")); + // DirectoryAssert.DoesNotExist(Path.Combine(_outDir.FullName, "Sub2")); + //} + + //[Test] + //public void PatientBothDirs() + //{ + // _extractor.PerPatient = true; + // _extractor.Directories = true; + // _extractor.Pattern = "$p"; + // _extractor.OutputDirectoryName = _outDir.FullName; + // _extractor.Check(ThrowImmediatelyCheckNotifier.Quiet); + + // FileAssert.DoesNotExist(Path.Combine(_outDir.FullName, "blah.txt")); + // FileAssert.DoesNotExist(Path.Combine(_outDir.FullName, "blah2.txt")); + + // _extractor.MovePatient("Sub1", "Rel1", _outDir, ThrowImmediatelyDataLoadEventListener.QuietPicky, + // new GracefulCancellationToken()); + // _extractor.MovePatient("Sub2", "Rel2", _outDir, ThrowImmediatelyDataLoadEventListener.QuietPicky, + // new GracefulCancellationToken()); + + // // does not exist + // var ex = Assert.Throws(() => _extractor.MovePatient("Sub3", "Rel3", _outDir, + // ThrowImmediatelyDataLoadEventListener.QuietPicky, new GracefulCancellationToken())); + // Assert.AreEqual( + // $"No Directories were found matching Pattern Sub3 in {_inDir.FullName}. For private identifier 'Sub3'", + // ex.Message); + + // // if not throwing on warnings then a missing sub just passes through and is ignored + // _extractor.MovePatient("Sub3", "Rel3", _outDir, ThrowImmediatelyDataLoadEventListener.Quiet, + // new GracefulCancellationToken()); + + // FileAssert.DoesNotExist(Path.Combine(_outDir.FullName, "blah.txt")); + // FileAssert.DoesNotExist(Path.Combine(_outDir.FullName, "blah2.txt")); + // DirectoryAssert.Exists(Path.Combine(_outDir.FullName, "Rel1")); + // DirectoryAssert.Exists(Path.Combine(_outDir.FullName, "Rel2")); + // DirectoryAssert.DoesNotExist(Path.Combine(_outDir.FullName, "Sub1")); + // DirectoryAssert.DoesNotExist(Path.Combine(_outDir.FullName, "Sub2")); + //} +} \ No newline at end of file diff --git a/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs b/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs index fa4467d089..09f5d697a3 100644 --- a/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs +++ b/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs @@ -58,7 +58,7 @@ public class ExtractionHoldout : IPluginDataFlowComponent, IPipelineR public IExtractDatasetCommand Request { get; private set; } - private bool validateIfRowShouldBeFiltered(DataRow row,DataTable toProcess) + private bool validateIfRowShouldBeFiltered(DataRow row, DataTable toProcess) { if (!string.IsNullOrWhiteSpace(dateColumn)) { @@ -146,9 +146,10 @@ public void PreInitialize(IExtractCommand request, IDataLoadEventListener listen private void writeDataTabletoCSV(DataTable dt) { StringBuilder sb = new(); - + string filename = Request.ToString(); + string path = $"{holdoutStorageLocation}/holdout_{filename}.csv"; IEnumerable columnNames = dt.Columns.Cast().Select(column => column.ColumnName); - if (overrideFile) + if (overrideFile || !File.Exists(path)) { sb.AppendLine(string.Join(",", columnNames)); } @@ -158,15 +159,14 @@ private void writeDataTabletoCSV(DataTable dt) IEnumerable fields = row.ItemArray.Select(field => field.ToString()); sb.AppendLine(string.Join(",", fields)); } - string filename = Request.ToString(); holdoutStorageLocation.TrimEnd('/'); holdoutStorageLocation.TrimEnd('\\'); - string path = $"{holdoutStorageLocation}/holdout_{filename}.csv"; + if (File.Exists(path)) { - if(!overrideFile) + if (!overrideFile) { - using(StreamWriter sw = File.AppendText(path)) + using (StreamWriter sw = File.AppendText(path)) { sw.WriteLine(sb.ToString()); } @@ -186,6 +186,7 @@ public DataTable ProcessPipelineData(DataTable toProcess, IDataLoadEventListener bool toProcessDTModified = false; if (dateColumn is not null && (afterDate != DateTime.MinValue || beforeDate != DateTime.MinValue)) { + //we only want to check for valid rows if dates are set, otherwise all rows are valid filterRowsBasedOnHoldoutDates(toProcess); toProcessDTModified = true; } @@ -196,8 +197,9 @@ public DataTable ProcessPipelineData(DataTable toProcess, IDataLoadEventListener holdoutData.BeginLoadData(); toProcess.BeginLoadData(); - var rowsToMove = toProcess.AsEnumerable().Where(row => row[holdoutColumnName] is true).OrderBy(r => rand.Next()).Take(holdoutCount); - if(rowsToMove.Count() <1) { + var rowsToMove = toProcess.AsEnumerable().Where(row => !toProcessDTModified || row[holdoutColumnName] is true).OrderBy(r => rand.Next()).Take(holdoutCount); + if (rowsToMove.Count() < 1) + { listener.OnNotify(this, new NotifyEventArgs(ProgressEventType.Warning, "No valid holdout rows were found. Please check your settings.")); } @@ -208,31 +210,33 @@ public DataTable ProcessPipelineData(DataTable toProcess, IDataLoadEventListener } holdoutData.EndLoadData(); toProcess.EndLoadData(); + if (holdoutStorageLocation is not null && holdoutStorageLocation.Length > 0) { - holdoutData.Columns.Remove(holdoutColumnName); + if (toProcessDTModified) + { + holdoutData.Columns.Remove(holdoutColumnName); + } writeDataTabletoCSV(holdoutData); } if (toProcessDTModified) { toProcess.Columns.Remove(holdoutColumnName); + } + return toProcess; } public void Check(ICheckNotifier notifier) { - if(Request is null) - { - return; - } if (string.IsNullOrWhiteSpace(holdoutStorageLocation)) { notifier.OnCheckPerformed(new CheckEventArgs($"No holdout file location set.", CheckResult.Fail)); } - if (holdoutCount is 0) + if (holdoutCount is 0) { - notifier.OnCheckPerformed(new CheckEventArgs($"No data holdout count set. This will result in no holdout data being generated.", CheckResult.Warning)); + notifier.OnCheckPerformed(new CheckEventArgs($"No data holdout count set.", CheckResult.Fail)); } } public void Abort(IDataLoadEventListener listener) From 3e1e486a112a936bf784c3e639a31bde3c6e2bd0 Mon Sep 17 00:00:00 2001 From: James Friel Date: Fri, 13 Oct 2023 10:36:35 +0100 Subject: [PATCH 17/26] tidy up --- .../WindowManagement/ActivateItems.cs | 16 - .../DataExtraction/ExtractionHoldoutTests.cs | 162 --------- .../Pipeline/CohortHoldoutCreationRequest.cs | 271 -------------- .../Pipeline/ICohortHoldoutCreationRequest.cs | 32 -- .../CommandExecution/AtomicCommandFactory.cs | 2 +- .../CohortCreationCommandExecution.cs | 31 -- .../ExecuteCommandCreateHoldoutCohort.cs | 110 ------ .../CommandExecution/BasicActivateItems.cs | 29 -- .../CommandExecution/IBasicActivateItems.cs | 6 - .../TableRepository.cs | 2 +- .../Options/AggregateBuilderCohortOptions.cs | 2 +- .../Advanced/AggregateTopXUI.cs | 9 - ...CohortHoldoutCreationRequestUI.Designer.cs | 343 ------------------ .../CohortHoldoutCreationRequestUI.cs | 212 ----------- .../CohortHoldoutCreationRequestUI.resx | 120 ------ Tests.Common/TestDatabases.txt | 2 +- 16 files changed, 4 insertions(+), 1345 deletions(-) delete mode 100644 Rdmp.Core/CohortCommitting/Pipeline/CohortHoldoutCreationRequest.cs delete mode 100644 Rdmp.Core/CohortCommitting/Pipeline/ICohortHoldoutCreationRequest.cs delete mode 100644 Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandCreateHoldoutCohort.cs delete mode 100644 Rdmp.UI/CohortUI/CohortHoldout/CohortHoldoutCreationRequestUI.Designer.cs delete mode 100644 Rdmp.UI/CohortUI/CohortHoldout/CohortHoldoutCreationRequestUI.cs delete mode 100644 Rdmp.UI/CohortUI/CohortHoldout/CohortHoldoutCreationRequestUI.resx diff --git a/Application/ResearchDataManagementPlatform/WindowManagement/ActivateItems.cs b/Application/ResearchDataManagementPlatform/WindowManagement/ActivateItems.cs index fdfa457323..4ca7a92805 100644 --- a/Application/ResearchDataManagementPlatform/WindowManagement/ActivateItems.cs +++ b/Application/ResearchDataManagementPlatform/WindowManagement/ActivateItems.cs @@ -840,22 +840,6 @@ public override IPipelineRunner GetPipelineRunner(DialogArgs args, IPipelineUseC return configureAndExecuteDialog; } - public override CohortHoldoutCreationRequest GetCohortHoldoutCreationRequest(ExternalCohortTable externalCohortTable, IProject project, CohortIdentificationConfiguration cic) - { - // if on wrong Thread - if (_mainDockPanel?.InvokeRequired ?? false) - return _mainDockPanel.Invoke(() => - GetCohortHoldoutCreationRequest(externalCohortTable, project, cic)); - - var ui = new Rdmp.UI.CohortUI.CohortHoldout.CohortHoldoutCreationRequestUI(this, externalCohortTable, project,cic); - - if (!string.IsNullOrWhiteSpace(cic.Description)) - ui.CohortDescription = $"{cic.Description} ({Environment.UserName} - {DateTime.Now})"; - //todo the ui portion does not work currently - return ui.ShowDialog() == DialogResult.OK ? ui.Result : ui.Result; - } - - public override CohortCreationRequest GetCohortCreationRequest(ExternalCohortTable externalCohortTable, IProject project, string cohortInitialDescription) { diff --git a/Rdmp.Core.Tests/DataExport/DataExtraction/ExtractionHoldoutTests.cs b/Rdmp.Core.Tests/DataExport/DataExtraction/ExtractionHoldoutTests.cs index 60a080a385..2c2b6e463d 100644 --- a/Rdmp.Core.Tests/DataExport/DataExtraction/ExtractionHoldoutTests.cs +++ b/Rdmp.Core.Tests/DataExport/DataExtraction/ExtractionHoldoutTests.cs @@ -138,166 +138,4 @@ public void ExtractionHoldoutPercentageAppend() } - //[Test] - //public void OneFile() - //{ - // _extractor.Directories = false; - // _extractor.Pattern = "blah.*"; - // _extractor.OutputDirectoryName = _outDir.FullName; - // _extractor.Check(ThrowImmediatelyCheckNotifier.Quiet); - - // FileAssert.DoesNotExist(Path.Combine(_outDir.FullName, "blah.txt")); - // FileAssert.DoesNotExist(Path.Combine(_outDir.FullName, "blah2.txt")); - - // _extractor.MoveAll(_outDir, ThrowImmediatelyDataLoadEventListener.Quiet, new GracefulCancellationToken()); - - // FileAssert.Exists(Path.Combine(_outDir.FullName, "blah.txt")); - // FileAssert.DoesNotExist(Path.Combine(_outDir.FullName, "blah2.txt")); - // DirectoryAssert.DoesNotExist(Path.Combine(_outDir.FullName, "Sub1")); - // DirectoryAssert.DoesNotExist(Path.Combine(_outDir.FullName, "Sub2")); - //} - - //[Test] - //public void AllDirs() - //{ - // _extractor.Directories = true; - // _extractor.Pattern = "*"; - // _extractor.OutputDirectoryName = _outDir.FullName; - // _extractor.Check(ThrowImmediatelyCheckNotifier.Quiet); - - // FileAssert.DoesNotExist(Path.Combine(_outDir.FullName, "blah.txt")); - // FileAssert.DoesNotExist(Path.Combine(_outDir.FullName, "blah2.txt")); - - // _extractor.MoveAll(_outDir, ThrowImmediatelyDataLoadEventListener.Quiet, new GracefulCancellationToken()); - - // FileAssert.DoesNotExist(Path.Combine(_outDir.FullName, "blah.txt")); - // FileAssert.DoesNotExist(Path.Combine(_outDir.FullName, "blah2.txt")); - // DirectoryAssert.Exists(Path.Combine(_outDir.FullName, "Sub1")); - // DirectoryAssert.Exists(Path.Combine(_outDir.FullName, "Sub2")); - //} - - //[Test] - //public void OneDir() - //{ - // _extractor.Directories = true; - // _extractor.Pattern = "*1"; - // _extractor.OutputDirectoryName = _outDir.FullName; - // _extractor.Check(ThrowImmediatelyCheckNotifier.Quiet); - - // FileAssert.DoesNotExist(Path.Combine(_outDir.FullName, "blah.txt")); - // FileAssert.DoesNotExist(Path.Combine(_outDir.FullName, "blah2.txt")); - - // _extractor.MoveAll(_outDir, ThrowImmediatelyDataLoadEventListener.Quiet, new GracefulCancellationToken()); - - // FileAssert.DoesNotExist(Path.Combine(_outDir.FullName, "blah.txt")); - // FileAssert.DoesNotExist(Path.Combine(_outDir.FullName, "blah2.txt")); - // DirectoryAssert.Exists(Path.Combine(_outDir.FullName, "Sub1")); - // DirectoryAssert.DoesNotExist(Path.Combine(_outDir.FullName, "Sub2")); - //} - - //[Test] - //public void PatientFiles() - //{ - // _extractor.PerPatient = true; - // _extractor.Directories = false; - // _extractor.Pattern = "$p.txt"; - // _extractor.OutputDirectoryName = _outDir.FullName; - // _extractor.Check(ThrowImmediatelyCheckNotifier.Quiet); - - // FileAssert.DoesNotExist(Path.Combine(_outDir.FullName, "blah.txt")); - // FileAssert.DoesNotExist(Path.Combine(_outDir.FullName, "blah2.txt")); - - // _extractor.MovePatient("Pat1", "Rel1", _outDir, ThrowImmediatelyDataLoadEventListener.QuietPicky, - // new GracefulCancellationToken()); - - // FileAssert.DoesNotExist(Path.Combine(_outDir.FullName, "blah.txt")); - // FileAssert.DoesNotExist(Path.Combine(_outDir.FullName, "blah2.txt")); - // FileAssert.Exists(Path.Combine(_outDir.FullName, "Rel1.txt")); - //} - - //[Test] - //public void PatientFileMissingOne() - //{ - // _extractor.PerPatient = true; - // _extractor.Directories = false; - // _extractor.Pattern = "$p.txt"; - // _extractor.OutputDirectoryName = _outDir.FullName; - // _extractor.Check(ThrowImmediatelyCheckNotifier.Quiet); - - // FileAssert.DoesNotExist(Path.Combine(_outDir.FullName, "blah.txt")); - // FileAssert.DoesNotExist(Path.Combine(_outDir.FullName, "blah2.txt")); - - // var mem = new ToMemoryDataLoadEventListener(true); - - // _extractor.MovePatient("Pat1", "Rel1", _outDir, mem, new GracefulCancellationToken()); - // _extractor.MovePatient("Pat2", "Rel2", _outDir, mem, new GracefulCancellationToken()); - - // FileAssert.DoesNotExist(Path.Combine(_outDir.FullName, "blah.txt")); - // FileAssert.DoesNotExist(Path.Combine(_outDir.FullName, "blah2.txt")); - // FileAssert.Exists(Path.Combine(_outDir.FullName, "Rel1.txt")); - - // Assert.AreEqual(ProgressEventType.Warning, mem.GetWorst()); - - // StringAssert.StartsWith("No Files were found matching Pattern Pat2.txt in ", - // mem.GetAllMessagesByProgressEventType()[ProgressEventType.Warning].Single().Message); - //} - - //[Test] - //public void PatientDirs() - //{ - // _extractor.PerPatient = true; - // _extractor.Directories = true; - // _extractor.Pattern = "$p"; - // _extractor.OutputDirectoryName = _outDir.FullName; - // _extractor.Check(ThrowImmediatelyCheckNotifier.Quiet); - - // FileAssert.DoesNotExist(Path.Combine(_outDir.FullName, "blah.txt")); - // FileAssert.DoesNotExist(Path.Combine(_outDir.FullName, "blah2.txt")); - - // _extractor.MovePatient("Sub1", "Rel1", _outDir, ThrowImmediatelyDataLoadEventListener.QuietPicky, - // new GracefulCancellationToken()); - - // FileAssert.DoesNotExist(Path.Combine(_outDir.FullName, "blah.txt")); - // FileAssert.DoesNotExist(Path.Combine(_outDir.FullName, "blah2.txt")); - // DirectoryAssert.Exists(Path.Combine(_outDir.FullName, "Rel1")); - // DirectoryAssert.DoesNotExist(Path.Combine(_outDir.FullName, "Rel2")); - // DirectoryAssert.DoesNotExist(Path.Combine(_outDir.FullName, "Sub1")); - // DirectoryAssert.DoesNotExist(Path.Combine(_outDir.FullName, "Sub2")); - //} - - //[Test] - //public void PatientBothDirs() - //{ - // _extractor.PerPatient = true; - // _extractor.Directories = true; - // _extractor.Pattern = "$p"; - // _extractor.OutputDirectoryName = _outDir.FullName; - // _extractor.Check(ThrowImmediatelyCheckNotifier.Quiet); - - // FileAssert.DoesNotExist(Path.Combine(_outDir.FullName, "blah.txt")); - // FileAssert.DoesNotExist(Path.Combine(_outDir.FullName, "blah2.txt")); - - // _extractor.MovePatient("Sub1", "Rel1", _outDir, ThrowImmediatelyDataLoadEventListener.QuietPicky, - // new GracefulCancellationToken()); - // _extractor.MovePatient("Sub2", "Rel2", _outDir, ThrowImmediatelyDataLoadEventListener.QuietPicky, - // new GracefulCancellationToken()); - - // // does not exist - // var ex = Assert.Throws(() => _extractor.MovePatient("Sub3", "Rel3", _outDir, - // ThrowImmediatelyDataLoadEventListener.QuietPicky, new GracefulCancellationToken())); - // Assert.AreEqual( - // $"No Directories were found matching Pattern Sub3 in {_inDir.FullName}. For private identifier 'Sub3'", - // ex.Message); - - // // if not throwing on warnings then a missing sub just passes through and is ignored - // _extractor.MovePatient("Sub3", "Rel3", _outDir, ThrowImmediatelyDataLoadEventListener.Quiet, - // new GracefulCancellationToken()); - - // FileAssert.DoesNotExist(Path.Combine(_outDir.FullName, "blah.txt")); - // FileAssert.DoesNotExist(Path.Combine(_outDir.FullName, "blah2.txt")); - // DirectoryAssert.Exists(Path.Combine(_outDir.FullName, "Rel1")); - // DirectoryAssert.Exists(Path.Combine(_outDir.FullName, "Rel2")); - // DirectoryAssert.DoesNotExist(Path.Combine(_outDir.FullName, "Sub1")); - // DirectoryAssert.DoesNotExist(Path.Combine(_outDir.FullName, "Sub2")); - //} } \ No newline at end of file diff --git a/Rdmp.Core/CohortCommitting/Pipeline/CohortHoldoutCreationRequest.cs b/Rdmp.Core/CohortCommitting/Pipeline/CohortHoldoutCreationRequest.cs deleted file mode 100644 index ae5697832a..0000000000 --- a/Rdmp.Core/CohortCommitting/Pipeline/CohortHoldoutCreationRequest.cs +++ /dev/null @@ -1,271 +0,0 @@ -// Copyright (c) The University of Dundee 2018-2019 -// This file is part of the Research Data Management Platform (RDMP). -// RDMP is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. -// RDMP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. -// You should have received a copy of the GNU General Public License along with RDMP. If not, see . - -using System; -using System.Data; -using System.Linq; -using FAnsi.Connections; -using Rdmp.Core.Curation.Data; -using Rdmp.Core.Curation.Data.Cohort; -using Rdmp.Core.Curation.Data.Pipelines; -using Rdmp.Core.DataExport; -using Rdmp.Core.DataExport.Data; -using Rdmp.Core.DataFlowPipeline; -using Rdmp.Core.DataFlowPipeline.Requirements; -using Rdmp.Core.MapsDirectlyToDatabaseTable; -using Rdmp.Core.Repositories; -using Rdmp.Core.ReusableLibraryCode.Checks; - -namespace Rdmp.Core.CohortCommitting.Pipeline; - -/// -/// All metadata details nessesary to create a cohort including which project it goes into, its name, version etc. There are no identifiers for the cohort. -/// Also functions as the use case for cohort creation (to which it passes itself as an input object). -/// -public sealed class CohortHoldoutCreationRequest : PipelineUseCase, ICanBeSummarised,ICohortHoldoutCreationRequest -{ - //private readonly IDataExportRepository _repository; - - //for pipeline editing initialization when no known cohort is available - - #region Things that can be turned into cohorts - - private FlatFileToLoad _fileToLoad; - private ExtractionInformation _extractionIdentifierColumn; - private CohortIdentificationConfiguration _cohortIdentificationConfiguration; - - public FlatFileToLoad FileToLoad - { - get => _fileToLoad; - set - { - //remove old value if it had one - Pop(_fileToLoad); - - _fileToLoad = value; - - //add the new one - Push(value); - } - } - - public CohortIdentificationConfiguration CohortIdentificationConfiguration - { - get => _cohortIdentificationConfiguration; - set - { - Pop(_cohortIdentificationConfiguration); - _cohortIdentificationConfiguration = value; - Push(value); - } - } - - public ExtractionInformation ExtractionIdentifierColumn - { - get => _extractionIdentifierColumn; - set - { - Pop(_extractionIdentifierColumn); - _extractionIdentifierColumn = value; - Push(_extractionIdentifierColumn); - } - } - - private void Pop(object oldValue) - { - if (oldValue != null && InitializationObjects.Contains(oldValue)) - InitializationObjects.Remove(oldValue); - } - - private void Push(object newValue) - { - AddInitializationObject(newValue); - } - - #endregion - - public CohortIdentificationConfiguration CIC { get; set; } - public int Count { get; set; } - public bool IsPercent { get; set; } - - public string Name { get; set; } - - //public IProject Project { get; private set; } - //public ICohortDefinition NewCohortDefinition { get; set; } - - //public ExtractableCohort CohortCreatedIfAny { get; set; } - - public string DescriptionForAuditLog { get; set; } - - public CohortHoldoutCreationRequest(CohortIdentificationConfiguration cic, string name,int count, bool isPercent, string descriptionForAuditLog) - { - //_repository = repository; - //Project = project; - //NewCohortDefinition = newCohortDefinition; - CIC = cic; - Name = name; - Count = count; - IsPercent = isPercent; - DescriptionForAuditLog = descriptionForAuditLog; - - //AddInitializationObject(Project); - AddInitializationObject(this); - - GenerateContext(); - } - - /// - /// For refreshing the current extraction configuration CohortIdentificationConfiguration ONLY. The ExtractionConfiguration must have a cic and a refresh pipeline configured on it. - /// - /// - public CohortHoldoutCreationRequest(ExtractionConfiguration configuration) - { - //_repository = (IDataExportRepository)configuration.Repository; - - //if (configuration.CohortIdentificationConfiguration_ID == null) - // throw new NotSupportedException( - // $"Configuration '{configuration}' does not have an associated CohortIdentificationConfiguration for cohort refreshing"); - - //var origCohort = configuration.Cohort; - //var origCohortData = origCohort.GetExternalData(); - //CohortIdentificationConfiguration = configuration.CohortIdentificationConfiguration; - //Project = configuration.Project; - - //if (Project.ProjectNumber == null) - // throw new ProjectNumberException($"Project '{Project}' does not have a ProjectNumber"); - - //var definition = new CohortDefinition(null, origCohortData.ExternalDescription, - // origCohortData.ExternalVersion + 1, (int)Project.ProjectNumber, origCohort.ExternalCohortTable) - //{ - // CohortReplacedIfAny = origCohort - //}; - - //NewCohortDefinition = definition; - //DescriptionForAuditLog = "Cohort Refresh"; - - //AddInitializationObject(Project); - //AddInitializationObject(CohortIdentificationConfiguration); - //AddInitializationObject(FileToLoad); - //AddInitializationObject(ExtractionIdentifierColumn); - //AddInitializationObject(this); - - //GenerateContext(); - } - - protected override IDataFlowPipelineContext GenerateContextImpl() => - new DataFlowPipelineContext - { - MustHaveDestination = typeof(ICohortPipelineDestination), - MustHaveSource = typeof(IDataFlowSource) - }; - - - public void Check(ICheckNotifier notifier) - { - //NewCohortDefinition.LocationOfCohort.Check(notifier); - - //if (NewCohortDefinition.ID != null) - // notifier.OnCheckPerformed( - // new CheckEventArgs( - // $"Expected the cohort definition {NewCohortDefinition} to have a null ID - we are trying to create this, why would it already exist?", - // CheckResult.Fail)); - //else - // notifier.OnCheckPerformed(new CheckEventArgs("Confirmed that cohort ID is null", CheckResult.Success)); - - //if (Project.ProjectNumber == null) - // notifier.OnCheckPerformed(new CheckEventArgs( - // $"Project {Project} does not have a ProjectNumber specified, it should have the same number as the CohortHoldoutCreationRequest ({NewCohortDefinition.ProjectNumber})", - // CheckResult.Fail)); - //else if (Project.ProjectNumber != NewCohortDefinition.ProjectNumber) - // notifier.OnCheckPerformed( - // new CheckEventArgs( - // $"Project {Project} has ProjectNumber={Project.ProjectNumber} but the CohortHoldoutCreationRequest.ProjectNumber is {NewCohortDefinition.ProjectNumber}", - // CheckResult.Fail)); - - - //if (!NewCohortDefinition.IsAcceptableAsNewCohort(out var matchDescription)) - // notifier.OnCheckPerformed(new CheckEventArgs($"Cohort failed novelness check:{matchDescription}", - // CheckResult.Fail)); - //else - // notifier.OnCheckPerformed(new CheckEventArgs( - // $"Confirmed that cohort {NewCohortDefinition} does not already exist", - // CheckResult.Success)); - - //if (string.IsNullOrWhiteSpace(DescriptionForAuditLog)) - // notifier.OnCheckPerformed( - // new CheckEventArgs("User did not provide a description of the cohort for the AuditLog", - // CheckResult.Fail)); - } - - //public void PushToServer(IManagedConnection connection) - //{ - // if (!NewCohortDefinition.IsAcceptableAsNewCohort(out var reason)) - // throw new Exception(reason); - - // NewCohortDefinition.LocationOfCohort.PushToServer(NewCohortDefinition, connection); - //} - - //public int ImportAsExtractableCohort(bool deprecateOldCohortOnSuccess, bool migrateUsages) - //{ - // if (NewCohortDefinition.ID == null) - // throw new NotSupportedException( - // "CohortHoldoutCreationRequest cannot be imported because its ID is null, it is likely that it has not been pushed to the server yet"); - - // var cohort = new ExtractableCohort(_repository, (ExternalCohortTable)NewCohortDefinition.LocationOfCohort, - // (int)NewCohortDefinition.ID); - // cohort.AppendToAuditLog(DescriptionForAuditLog); - - // CohortCreatedIfAny = cohort; - - // if (deprecateOldCohortOnSuccess && NewCohortDefinition.CohortReplacedIfAny != null) - // { - // NewCohortDefinition.CohortReplacedIfAny.IsDeprecated = true; - // NewCohortDefinition.CohortReplacedIfAny.SaveToDatabase(); - // } - - // if (migrateUsages && NewCohortDefinition.CohortReplacedIfAny != null) - // { - // var oldId = NewCohortDefinition.CohortReplacedIfAny.ID; - // var newId = cohort.ID; - - // // ExtractionConfigurations that use the old (replaced) cohort - // var liveUsers = _repository.GetAllObjects() - // .Where(ec => ec.Cohort_ID == oldId && ec.IsReleased == false); - - // foreach (var ec in liveUsers) - // { - // ec.Cohort_ID = newId; - // ec.SaveToDatabase(); - // } - // } - - // return cohort.ID; - //} - - - /// - /// Design time types - /// - private CohortHoldoutCreationRequest() : base(new Type[] - { - typeof(FlatFileToLoad), - typeof(CohortIdentificationConfiguration), - typeof(Project), - typeof(ExtractionInformation), - typeof(ICohortCreationRequest) - }) - { - GenerateContext(); - } - - public static PipelineUseCase DesignTime() => new CohortHoldoutCreationRequest(); - - public override string ToString() => ""; - //NewCohortDefinition == null ? base.ToString() : NewCohortDefinition.Description; - - public string GetSummary(bool includeName, bool includeId) => ""; - //$"External Cohort Table: {NewCohortDefinition?.LocationOfCohort}"; -} \ No newline at end of file diff --git a/Rdmp.Core/CohortCommitting/Pipeline/ICohortHoldoutCreationRequest.cs b/Rdmp.Core/CohortCommitting/Pipeline/ICohortHoldoutCreationRequest.cs deleted file mode 100644 index 8683b5f0d4..0000000000 --- a/Rdmp.Core/CohortCommitting/Pipeline/ICohortHoldoutCreationRequest.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) The University of Dundee 2018-2019 -// This file is part of the Research Data Management Platform (RDMP). -// RDMP is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. -// RDMP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. -// You should have received a copy of the GNU General Public License along with RDMP. If not, see . - -using FAnsi.Connections; -using Rdmp.Core.Curation.Data; -using Rdmp.Core.Curation.Data.Cohort; -using Rdmp.Core.Curation.Data.Pipelines; -using Rdmp.Core.DataExport.Data; -using Rdmp.Core.DataFlowPipeline.Requirements; -using Rdmp.Core.ReusableLibraryCode.Checks; - -namespace Rdmp.Core.CohortCommitting.Pipeline; - -/// -/// See CohortCreationRequest -/// -public interface ICohortHoldoutCreationRequest : ICheckable, IHasDesignTimeMode, IPipelineUseCase -{ - // IProject Project { get; } - // ICohortDefinition NewCohortDefinition { get; set; } - // ExtractionInformation ExtractionIdentifierColumn { get; set; } - // CohortIdentificationConfiguration CohortIdentificationConfiguration { get; set; } - // ExtractableCohort CohortCreatedIfAny { get; } - // FlatFileToLoad FileToLoad { get; set; } - - // int ImportAsExtractableCohort(bool deprecateOldCohortOnSuccess, bool migrateUsages); - // void PushToServer(IManagedConnection transaction); - -} \ No newline at end of file diff --git a/Rdmp.Core/CommandExecution/AtomicCommandFactory.cs b/Rdmp.Core/CommandExecution/AtomicCommandFactory.cs index 4984a63bc4..c68e3c7b35 100644 --- a/Rdmp.Core/CommandExecution/AtomicCommandFactory.cs +++ b/Rdmp.Core/CommandExecution/AtomicCommandFactory.cs @@ -469,7 +469,7 @@ public IEnumerable CreateCommands(object o) yield return new ExecuteCommandViewData(_activator, cic, ViewType.All, null, true) { Weight = -99.7f }; yield return new ExecuteCommandViewData(_activator, cic, ViewType.All, null, false) { Weight = -99.6f }; - yield return new ExecuteCommandCreateHoldoutCohort(_activator,cic) { Weight = -99.5f }; + yield return new ExecuteCommandFreezeCohortIdentificationConfiguration(_activator, cic, !cic.Frozen) { Weight = -50.5f }; diff --git a/Rdmp.Core/CommandExecution/AtomicCommands/CohortCreationCommands/CohortCreationCommandExecution.cs b/Rdmp.Core/CommandExecution/AtomicCommands/CohortCreationCommands/CohortCreationCommandExecution.cs index 6f1dd7e0ac..9ac20303ae 100644 --- a/Rdmp.Core/CommandExecution/AtomicCommands/CohortCreationCommands/CohortCreationCommandExecution.cs +++ b/Rdmp.Core/CommandExecution/AtomicCommands/CohortCreationCommands/CohortCreationCommandExecution.cs @@ -4,12 +4,10 @@ // RDMP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. // You should have received a copy of the GNU General Public License along with RDMP. If not, see . -using System; using System.Linq; using Rdmp.Core.CohortCommitting.Pipeline; using Rdmp.Core.CommandLine.Runners; using Rdmp.Core.Curation.Data; -using Rdmp.Core.Curation.Data.Cohort; using Rdmp.Core.Curation.Data.Defaults; using Rdmp.Core.Curation.Data.Pipelines; using Rdmp.Core.DataExport.Data; @@ -70,35 +68,6 @@ protected CohortCreationCommandExecution(IBasicActivateItems activator, External SetImpossible("There are no cohort sources configured, you must create one in the Saved Cohort tabs"); } - protected CohortHoldoutCreationRequest GetCohortHoldoutCreationRequest(CohortIdentificationConfiguration cic) - { - //user wants to create a new cohort - var ect = ExternalCohortTable; - - //do we know where it's going to end up? - if (ect == null) - if (!SelectOne( - GetChooseCohortDialogArgs(), - BasicActivator.RepositoryLocator.DataExportRepository, - out ect)) //not yet, get user to pick one - return null; //user didn't select one and cancelled dialog - - - //and document the request - - // if we have everything we need to create the cohort right here - //if (!string.IsNullOrWhiteSpace(_explicitCohortName) && Project?.ProjectNumber != null) - //return GenerateCohortCreationRequestFromNameAndProject(_explicitCohortName, auditLogDescription, ect); - // otherwise we are going to have to ask the user for it - - //Get a new request for the source they are trying to populate - var req = BasicActivator.GetCohortHoldoutCreationRequest(ect, Project, cic); - - //Project ??= req?.Project; - - return req; - } - protected ICohortCreationRequest GetCohortCreationRequest(string auditLogDescription) { //user wants to create a new cohort diff --git a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandCreateHoldoutCohort.cs b/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandCreateHoldoutCohort.cs deleted file mode 100644 index 09e8529746..0000000000 --- a/Rdmp.Core/CommandExecution/AtomicCommands/ExecuteCommandCreateHoldoutCohort.cs +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright (c) The University of Dundee 2018-2023 -// This file is part of the Research Data Management Platform (RDMP). -// RDMP is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. -// RDMP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. -// You should have received a copy of the GNU General Public License along with RDMP. If not, see . - -using NPOI.OpenXmlFormats.Dml.Diagram; -using NPOI.XWPF.UserModel; -using Rdmp.Core.CohortCommitting.Pipeline; -using Rdmp.Core.Curation.Data; -using Rdmp.Core.Curation.Data.Aggregation; -using Rdmp.Core.Curation.Data.Cohort; -using Rdmp.Core.DataExport.Data; -using Rdmp.Core.Icons.IconProvision; -using Rdmp.Core.Repositories; -using Rdmp.Core.ReusableLibraryCode.Checks; -using Rdmp.Core.ReusableLibraryCode.Icons.IconProvision; -using SixLabors.Fonts; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.PixelFormats; -using System; -using System.ComponentModel; -using Terminal.Gui; - -namespace Rdmp.Core.CommandExecution.AtomicCommands.CohortCreationCommands; - -//Based off of ExecuteCommandCreateNewCohortByExecutingACohortIdentificationConfiguration - -public class ExecuteCommandCreateHoldoutCohort : CohortCreationCommandExecution -{ - private readonly CohortIdentificationConfiguration _cic; - private IBasicActivateItems _activator; - //private CohortIdentificationConfiguration CloneCreatedIfAny; - private CohortIdentificationConfiguration HoldoutConfiguration; - - public ExecuteCommandCreateHoldoutCohort(IBasicActivateItems activator, - CohortIdentificationConfiguration cic) : base(activator) - { - _activator = activator; - _cic = cic; - } - - public override string GetCommandHelp() => "TODO"; - - - public override string GetCommandName() => "Create Holdout Cohort"; - - public override void Execute() - { - base.Execute(); - //this should give use the suer configuration for their hold out - //we can fake it for now - CohortHoldoutCreationRequest request = GetCohortHoldoutCreationRequest(_cic); - - if(request is null) - { - return; - } - - - //SELECT distinct top request.count percent - //extraction_identifier, Rand() - //FROM - //data - //where request.sql - //group by extraction_identifier - //ORDER BY RAND() desc - - - //copy cohort - HoldoutConfiguration = _cic.CreateClone(ThrowImmediatelyCheckNotifier.Quiet); - CohortAggregateContainer container = HoldoutConfiguration.RootCohortAggregateContainer; - - - CohortAggregateContainer subcontainer = new CohortAggregateContainer(_activator.RepositoryLocator.CatalogueRepository, SetOperation.INTERSECT); - subcontainer.SaveToDatabase(); - container.AddChild(subcontainer); - - container.SaveToDatabase(); - //AggregateConfiguration[] aggregateConfirgurations = container.GetAggregateConfigurations(); - //foreach(AggregateConfiguration aggregate in aggregateConfirgurations) - //{ - // AggregateFilterContainer filterContainer = new AggregateFilterContainer(_activator.RepositoryLocator.CatalogueRepository, FilterContainerOperation.AND); - // var filter = new AggregateFilter(_activator.RepositoryLocator.CatalogueRepository, "name", filterContainer) - // { - // WhereSQL = "" - // }; - // filter.SaveToDatabase(); - // filterContainer.SaveToDatabase(); - //} - //inject where - //RootFilterContainer - //var container = new AggregateFilterContainer(_activator.RepositoryLocator.CatalogueRepository, FilterContainerOperation.AND); - //var filter = new AggregateFilter(_activator.RepositoryLocator.CatalogueRepository,"name",container ) - //{ - // WhereSQL = "" - //}; - //filter.SaveToDatabase(); - - HoldoutConfiguration.SaveToDatabase(); - //todo add container ro aggregate configuration - //inject top x % - //savecohort - //add cohhort as new excpet exisitng cohort builder query - - } - - public override Image GetImage(IIconProvider iconProvider) => - iconProvider.GetImage(RDMPConcept.AllCohortsNode, OverlayKind.Add); -} \ No newline at end of file diff --git a/Rdmp.Core/CommandExecution/BasicActivateItems.cs b/Rdmp.Core/CommandExecution/BasicActivateItems.cs index da877933c4..c7917c0c79 100644 --- a/Rdmp.Core/CommandExecution/BasicActivateItems.cs +++ b/Rdmp.Core/CommandExecution/BasicActivateItems.cs @@ -635,35 +635,6 @@ public bool SelectObjects(string prompt, T[] available, out T[] selected, str public virtual IPipelineRunner GetPipelineRunner(DialogArgs args, IPipelineUseCase useCase, IPipeline pipeline) => new PipelineRunner(useCase, pipeline); - /// - public virtual CohortHoldoutCreationRequest GetCohortHoldoutCreationRequest(ExternalCohortTable externalCohortTable, - IProject project, CohortIdentificationConfiguration cic) - { - int version; - var projectNumber = project?.ProjectNumber; - - if (!TypeText("Name", "Enter name for cohort", 255, null, out var name, false)) - throw new Exception("User chose not to enter a name for the cohort and none was provided"); - - - if (projectNumber == null) - if (SelectValueType("enter project number", typeof(int), 0, out var chosen)) - projectNumber = (int)chosen; - else - throw new Exception("User chose not to enter a Project number and none was provided"); - - - if (SelectValueType("enter version number for cohort", typeof(int), 0, out var chosenVersion)) - version = (int)chosenVersion; - else - throw new Exception("User chose not to enter a version number and none was provided"); - - return new CohortHoldoutCreationRequest(cic,"", 1, false, ""); - //return new CohortCreationRequest(project, - // new CohortDefinition(null, name, version, projectNumber.Value, externalCohortTable), - // RepositoryLocator.DataExportRepository, cic.Description); - } - /// public virtual CohortCreationRequest GetCohortCreationRequest(ExternalCohortTable externalCohortTable, IProject project, string cohortInitialDescription) diff --git a/Rdmp.Core/CommandExecution/IBasicActivateItems.cs b/Rdmp.Core/CommandExecution/IBasicActivateItems.cs index 713eba0aba..08151a4cf8 100644 --- a/Rdmp.Core/CommandExecution/IBasicActivateItems.cs +++ b/Rdmp.Core/CommandExecution/IBasicActivateItems.cs @@ -1,5 +1,4 @@ // Copyright (c) The University of Dundee 2018-2019 -// Copyright (c) The University of Dundee 2018-2019 // This file is part of the Research Data Management Platform (RDMP). // RDMP is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. // RDMP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. @@ -164,11 +163,6 @@ public interface IBasicActivateItems #region Select X Modal methods - - //todo description - CohortHoldoutCreationRequest GetCohortHoldoutCreationRequest(ExternalCohortTable externalCohortTable, IProject project, - CohortIdentificationConfiguration cic); - /// /// Prompts the user to enter a description for a cohort they are trying to create including whether it is intended to replace an old version of another cohort. /// diff --git a/Rdmp.Core/MapsDirectlyToDatabaseTable/TableRepository.cs b/Rdmp.Core/MapsDirectlyToDatabaseTable/TableRepository.cs index 42c37c03e6..5bf06aa420 100644 --- a/Rdmp.Core/MapsDirectlyToDatabaseTable/TableRepository.cs +++ b/Rdmp.Core/MapsDirectlyToDatabaseTable/TableRepository.cs @@ -174,7 +174,7 @@ protected static void PopulateUpdateCommandValuesWithCurrentState(DbCommand cmd, //if it is a complex type but IConvertible e.g. CatalogueFolder if (!prop.PropertyType.IsValueType && propValue is IConvertible c && c.GetTypeCode() == TypeCode.String) propValue = c.ToString(CultureInfo.CurrentCulture); - //need to add a dimention first + SetParameterToValue(p, propValue); } diff --git a/Rdmp.Core/QueryBuilding/Options/AggregateBuilderCohortOptions.cs b/Rdmp.Core/QueryBuilding/Options/AggregateBuilderCohortOptions.cs index 95ea58cdb1..c041e4abcb 100644 --- a/Rdmp.Core/QueryBuilding/Options/AggregateBuilderCohortOptions.cs +++ b/Rdmp.Core/QueryBuilding/Options/AggregateBuilderCohortOptions.cs @@ -83,7 +83,7 @@ public bool ShouldBeEnabled(AggregateEditorSection section, AggregateConfigurati return section switch { AggregateEditorSection.Extractable => false, - AggregateEditorSection.TOPX => true, + AggregateEditorSection.TOPX => false, AggregateEditorSection.PIVOT => false, AggregateEditorSection.AXIS => false, _ => throw new ArgumentOutOfRangeException(nameof(section)) diff --git a/Rdmp.UI/AggregationUIs/Advanced/AggregateTopXUI.cs b/Rdmp.UI/AggregationUIs/Advanced/AggregateTopXUI.cs index f1108c1ab5..a301d4fcac 100644 --- a/Rdmp.UI/AggregationUIs/Advanced/AggregateTopXUI.cs +++ b/Rdmp.UI/AggregationUIs/Advanced/AggregateTopXUI.cs @@ -6,7 +6,6 @@ using System; using System.Drawing; -using Rdmp.Core.CommandExecution; using Rdmp.Core.Curation.Data; using Rdmp.Core.Curation.Data.Aggregation; using Rdmp.Core.QueryBuilding.Options; @@ -29,7 +28,6 @@ public partial class AggregateTopXUI : RDMPUserControl private AggregateConfiguration _aggregate; private const string CountColumn = "Count Column"; - private const string RandomOrder = "Random"; public AggregateTopXUI() { @@ -65,7 +63,6 @@ private void RefreshUIFromDatabase() bLoading = true; ddOrderByDimension.Items.Clear(); ddOrderByDimension.Items.Add(CountColumn); - ddOrderByDimension.Items.Add(RandomOrder); ddOrderByDimension.Items.AddRange(_aggregate.AggregateDimensions); if (_topX != null) @@ -146,12 +143,6 @@ private void ddOrderByDimension_SelectedIndexChanged(object sender, EventArgs e) if (ddOrderByDimension.SelectedItem is AggregateDimension dimension) _topX.OrderByDimensionIfAny_ID = dimension.ID; - //ddOrderByDimension.SelectedItem.GetType().Name is "String" and - else if (ddOrderByDimension.SelectedItem.ToString() is RandomOrder) - { - //_topX.OrderByDimensionIfAny_ID = x.ID; //means use random - //todo need to add random to the query - } else _topX.OrderByDimensionIfAny_ID = null; //means use count column diff --git a/Rdmp.UI/CohortUI/CohortHoldout/CohortHoldoutCreationRequestUI.Designer.cs b/Rdmp.UI/CohortUI/CohortHoldout/CohortHoldoutCreationRequestUI.Designer.cs deleted file mode 100644 index e9c237aa53..0000000000 --- a/Rdmp.UI/CohortUI/CohortHoldout/CohortHoldoutCreationRequestUI.Designer.cs +++ /dev/null @@ -1,343 +0,0 @@ -using Rdmp.UI.ChecksUI; -using Rdmp.UI.SimpleControls; -using System.Collections.Generic; - -namespace Rdmp.UI.CohortUI.CohortHoldout -{ - partial class CohortHoldoutCreationRequestUI - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - gbNewCohort = new System.Windows.Forms.GroupBox(); - label7 = new System.Windows.Forms.Label(); - tbName = new System.Windows.Forms.TextBox(); - gbChooseCohortType = new System.Windows.Forms.GroupBox(); - label1 = new System.Windows.Forms.Label(); - comboBox1 = new System.Windows.Forms.ComboBox(); - numericUpDown1 = new System.Windows.Forms.NumericUpDown(); - groupBox3 = new System.Windows.Forms.GroupBox(); - label9 = new System.Windows.Forms.Label(); - tbDescription = new System.Windows.Forms.TextBox(); - btnClearProject = new System.Windows.Forms.Button(); - btnOk = new System.Windows.Forms.Button(); - ragSmiley1 = new RAGSmiley(); - gbDescription = new System.Windows.Forms.GroupBox(); - taskDescriptionLabel1 = new SimpleDialogs.TaskDescriptionLabel(); - panel1 = new System.Windows.Forms.Panel(); - panel2 = new System.Windows.Forms.Panel(); - groupBox1 = new System.Windows.Forms.GroupBox(); - scintilla1 = new ScintillaNET.Scintilla(); - gbNewCohort.SuspendLayout(); - gbChooseCohortType.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)numericUpDown1).BeginInit(); - groupBox3.SuspendLayout(); - gbDescription.SuspendLayout(); - panel1.SuspendLayout(); - panel2.SuspendLayout(); - groupBox1.SuspendLayout(); - SuspendLayout(); - // - // gbNewCohort - // - gbNewCohort.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; - gbNewCohort.Controls.Add(label7); - gbNewCohort.Controls.Add(tbName); - gbNewCohort.Location = new System.Drawing.Point(13, 24); - gbNewCohort.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); - gbNewCohort.Name = "gbNewCohort"; - gbNewCohort.Padding = new System.Windows.Forms.Padding(4, 3, 4, 3); - gbNewCohort.Size = new System.Drawing.Size(1005, 54); - gbNewCohort.TabIndex = 0; - gbNewCohort.TabStop = false; - gbNewCohort.Text = "Holdout Cohort Name"; - // - // label7 - // - label7.AutoSize = true; - label7.Location = new System.Drawing.Point(7, 25); - label7.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); - label7.Name = "label7"; - label7.Size = new System.Drawing.Size(42, 15); - label7.TabIndex = 0; - label7.Text = "Name:"; - // - // tbName - // - tbName.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; - tbName.Location = new System.Drawing.Point(58, 22); - tbName.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); - tbName.Name = "tbName"; - tbName.Size = new System.Drawing.Size(939, 23); - tbName.TabIndex = 1; - tbName.TextChanged += tbName_TextChanged; - // - // gbChooseCohortType - // - gbChooseCohortType.Controls.Add(label1); - gbChooseCohortType.Controls.Add(comboBox1); - gbChooseCohortType.Controls.Add(numericUpDown1); - gbChooseCohortType.Dock = System.Windows.Forms.DockStyle.Top; - gbChooseCohortType.Location = new System.Drawing.Point(0, 0); - gbChooseCohortType.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); - gbChooseCohortType.Name = "gbChooseCohortType"; - gbChooseCohortType.Padding = new System.Windows.Forms.Padding(4, 3, 4, 3); - gbChooseCohortType.Size = new System.Drawing.Size(1048, 217); - gbChooseCohortType.TabIndex = 9; - gbChooseCohortType.TabStop = false; - gbChooseCohortType.Text = "1. Define Holdout settings"; - gbChooseCohortType.Enter += gbChooseCohortType_Enter; - // - // label1 - // - label1.AutoSize = true; - label1.Location = new System.Drawing.Point(171, 20); - label1.Name = "label1"; - label1.Size = new System.Drawing.Size(110, 15); - label1.TabIndex = 5; - label1.Text = "of people in Cohort"; - label1.Click += label1_Click; - // - // comboBox1 - // - comboBox1.AllowDrop = true; - comboBox1.FormattingEnabled = true; - comboBox1.Items.AddRange(new object[] { "%", "#" }); - comboBox1.Location = new System.Drawing.Point(122, 17); - comboBox1.Name = "comboBox1"; - comboBox1.Size = new System.Drawing.Size(43, 23); - comboBox1.TabIndex = 4; - // - // numericUpDown1 - // - numericUpDown1.Location = new System.Drawing.Point(20, 18); - numericUpDown1.Name = "numericUpDown1"; - numericUpDown1.Size = new System.Drawing.Size(87, 23); - numericUpDown1.TabIndex = 3; - // - // groupBox3 - // - groupBox3.Controls.Add(gbNewCohort); - groupBox3.Dock = System.Windows.Forms.DockStyle.Top; - groupBox3.Location = new System.Drawing.Point(0, 217); - groupBox3.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); - groupBox3.Name = "groupBox3"; - groupBox3.Padding = new System.Windows.Forms.Padding(4, 3, 4, 3); - groupBox3.Size = new System.Drawing.Size(1048, 95); - groupBox3.TabIndex = 10; - groupBox3.TabStop = false; - groupBox3.Text = "3. Configure Cohort (doesn't exist yet, next screen will actually create it)"; - // - // label9 - // - label9.AutoSize = true; - label9.Location = new System.Drawing.Point(7, 24); - label9.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); - label9.Name = "label9"; - label9.Size = new System.Drawing.Size(64, 15); - label9.TabIndex = 2; - label9.Text = "Comment:"; - // - // tbDescription - // - tbDescription.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; - tbDescription.Location = new System.Drawing.Point(71, 24); - tbDescription.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); - tbDescription.Multiline = true; - tbDescription.Name = "tbDescription"; - tbDescription.Size = new System.Drawing.Size(969, 67); - tbDescription.TabIndex = 3; - // - // btnClearProject - // - btnClearProject.Anchor = System.Windows.Forms.AnchorStyles.Top; - btnClearProject.Location = new System.Drawing.Point(528, 7); - btnClearProject.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); - btnClearProject.Name = "btnClearProject"; - btnClearProject.Size = new System.Drawing.Size(141, 27); - btnClearProject.TabIndex = 14; - btnClearProject.Text = "Cancel"; - btnClearProject.UseVisualStyleBackColor = true; - btnClearProject.Click += btnCancel_Click; - // - // btnOk - // - btnOk.Anchor = System.Windows.Forms.AnchorStyles.Top; - btnOk.Location = new System.Drawing.Point(372, 7); - btnOk.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); - btnOk.Name = "btnOk"; - btnOk.Size = new System.Drawing.Size(149, 27); - btnOk.TabIndex = 13; - btnOk.Text = "Ok"; - btnOk.UseVisualStyleBackColor = true; - btnOk.Click += btnOk_Click; - // - // ragSmiley1 - // - ragSmiley1.AlwaysShowHandCursor = false; - ragSmiley1.Anchor = System.Windows.Forms.AnchorStyles.Top; - ragSmiley1.BackColor = System.Drawing.Color.Transparent; - ragSmiley1.Location = new System.Drawing.Point(335, 4); - ragSmiley1.Margin = new System.Windows.Forms.Padding(5, 3, 5, 3); - ragSmiley1.Name = "ragSmiley1"; - ragSmiley1.Size = new System.Drawing.Size(30, 30); - ragSmiley1.TabIndex = 12; - // - // gbDescription - // - gbDescription.Controls.Add(label9); - gbDescription.Controls.Add(tbDescription); - gbDescription.Dock = System.Windows.Forms.DockStyle.Top; - gbDescription.Location = new System.Drawing.Point(0, 312); - gbDescription.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); - gbDescription.Name = "gbDescription"; - gbDescription.Padding = new System.Windows.Forms.Padding(4, 3, 4, 3); - gbDescription.Size = new System.Drawing.Size(1048, 99); - gbDescription.TabIndex = 11; - gbDescription.TabStop = false; - gbDescription.Text = "4. Enter Description Of Holdout"; - // - // taskDescriptionLabel1 - // - taskDescriptionLabel1.AutoSize = true; - taskDescriptionLabel1.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; - taskDescriptionLabel1.Dock = System.Windows.Forms.DockStyle.Top; - taskDescriptionLabel1.Location = new System.Drawing.Point(0, 0); - taskDescriptionLabel1.Name = "taskDescriptionLabel1"; - taskDescriptionLabel1.Size = new System.Drawing.Size(1048, 42); - taskDescriptionLabel1.TabIndex = 19; - // - // panel1 - // - panel1.Controls.Add(panel2); - panel1.Controls.Add(gbDescription); - panel1.Controls.Add(groupBox1); - panel1.Controls.Add(groupBox3); - panel1.Controls.Add(gbChooseCohortType); - panel1.Dock = System.Windows.Forms.DockStyle.Fill; - panel1.Location = new System.Drawing.Point(0, 42); - panel1.Name = "panel1"; - panel1.Size = new System.Drawing.Size(1048, 509); - panel1.TabIndex = 20; - // - // panel2 - // - panel2.Controls.Add(btnOk); - panel2.Controls.Add(ragSmiley1); - panel2.Controls.Add(btnClearProject); - panel2.Dock = System.Windows.Forms.DockStyle.Top; - panel2.Location = new System.Drawing.Point(0, 411); - panel2.Name = "panel2"; - panel2.Size = new System.Drawing.Size(1048, 55); - panel2.TabIndex = 20; - // - // groupBox1 - // - groupBox1.Controls.Add(scintilla1); - groupBox1.Location = new System.Drawing.Point(13, 66); - groupBox1.Name = "groupBox1"; - groupBox1.Size = new System.Drawing.Size(997, 139); - groupBox1.TabIndex = 6; - groupBox1.TabStop = false; - groupBox1.Text = "2. Define Holdout Criteria"; - // - // scintilla1 - // - scintilla1.AutoCMaxHeight = 9; - scintilla1.BiDirectionality = ScintillaNET.BiDirectionalDisplayType.Disabled; - scintilla1.CaretLineBackColor = System.Drawing.Color.Black; - scintilla1.LexerName = null; - scintilla1.Location = new System.Drawing.Point(19, 22); - scintilla1.Name = "scintilla1"; - scintilla1.ScrollWidth = 49; - scintilla1.Size = new System.Drawing.Size(958, 100); - scintilla1.TabIndents = true; - scintilla1.TabIndex = 0; - scintilla1.Text = "scintilla1"; - scintilla1.UseRightToLeftReadingLayout = false; - scintilla1.WrapMode = ScintillaNET.WrapMode.None; - // - // CohortHoldoutCreationRequestUI - // - AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); - AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - ClientSize = new System.Drawing.Size(1048, 551); - Controls.Add(panel1); - Controls.Add(taskDescriptionLabel1); - Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); - Name = "CohortHoldoutCreationRequestUI"; - StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; - Text = "Create Cohort"; - Load += CohortHoldoutCreationRequestUI_Load; - gbNewCohort.ResumeLayout(false); - gbNewCohort.PerformLayout(); - gbChooseCohortType.ResumeLayout(false); - gbChooseCohortType.PerformLayout(); - ((System.ComponentModel.ISupportInitialize)numericUpDown1).EndInit(); - groupBox3.ResumeLayout(false); - gbDescription.ResumeLayout(false); - gbDescription.PerformLayout(); - panel1.ResumeLayout(false); - panel2.ResumeLayout(false); - groupBox1.ResumeLayout(false); - ResumeLayout(false); - PerformLayout(); - } - - #endregion - //private System.Windows.Forms.Label label3; - //private System.Windows.Forms.Label lblExternalCohortTable; - private System.Windows.Forms.GroupBox gbNewCohort; - private System.Windows.Forms.Label label7; - private System.Windows.Forms.TextBox tbName; - //private System.Windows.Forms.Button btnNewProject; - private System.Windows.Forms.GroupBox gbChooseCohortType; - private System.Windows.Forms.GroupBox groupBox3; - private System.Windows.Forms.Label label9; - private System.Windows.Forms.TextBox tbDescription; - //private System.Windows.Forms.Label lblProject; - //private System.Windows.Forms.Label label11; - private System.Windows.Forms.Button btnClearProject; - private System.Windows.Forms.Button btnOk; - private RAGSmiley ragSmiley1; - private System.Windows.Forms.GroupBox gbDescription; - //private System.Windows.Forms.PictureBox pbProject; - //private System.Windows.Forms.PictureBox pbCohortSource; - //private System.Windows.Forms.Button btnExisting; - //private System.Windows.Forms.Label lblErrorNoProjectNumber; - //private System.Windows.Forms.TextBox tbSetProjectNumber; - //private System.Windows.Forms.Button btnClear; - private SimpleDialogs.TaskDescriptionLabel taskDescriptionLabel1; - private System.Windows.Forms.Panel panel1; - private System.Windows.Forms.Panel panel2; - private System.Windows.Forms.GroupBox groupBox1; - //private System.Windows.Forms.CheckBox checkBox1; - private System.Windows.Forms.Label label1; - private System.Windows.Forms.ComboBox comboBox1; - private System.Windows.Forms.NumericUpDown numericUpDown1; - private ScintillaNET.Scintilla scintilla1; - } -} \ No newline at end of file diff --git a/Rdmp.UI/CohortUI/CohortHoldout/CohortHoldoutCreationRequestUI.cs b/Rdmp.UI/CohortUI/CohortHoldout/CohortHoldoutCreationRequestUI.cs deleted file mode 100644 index b0d9f593d9..0000000000 --- a/Rdmp.UI/CohortUI/CohortHoldout/CohortHoldoutCreationRequestUI.cs +++ /dev/null @@ -1,212 +0,0 @@ -// Copyright (c) The University of Dundee 2018-2019 -// This file is part of the Research Data Management Platform (RDMP). -// RDMP is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. -// RDMP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. -// You should have received a copy of the GNU General Public License along with RDMP. If not, see . - -using System; -using System.Drawing; -using System.Linq; -using System.Windows.Forms; -using Rdmp.Core.CohortCommitting.Pipeline; -using Rdmp.Core.CommandExecution; -using Rdmp.Core.Curation.Data.Cohort; -using Rdmp.Core.DataExport.Data; -using Rdmp.Core.Icons.IconProvision; -using Rdmp.Core.Providers; -using Rdmp.Core.Repositories; -using Rdmp.Core.ReusableLibraryCode.Checks; -using Rdmp.UI.ItemActivation; -using Rdmp.UI.Refreshing; -using Rdmp.UI.SimpleDialogs; -using Rdmp.UI.TestsAndSetup.ServicePropogation; -using Point = System.Drawing.Point; - -namespace Rdmp.UI.CohortUI.CohortHoldout; - -/// -/// this is incorrect -/// Once you have created a cohort database, this dialog lets you upload a new cohort into it. You will already have selected a file which contains the private patient identifiers of -/// those you wish to be in the cohort. Next you must create or choose an existing Project for which the cohort belongs. -/// -/// Once you have chosen the project you can choose to either create a new cohort for use with the project (use this if you have multiple cohorts in the project e.g. 'Cases' and -/// 'Controls'). Or 'Revised version of existing cohort' for if you made a mistake with your first version of a cohort or if you are doing a refresh of the cohort (e.g. after 5 years -/// it is likely there will be different patients that match the research study criteria so a new version of the cohort is appropriate). -/// -public partial class CohortHoldoutCreationRequestUI : RDMPForm -{ - private readonly IExternalCohortTable _target; - private IDataExportRepository _repository; - private readonly CohortIdentificationConfiguration _cic; - - public string CohortDescription - { - get => tbDescription.Text; - set => tbDescription.Text = value; - } - - public CohortHoldoutCreationRequestUI(IActivateItems activator, IExternalCohortTable target, IProject project = null, CohortIdentificationConfiguration cic = null) : - base(activator) - { - _target = target; - - InitializeComponent(); - - if (_target == null) - return; - - _repository = (IDataExportRepository)_target.Repository; - _cic = cic; - SetProject(project); - tbName.Text = $"holdout_{cic.Name}"; //todo this should be the existing cohorts name - - taskDescriptionLabel1.SetupFor(new DialogArgs - { - TaskDescription = - "todo. maybe remove?" - }); - } - - - public CohortHoldoutCreationRequest Result { get; set; } - public IProject Project { get; set; } - - private void btnOk_Click(object sender, EventArgs e) - { - string name; - name = tbName.Text; - Result = new CohortHoldoutCreationRequest(_cic, tbName.Text, Decimal.ToInt32(numericUpDown1.Value), comboBox1.Text =="%","" ); - //see if it is passing checks - var notifier = new ToMemoryCheckNotifier(); - Result.Check(notifier); - //if (notifier.GetWorst() <= CheckResult.Warning) - //{ - DialogResult = DialogResult.OK; - Close(); -// } -// else -// { -// var bads = notifier.Messages.Where(c => c.Result == CheckResult.Fail); - -// WideMessageBox.Show("Checks Failed", -// $@"Checks must pass before continuing: -//- {string.Join($"{Environment.NewLine}- ", bads.Select(b => b.Message))}"); - -// //if it is not passing checks display the results of the failing checking -// ragSmiley1.Reset(); -// Result.Check(ragSmiley1); -// } - } - - private void btnCancel_Click(object sender, EventArgs e) - { - Result = null; - DialogResult = DialogResult.Cancel; - Close(); - } - - - private void CohortHoldoutCreationRequestUI_Load(object sender, EventArgs e) - { - _target.Check(ragSmiley1); - } - - - private void btnNewProject_Click(object sender, EventArgs e) - { - try - { - var p = new ProjectUI.ProjectUI(); - var dialog = new RDMPForm(Activator); - - p.SwitchToCutDownUIMode(); - - var ok = new Button(); - ok.Click += (s, ev) => - { - dialog.Close(); - dialog.DialogResult = DialogResult.OK; - }; - ok.Location = new Point(0, p.Height + 10); - ok.Width = p.Width / 2; - ok.Height = 30; - ok.Text = "Ok"; - - var cancel = new Button(); - cancel.Click += (s, ev) => - { - dialog.Close(); - dialog.DialogResult = DialogResult.Cancel; - }; - cancel.Location = new Point(p.Width / 2, p.Height + 10); - cancel.Width = p.Width / 2; - cancel.Height = 30; - cancel.Text = "Cancel"; - - dialog.Controls.Add(ok); - dialog.Controls.Add(cancel); - - dialog.Height = p.Height + 80; - dialog.Width = p.Width + 10; - dialog.Controls.Add(p); - - ok.Anchor = AnchorStyles.Bottom; - cancel.Anchor = AnchorStyles.Bottom; - - var project = new Project(_repository, "New Project"); - p.SetDatabaseObject(Activator, project); - var result = dialog.ShowDialog(); - result = DialogResult.OK; //temp - if (result == DialogResult.OK) - { - //project.SaveToDatabase(); - //SetProject(project); - Activator.RefreshBus.Publish(this, new RefreshObjectEventArgs(project)); - } - else - { - //project.DeleteInDatabase(); - } - } - catch (Exception exception) - { - ExceptionViewer.Show(exception); - } - } - - private void SetProject(IProject project) - { - Project = project; - - gbChooseCohortType.Enabled = true; - } - - private void tbName_TextChanged(object sender, EventArgs e) - { - } - - private void btnExisting_Click(object sender, EventArgs e) - { - if (Activator.SelectObject(new DialogArgs - { - TaskDescription = - "Choose a Project which this cohort will be associated with. This will set the cohorts ProjectNumber. A cohort can only be extracted from a Project whose ProjectNumber matches the cohort (multiple Projects are allowed to have the same ProjectNumber)" - }, Activator.RepositoryLocator.DataExportRepository.GetAllObjects(), out var proj)) - SetProject(proj); - } - - private void btnClear_Click(object sender, EventArgs e) - { - SetProject(null); - } - - private void gbChooseCohortType_Enter(object sender, EventArgs e) - { - - } - - private void label1_Click(object sender, EventArgs e) - { - - } -} \ No newline at end of file diff --git a/Rdmp.UI/CohortUI/CohortHoldout/CohortHoldoutCreationRequestUI.resx b/Rdmp.UI/CohortUI/CohortHoldout/CohortHoldoutCreationRequestUI.resx deleted file mode 100644 index af32865ec1..0000000000 --- a/Rdmp.UI/CohortUI/CohortHoldout/CohortHoldoutCreationRequestUI.resx +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - \ No newline at end of file diff --git a/Tests.Common/TestDatabases.txt b/Tests.Common/TestDatabases.txt index bc2a95b27d..168e37154c 100644 --- a/Tests.Common/TestDatabases.txt +++ b/Tests.Common/TestDatabases.txt @@ -6,7 +6,7 @@ #To achieve this, you can run DatabaseCreation.exe with argument 1 being your ServerName #You can apply a prefix e.g. TEST_ as an argument to DatabaseCreation.exe and include that prefix below if you like -Prefix: TEST_ +Prefix: RDMP_ ServerName: (localdb)\MSSQLLocalDB #Uncomment to run tests with a YamlRepository #UseFileSystemRepo: true From b9a2f6d25ce6d71b62424680a0471dcec60320fc Mon Sep 17 00:00:00 2001 From: James Friel Date: Fri, 13 Oct 2023 10:38:28 +0100 Subject: [PATCH 18/26] tidy up --- Rdmp.UI/SimpleDialogs/SelectDialog`1.resx | 982 ++++++++++++++++++++++ 1 file changed, 982 insertions(+) create mode 100644 Rdmp.UI/SimpleDialogs/SelectDialog`1.resx diff --git a/Rdmp.UI/SimpleDialogs/SelectDialog`1.resx b/Rdmp.UI/SimpleDialogs/SelectDialog`1.resx new file mode 100644 index 0000000000..68cc010e31 --- /dev/null +++ b/Rdmp.UI/SimpleDialogs/SelectDialog`1.resx @@ -0,0 +1,982 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + + + + R0lGODlhZABkAPcAAPhw5P7+/v78/vLc8P5K5P4g3P4K2v6a8Pym7vxE4Pw24PTE7vy68vyi8Pb29v5q + 6P484P4W2v6q8vx86PqW6vz6/Pj4+Ppq5PpM4PiO6Pz6+viC5vh45vi68PiK6Pz4/Pzr+fj2+PbE8Paa + 6PrO8vTS8PLy8vTM7vj0+Pjt9vTw8vTs9PLs8v5a5v4w4Pxg5vqM6v5x6P464v4a3PyS7Pqi7vpu5vpm + 5Piy7viA6Pau7Pbn9Pai6PTW7vyF7Pqd7PqY7Pic7PqE6PyE6vx+6vx86viW6vas6vxC4PTo8vxU5PzU + 9v4Y2vTm8v4c3P5Y5P5M5Ppu5Pps5Ppc5Pjc8/bW8PxI4vxC4vTi8P4q3vxG4Pw+4P5A4PTg8v4e3P4o + 3v4k3v4i3v4e3v4m3P4W3PTc8vjI8PbS8PzH9PbM8Pqz8PzC8vjC8P5C4vi88P5I5Py+8vy88vut8P6q + 8Pyo8P6i8Pym8Pyk8PqQ6v566vqK6vp+6PqO6vpw5viK5vpg5PqI6v5/6/6K7vye7vyK7PyI6vyA6vyC + 6vx16Pxc5PxM4vxK4PxI4Pi47va27Pig6vie6v6Q7fyM6vxv5/6c7vyO6vyO7PyQ7Pxr5/5i5vqm7vqg + 7P5W5Pxi5vyg7vqm7PxY5Pqo7v5U5Pqq7v6x8v5S5Pqs7v5Q5P669Pqw7v5O5Pqy7vxK4va87P689PbA + 7vz8/P7S9/464P4t3/444P424P404P4y4PxS4v76/v7n+vro9/ya7vxe5PyW7Pxe5vxg5Pp05vxk5Prf + 9vy48vyS7vp25vzb9/qC6P7E9f7g+P76/Pp45vbW8vq68Pp66Pz4+vrA8vr4+fz0/PrX9ffw9/Xx9Pbu + 9PzY9/ji9PbS8vjT8vbQ8vzN9fu28fzC8/jE8PjA8Py99Puw8Pyq8P6o8P649P7A9P78/P7a+P7w/Pry + +Prj9/zh+P7L9v7j+vbf8vrI8vy28vyv8vzy+vrO9P546vqk7vyJ6/jK8P6I7P4w3vrs+Pz2/Pz2+gAA + ACH5BAAFAAAAIf8LTkVUU0NBUEUyLjADAQAAACwAAAAAZABkAAAI/gADCBxIsKDBgwgTCtTgwIRDBxUU + SpxIsaLFiwJR6GnzxkO1iBhDihwZkpCMkzL4aCDJsqVLgRXaoJRBYOXLmzgphpgpA4LNnECDDnTA06fQ + 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 + PI9WCGup3FW1DOO35OjwhWKtEKq2FlwSrACCRQvsTPM6IfRILir6nBOzilYclCr1aSHWjeS+GknTKIZo + i+FeYxN6fpJ2LCko3SFX/BY1E5H2AdJ2jOS+dkWhFm0d0qbutAkAua0Fcock8Nv7dOrYEtFFclXboQr8 + lhTMU7euC5KM5K5IA9o+q4LrguaJJFvcK4LFh88a4n/r+CIlhuuJQB/ejcS+G8AVp0g6XPFfCXZV4VKp + tQ7VzWxEO9q7Mrf5RIaLWuPq/CimRS3Bfx4UaCBWsTUn+oykYQG25vwlAQEAIfkEAAUAAAAsAAAAAGQA + ZACH9nzk/v7+/vz+8t7w/kTi/iTe/gTa/pTu/Jzs/Eri/Bzc8szs/Jru+Pj4/mjm/jTg/gba/qTw/I7q + +oTo+vr6+lLi+j7e+qTu+Ijm+vj6+vj4+Gzi+GLi+LTu/Pb79ojm9sLu8vLy9pLo/hTc+fP49vL19PL0 + 9OLy9NTw9Mbu9Lbq9Kzq9KLo/Mj08ujw8uLw9erz9Oby9Njw9M7u/lTm/jLg/HLo/ELg/nTq/jrh/hLa + /Jbs+pLs+lji+Ibo+tn1+Jjq+HDk9rDs/hnc9Lzs/JTs/JLs/JDs/I7s/Izs/mTo+pLq+orq+JLq9p7q + /Fzk+obo+JDo+Ijo9try/iLc/mrm/kbi/FLi+OD09OTy/FDi/Ezi+lTi/ibe/DLc+kjg+kTg/CTc/CDc + /B7c+Mvy/hDa/gza/gra/gja+rfw9Nbw9tLy9s7w+L7w/kLi/LXx+Lzw+Lru+Lbu+rDw/Krw+qju/KXv + +qbu/KDu/qLw/qDw/pnv/J7u/Jzu/n3q/mzo/nbo/IPq/Gnm/Fjk/E7i+mLk+H3m/oTs+nvn/Dze/Cjc + /Hjo+pjs+pTq+pDq/ovs/pLu/HTo+Jrs9rLs9MTs+JTq9qbq+prq/GTm+pzs+Jzq/lvm/GDk/lTk+p7s + +KLq+qDs+KLs+KTq/qrx/C7c+Kzs/rT0/FTi/lLk/lDk/kzk9r7s/Eji/rjz/Ebi/Ebg/Pz8/Dzg/vr+ + /sn2/D7g/DTe/jDe/Frk/tX4/Nr3/izg/ize/ire/ije+mTk/uL6+mjk8uzy/Gzm+r3x+mrk/Gzo+nDm + /G7m/Of4/HTm+snz9N7w+Ov2/vr8+nLk+nLm/MPz+pbs+eT1/iDc+NTz9N7y/Pr7/Pj8+vb69vX29PT0 + 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 + +1Hq2vWuediQ3SxckGaQI7StcF6CgDAEfiaixAFoQTrFm5FWzPZmFZauwcQa2ndkhBnOOAM4gXARfEDi + EaNARg5la9lzGAwWLDYIPnr7W7GsoQJmUAAGjDsSAYxitu4VCP6WhXyQ6oYWH40NwCyH+BpczJYcYFoz + bvXH3sCKbiQ4FYkH1tHf8ao5ywa5xoAD2wop8jIA+JhtaRCiZ87GEh4ZfnQ9TrzVCKjYIJXebCxhwVtG + E3aXPrZsfCiN6IOANrT+CHRV4jFbdnwq1BH46FHbm0ssz3ayoG71QTzQ23bIWig6tSw0RIbrEgM2tH8+ + 4KAtuw5DF6TZ6Fv0WBu9wnbMdrTGETZC4tFbbv6oHrMdRfCwTZHecflH7JjtNyzCboq4ObCeRRAszOu+ + elNk19uGkTUIvVX/0kXcCSE2o2EECwlvtUEHZ/NFMEzWH1ljG3wFN70RrhB4tKMd0WYhoCs5bpxHYwTA + JudLeo+d8onclrO6bTlSNMvZfMv8b3vta5xvbpEmLWXnQQkIACH5BAAFAAAALAAAAABkAGQAh/aM5v7+ + /v78/vLe8P5M5P4e3P4M2v6c8Pyk7vwk3PSw6vy48vyi8Pb09v5s6P4+4P4O2v6s8vx66Pz6/Pr4+vp6 + 5vpY4vpE4Pqu8PqY6vz6+vj4+Phy5Pho4viY6vz4/Pz4+vj2+Pj0+Pj09vaW6PbE8Pam6Pzc9/rr9/b2 + 9vTW8PLq8vTG7vS47Pjk9fTy9PTY8PLk8P5c5v404Pxa5P587P484v4c3PyK7PqG6vpw5viC5vqh7fig + 7PTp8/TQ7vTI7vS+7v4a3PyE6vqc7PqW7P5y6fx86Pic6via6vaq6vp+6Pp66P5O5Pq88Pp85vpm5PxK + 4vpY5Pbd8vw83vjS8v4s4PpO4P4u3v4q3v4o3vww3vwm3P4g3P4U3P4S3P4Q3PzN9P4Y2v4W2v4U2v4Q + 2vbU8PbQ8vq48PzE9PzB8v5D4vy88v5K5Py08Pq28Pqy8Pys8P6q8vyo8P6k8Pym8P6e8PqT6vp25viO + 6PqM6PqA5vpu5vpe5P6H7P6A6vqM6vqC6PyS7PyI7Px+6vyA6v6M7vxr5vxY4vxC4Pw23vws3v6T7v6a + 7v5j5/5a5vyU7PyW7Pqk7Pim7PTO7vqe7PqY7Pii6vyY7Pya7Pxh5vya7vyc7vxf5Pyg7vqq7vxc5P5Y + 5vqk7vqo7v5W5Pio7P5T5PxS4/i47viw7vqs7v6y8vqu7vxO4v689Pa07PbC7vi+7vw63v7A9Pz8/P7c + +P464P4w4P444P424PxG4vxM4PrS9Pp45vxY5P76/v76/Pzx+vTa8P7I9vTe8PyQ7PjA8Prd9vLy8vyN + 7PrF8vzn+fby9P5+6vja9P7s+v586vx05/7n+v7Q9vr6+vzi+Pr2+vfs9vT09PTs9Pq/8fTk8vjW8/zV + 9vbW8PzJ9PzA9Py+8vu18vq08Pyu8Pyq8P6o8v6g8P669P7D9v78/P7g+frX9fzz+/7M9vjG8Prj9vrM + 8/zs+vfx9vjf9P7q+v7U+Pq/8vyv8vjK8vr0+Pbt9P566vzQ9f5K4gAAAAj+AAMIHEiwoMGDCBMKpLah + wYsGG2gpnEixosWLGAVag0QqVJEGEjOKHElSJCQCKAlYmlCypcuXAieESkngEUuYOHNWpECTQBNqOoMK + LRii58+hSBHSokDt5sUGRimIpAU0KcYJCwj9WeC0IlSaTaRenDDHkaNPG6xaJCejrQwMGL+mDHuRViYb + eG3wqKo2oQAHbmU4WvY06kV6a/LaaJK2r19HgWUICklRLkq6FTdIUGxjjVjHCItFltHN4gvDFUtwtiGB + MmiDLiAHhtY14WmwnxU2kMF5zbHXCWkhGM2m4u25uZWWWg3JNfCC1wAHdqCP4vHLyQ/6IMC5SfXnSp3+ + jEbg3OB1n9kLUgO0Gg543RIiO4I38TzmhN4ecHZU+33BKqPh0B9B9qU30AY6rBaPfwpRIFpkYShUoEKx + rEbIgAwO5IImkRlhYAATIsRMKL25kKFCZI3mhm2oGTQBD6sRceJE80jnlgPyIBSiQdvowpkp382YkDGj + cVIeiC0SREEOqzkB2lITHDnRBn+M9pt5SQ5UhX6KHYKhQrQI8OVBE4jDjxGb0JcRN6MN0Z9l6BkUwiGc + PcBLRtRw48AtNhQxJkHhOCCoA0bEEQxG1BQxmjIGwXnfQK+sloOUB9Hiww63WKGpFZJURAsOgw7Kjxp/ + EuSDbG4ZYU1Bjia3jyn+nLWBwlOX2LDppjR8SJAhoYbqBzuUEkQLBqORw2qS1GSwWqeZwXLKrbfS0NhE + s/Ta6wGzVmaEfNkK5ChfAUyRmGI0rDoRNcLwAS20k4Cr0AJGWDuoEW6Yq9A7o0VCWVFgOUXNE6tpM5Gl + esyw7qYzZKArbJzIO2gN34xJgR+jsTMQT2BRls9qOrjLqhJrHLxpNGriyY0fDguazAmUOjPahQIJYApN + j4Q0gSO9OZPQBkAgIrKmpORTqkIhqMFPyg4w0G1BE1QyWoQCMUITJiGFs1oxR05ADB4/W/HAJwtfNE8c + 8TpsxALpRReZsQJ9EElHmNh7AGcELD3QPncYLPL+LTjY/dIELkSC9B9h1EbLApF9M9BSDW3wmSucwUXU + K210fYgzwbZEQRVDIC1INUxT3BYOAlA0gRF5eUjQBj9o0nUTxgztUghOlC2vEXMcOpAA6ciRjuxYRcLV + 4tsskanItvAQwmu0XIMA0vyEI3twSlZiy89YBOK3Y9TQU4zn00/0ges/a8JN5mpRkE8zKSsOEzk/64JG + 2KCJII7toa6CEw4H55KBCEISlgs8YS33vQQf6+pFyQJIEGo4A1SCYkTpYPIBR+AqHuHzzwSmsYBpTBAn + WCmCNjLIwBKa8IQFocUEVsjCFrrwhTBcIfpcMoEUvOCGOMyhDneYwxTwhRb+1nCDH2pAxCIa8YhIRKIf + VkFCi3xAAlkogBSnSMUqWtGKWpBAdSbghiR68YtJ1N9QJHDFMprxihJY4RDByMYv+mGGGZlAFM9IRzNm + gQITWGMb92hEPwyFGnOsoyCpmIWIdJGPiKyBGIMygSMM8pFSrIBUuKjHRHpxiR8MigigCEk6ZqECzHAK + VTZAylKa8pSoLCVT+jIBCqTylbA0JR5RSMta2tJFtUhDN5qYkAnEYQhuoN+MqsEAQxhTDplsiTWiYIBm + UiEI0zphMFZhzGoawh44QUAzt2mATsBAmK+hQBoYYc1qpgMnveDmNsewgxXAUS206EYjymlNbMJEEur+ + 5GYWTNCAEy2jDvS0ZgRyYo0m5JObF2BBNF/TjgUEtJqMCEMyZ5eBAhy0mRDowAA8lpQJfIOcDzUEG4IU + AAHUYhi1mChCBCCNWkhDgDoAw0UNIAQAZOOdJKHFCQ4QUkNEAAWu4eIBhroAi0zDFUidBtOqsIuZGmAL + CkhBUpYRgZ7agR39scdQt7qOxU2gKa6RBlLH+tKCUCAVWHDqFX6w0Je0gw09ZcQ30vMBOmx1qEqNyTRW + sQoPDqQWY0VqOj5wEBHoQQwz/QIHYsBLpoUBpA9dwPYEkoa7DrWrAplGBDYbgbwGoB6BRWotlAIPKEBg + pgUgwQtccoJz9HQO8Pj+kjzsYNlFBmAVnI2AbYcRWlfo7iDUyEcinKqIFrS1IvLAR08b0Q2OLm4Vlj0A + YQaS280S5Bm9nUawKDAJLTjVAiqY3gTGGVJGpGF5E9mpZWfhmupGwDX26O10FSKCPXhhpl7IwdBo8Y2e + ugGoppODZc9B0gC4tysfmEVoh1EqWhyjE07FREUmQIeHMiC2Ro0u1BZ34IIANrSYpQg1YiGLi66hwRUu + ZyP6Ac5gnMOyckgOLTpMEN6FdhaEtQgFiNCFfCbCuQUJgzUZAY4CU2QW0T2BQWZc3f6INbSlwcg18PAF + bsrIIsM4ACNWAWCMSCO64ugPk3M7IHf09rcWmYD+GWgwhgTkAZwtEQB072qH7Y2ZsxwNRm/dgdOFbCAF + cG6Jeu+ahiPdebPOjW9o55uRPpfkA+awLB2MLJBDR8C5H0hHaH9nS3dEtx/BcS+Q19Hb0dLSxZaNwIcs + DWQB8Daws2gHLV0RXdCFurpA/mx2UbiM6A7v1rnNdQCOGtpnmHACEbCsHXIEJlFPpB29HYZKGdSN6A4j + c6ymyIcDa2sh1VXS9gI2nk2n6cByWkhZtmyUB+ZsilSj1EKarWXxsd92T8TVvZX1idAR3QU2G9cWwW5o + 7cmgCdD2rmyAo6Wnp+ixzuJEFL7rOdDM7iZfJMHmPhEtpnFXz3qKxhYh9Vg+Q1zwbvCV4BZZeEaq4Q53 + dPuW4t5sY2FOEZDTPCjudfTNK4Jbztp25znRLGc9DnQQ7rWvMy86majB9KTDJCAAIfkEAAUAAAAsAAAA + AGQAZACH9obm/v7+/vz+8uDw/kbk/hbc/gba/pbw/Jbu/Bra8srs/KDw+Pj4/mbo/ibe/gja/rb0/Kru + /HLo+vr6+mTk+jze+q7w+pLq+vj6+Hzk+Gzk+GLi+Fjg+MDw9pro/Pf79szw8vLy9pbo/M/1+fb49/P2 + 9vL09O709OTy9Nrw9NLw9Kro/hTa+uj39PL08ury8tbu+Nz09vD29PD09Ory9ODw8uzy/lbm/hjc/Kju + /Kbu/KTu/KLu/KDu/J7u/Jzu/Jru/E7i/nbq/i3e/hDa/Ijs+oDo+lbi+prs+Iro+G7k+Jrq9uXz9MTu + 9Kjq8tzu+pDs+KDs9Lrs/m3o/IDq/Hbo+prq+JTq+nTo+Ijo+JLo+nDm+Ibm9Oby/lTk/kjk+mzk+s/y + 9tjy/Dze+lLi+kbg/DDe/Cje/CLc9Nzw/g7a/gza/gra+NHy/jbg9tLy9tLw/Lzy+rzw+Mjw/j3h+MLw + /kzi/LXw+rrw+rbw/qby+rLw/LLx/K/w/Kvw/qbw/Kbw/p7w/n7q/nLq+ojp+mDk+HLk+nzm+nbm+lTi + +krg+pzs/ILq/Hjo/obs/Hrq/H7o/IDo+nro/Ibq/o/t/Ijq/pzu/Gbl/ETh/Dbe/Cre+LLu9sLu9Mju + 9LLq+Kzs+Kbs9Lzs+Jbq9qDo/l7m/lDk/Jzs+p7s+qDs/Fvk+lzk+qTu+qbs/Fbk+qbu+qju/FLj+q7u + /k7k/kzk/krk+Lju/rrz/Cze+Lru/vz8/EHf/vr+/vr8/sX1/D7g9sTu/t/5/DPe9NDu+mTi+JDo/Nz3 + /Eri/Jbs/iTe/Ezg/iLc+K3u/h7c/I/s+J7q+Hbk/Of4/hze/hzc/vj8/hre/hrc/Izs/vb8+pTs/tf4 + +t/2+Or2+nzo+Kjs+n7o/kri/pru/PD6/jTg/Mf0+sby/kji/kbi/q/y/pju/tL29q7s/Pv8/Pj8/NX2 + +vb69/P39fP1+vH4+OH09Oz09ur0+tf09uLy+Nbz/MDy+r7w+Mry+MTw/Lny+rry+rjwAAAACP4AAwgc + SLCgwYMIEwqcwACeC3gM1imcSLGixYsYBboDQorUoncSM4ocSVIkkBsob/QIWbKly5frSKW80YDly5s4 + LWKYiXJCzp9AEZLgecNn0KMKJ0ywWbEEUaMY1zFFWnFCHEeO4kxV6JQn1Irr+kyZYoEB1Yt8GqhtcAdj + 15lfJ67rQaAuAR1xzx4UMGhtgyktLr5NmTehvFl2Cdwwq1ehgCl+GyDYenBwT4sMqCQmIItx44ToIjeo + ZxHeU4tzNhNgRPmzQG2iGRU2aNprRXiXNs/S5lrhOlei8d0+LXeZaiCteweI19fvoHgUa8OlSM/L5hvQ + lSvMJzpCdOIJJ/5cUJ1H+0QSjCIDniid8EQxXzZPmW1+4D3RlJK3v5yQQRXVYdQ30QQIiDaCQvsVpVAH + qk2SnIABMAGZX4JgkFCC9JVAymZ2BAahbxaIxs+F4BW0DiuqofIhRSUIEdkg4SCEIUJMlLIZKSSsSNE+ + ou0gY4kDMVCJanK4to5SDx7EQBGi8UYbkAK9EV9iEtCXkFRJErQOP1Q8soCHGN0XmTVTWaZgQSRIsNkX + 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 + 9t7y9O3y8ury8uDw8tzw8szu/hDa/lDk/ize/JLs/Ejg/mfn/jDg/g7a/HDo+nro+lrk+orq+ITo+HDk + +JTq9qzq9L7s/hXb/I7s/Izs/iLc/Izq/Ibq/Hzq+obq+oro9p7q/H7o/Hzo/Gjm+I7o9OXy/Gbm/GTm + +mzm+dHz+Hzm/k7k/kLi+lzi9Njw/Djg/DLe+k7g/iLe/Cre/Crc+Mnw9Mru/L3y+rrx/jng98Du/j7g + /Kfw+LTu+qru+LLu+LDu+qju+qbu/KDu+qTu/pXu/J7u/Jzu/nDq+o7q/Grm/Ijq/ITq/ILo/Gzo/G7m + /G7o/nbp/oDs+oPo+mbk+Ibo+nTm+l7i/IDq/IDo+nzo/ojs/pDs+KLs+Jbo9rbs+J7q+Jjq+JTo+nLm + +pDs+pbq+prs+mTk+p7s/FPj/EDg/Dze/DDe/lbl/kzk+qDs+qLs/rTy+Kjs+qTs+Krs/FHj+LLs/E7i + /kri/Pz8/kji/ub6/kbi/kTk/kTi/D7g/Drg/DTg/r70/ire/PH5/ije/ibe/sD0/iTe/sL0/sT2+mjk + /ETg/sb0/sb2/sj2/Ebg/sz2/Nr49PT0+N70/tT29ur0/Hbo/iDc+Jrq+sHy/LLx/vr8/Mn0/rDz/Pr7 + +vb5/Nb2+fL59ub08/Dz9Ojy+df09Nzw+Mvy9NLu/ML0+r3y+MLw/Kzw+Lbu+q3u/KLu/qDw/rn0/vz8 + /PX8/N749vL2+eL2/tb49u31+sfy/Ljy/Mz1+Lru+qzw/KTu/nDo/HLo/hzc9tHw/N7399Ty/OX4/t74 + 9OLy+MTw/K7w+rLw/LDw+rTw+rbw/q7y+PD3+Nry/jzg/pzu/Fvk/FbkAAAACP4AAwgcSLCgwYMIEwpE + lqEBrwYZRCmcSLGixYsYBSZjQ4PGhRASM4ocSVKkNgcoHaBDVrKly5cCRdFI6eAOS5g4c1akQBPlTZ1A + gw4M0dPBT6FIDVJAFvJig6IURIo6mrSiKFhBDgxrWvFpz6gXRRFTp4dehqoWycVZG4ccRq80wVptA6Mu + jDNy0SIUYIRtHD0onEK9CM6SXRgO8uo1KMDvWjZcFcJNqTghhSKHYViqvJjgG8dxdlmcjJLzwWiZYdCJ + 3JngO9CEqCIknbjrncyWwLVOKMoa6GldB08UhSY15N0J8/X1a2QZRdqmCf5ykNlBK+QKh4Ge91y4ZUep + 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 + EwpQFq82qDbLd0KLykZB5DP3jwo2YAMbVKBrOdcJBXhhggHUdOO7CQgAIfkEAAUAAAAsAAAAAGQAZACH + +Gzk/v7+/vz+8PDw/krk/hrc/gra/prw/Kbu/Izs/Cze7u7u/MD0/KTw9vb2/mro/ire/hba/qry/KTu + /HLo/Pr8+Pj4+nDm+lTi+kzg+pDq/Pr6+Hbk+G7k+Lbw+JTq/Pj8/Pj6+Pb4+PT49sbw9p7q/N34+sfy + 9NTw9L7s9LDq9vD19PT09Nrw9PL09PD0/lrm/hzc/KDu/J7u/Jru/Jbu/JTu/Fjk/m/o/jDf/hbc/Irq + +oLo+mjm+Iro+pbs+H7m+Jzs9qjq9Mju/Jjs/JLs/I7s+pzq9+Hz/IDq/Hrp+pLq+Jrq9qLq/Hbo/HTo + +njm/hja+nLm/lrk/kzk/ELi+lri+lji/D7g/Djg/DLe/NP299by9tby/jri/Mz09tDy+Mby9s7w9srw + /Mb0+Mbw/ML0+7fx/j/g+Lrw/kbi+6/w/Krw/Kjw/qjw/qHw/Kbw/nrq+orq+m7k+JDp+Ibm+nro+mTk + +qbu/ITq/H7o+oXp/oDr/oru+nrm/Ibq/pHt/Gzm/Ezi/ETg/Drg/DTg/p7u+mzm+pvs9rru9rDq9M7u + +Kzs/mHm/lbk/GPl+LDu/F3k+qjs/lTk+qju+q7u/rHy/lLk/lDk/rr0/k7k+rLu+LTu/Efh+Lbu+L7u + /ELg9r7u/r/0/Pz89sDu/uL5+qXt+vD4/Izq/ibe/E7i+tz2/iLe/iLc/sb2/FDi/iDc/FLk/h7e/h7c + /FTk/O759N7w/vr8/sr2+qDs9N7y9OLy+tDy/sz2+KLs9Oby/s72/Ob5/jrg/jjg/kTi/pzu/tD2+vj6 + /OL4+sry9/D3+OH2/Nj3+Nj0/M/2+Mry/Mj0/MT0/Lzy+MDw+rLw+qzw+qrw/qbw/rj0/sP1/vz8/ur6 + +vL6+uT2/sj2/PT6+tf09Oj0/Or6/tD4/LLw/LTw+rbw/LDy/njq/jbg+J7q9ur0+rzx/ors/pru+uH2 + +On2+r7y+ub39Ory/tj4+sz0+M7y/LTy+sLy9O7y+8Py8vLy/MTy9O30/nTqAAAACP4AAwgcSLCgwYMI + EwqsYMGBQwujFEqcSLGixYsCQ8Dx5w+BiIgYQ4ocGfLag5MP2FQgybKly4U4UD7wt/KlzZsUj8k8WROn + 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 + B9nADXSbggPCEbPjxbBtUX0QcMiisIP4Mo3IfFUAbmXTCKmAHXRbAnvepAJWpXOKWQRshMADAoVVwDrI + B+i8UkwhpjYArRGSC90CwdguuWxeGQ2cZiNEBFUorCy4YCJgYPZK0TE3Qj6h20e82ScCwKwxmPJiZz1i + 0uAmySiSbVP0xtvIFVkFLMaag4CT5L82DfDBO6trdR1YqEu4t09GUQpc4GKgoOl3TkeYIAhBqGPKlWQ2 + wi0iSAdo3IDqpXjKjRJzwIJ65jexgA46GwFE4zwkFphDZynw8p+Hpx8A6OoFRmB0n2yABQOIOgtorJqA + AAAh+QQABQAAACwAAAAAZABkAIf2guT+/v7+/P7u7u7+ROL+JN7+BNr+lO78kuz8RuL8GNryyOz8lu74 + +Pj+aOb+NOD+Btr+pPD8iOr6dub6+vr6TOD6Qt76ou74lur6+Pr4fOb4ZuL4sO74lOj89vr2jOby8vL2 + luj+FNz47/f27vX08PT07vT01vD00PD0sOr0puj08vTy0O78xvTw8PD06PLy5PDy2u7+VOb+MuD8cuj8 + QOD+dOr+OuD+Etr8kOz6kOr6XuT4gub4qOz4cOT4lOr0tOz+Gtz8juz4puz4nur+ZOj8jur8jOr6gOj5 + 1vT20vL2our+Itz8XOT6eOb+SuL8VOL8TuL8SuL8SOL6VOL6UuL6TuL+Jt78Nt703PDy4PD8Mt78Ktz8 + JNz+Dtr+DNr+Ctr+CNr02PD6uvD2xu/+QuL8rvD2v+74tO76svD6rO78qvD6qu78p+/6pu78nO7+ovD+ + oPD+mO/8mu78mO7+e+r+a+j+cur6auT4i+j6VuL8ger6luz6huj8ZOX6gOb8VuT8UOL+hOz8eOj+iuz6 + iOr+ku78dOj6dOb8ON78Lt76jOr6jur2rOz0uur4ouz6ZuT6mOr+Xeb+WOT6mOz6muz6nOz8YOX6nuz8 + Xub6oOz6ouz+qfH+VOT+tPT2tOz+TuT8RuD+TOT+tvP8/Pz++vz0wOz+yPb8Ot78POD6XOL+MN78WOT+ + 0fb+Lt78WuT82vf+LOD+LN7+Kt7+KN7+2vj6aub8aOb6buT6cOb6k+r+5Pr8aub6cuT8bub32/P84vj4 + 5PX6xvL8t/H+5vr80vb+INz24/L++Pz8+vv8+Pz58vn29fb09PT8yvT07PTy6vD53fX31vP04PD6v/L4 + zPD8svD4wvD4uO76tPD6rPD8rPD8qPD8nu7+nvD+sPL+wPT+/Pz+zPb+1fj83/j+3/r42vT86Pr55/b6 + y/P8v/L81/b25/T4uu78oO78ovD+uvP85vj+xPX87vny7vL03vL20PD8sfL6tvD6sPD8pPD4yfL6uPD8 + 9vz88vwAAAAI/gADCBxIsKDBgwgTCkzWoAGzhqQUSpxIsaLFiwIzbKtTx0yDiBhDihwZkpONkzbMgCTJ + sqVLAXVQ2qiz0qXNmxUzyDyZDKfPnwgb7LTRE6hRhcmS1aQodGdRjE+PWiTV4o03Z0slNpUZdSIpeHsE + jaEg1SI4QWgFqbu4FWVXiWaKyC2S5m3Zg0LSCiI0z2JbnhaDOZhb5A7ZuxL1ot3md6jdgxQQEC5i5zDi + hJwUCyJXkZnjikomF2FwWSI6zXIeF/TslCKzOpMdoCstcZhmZ64/Txwjeg1ticvy6hXiYSJrrhNJ3Jl8 + p/hvhek0wzOuO2GyC6LXPlfYYI7mcxKP/ruVGE30HtXbBUbT3CYrQfGAEzY4InpdeonJ1miGpRA+Ueii + zeHefQSdptgBqvmXIGyEOQAegQqRQo1mLSSkIEKkZCNaPhBOtIwhihHiD0IXHsSOHZPZkEGHE0Wn2D0k + VkcQBW+IVuFlpCQlUgNxaBbPQSUWZI5oEgwYoQACXCRAC97IMUpfGAGjGRxZ/fVfQQ1IIBpnFyUTjQQy + FLFGkhS1csCZB8gBjnMVJXOPZsUYZOVbwojWzUWkkFCJDHzyOUxFnKCJpjfFGGnQCIQoZogyWMoYwAg2 + TGYHlK6hUUSffdZhmUKBCoomJ8BYNKFi2g00J0HJqCHan0xVQwOm/phqWqannj7J1AEhUhrAqQMFgyJh + dayI3zFgwoopPugRJI4ctKIpRwtsJpSEZmYQZOVKyfAhWjoSkTLCBZMY2+ckaiRb0DzDNItmHOsYSkEE + moW60FArJSGaEOgxw5u4fQoxW0ikANOGumeu8SBCwWj2BkgwyVTHQMnsIZq8kCkxCL982lAoSxQU4w3B + B8Az4kGk2KYYfwJ9I1O1AjkjWhsYspMDxmGOddMy6jCrrhzOMGrQMonqdWMAypjBkRnCBvANc8scREIP + ktDszgg/kYLOPSDDAUtWLSg2zkCkUNBQA0+dMhk1cl5jB80SBGMoS8nAEgHIZhwsECnwohUB/pkKCSDE + XDlERcEvddBsRzpv25SBMzqrq060AowDzjiJL9TCNi08lScfNEuSRtKX4QyyN61UblEpaUSNMSW60ma1 + GSB/Y/pEpRSO8R7RzG4UBUnAQXAtN6mDsQPCmFtaAy00Lqg8N8UhriRqRMuiQCPAQyvwNoFjLAJ2T09Q + wJ0e8A3fLSmzR5+DtOt93+SIU85PybSwBlbr12///S6Rov/+/Pfv//+XYQgzBkjAAhrwgAUkG6pOwYkI + OPCBEIygBCXIiVOQzyfIQAAoCMDBDnrwgyAEISgQ4JxTTPCEKJzgKY6CgBC68IUhRIBSGpjCGqKQE0Yh + xQZhyMMXgoIs/jS0oRAhiEOgJGOHPUyiB0HRgACYcIhQjIA4cthCJVqRAAggiwDEEcQonpAT4rggTjKg + wSvycIQjiEoyKMDGNrrxjXBso1IQs8ax2fGOeMzj2ChgPPz58Y/1I0YtdvGTUsCDDuroY4fmIQ9PONKC + N1GGAx5AySbso4l+9IA4HMlJTxDSJtagpCgf4IR6bGp6pajFKDrJya/ZRAijFOUNMGCC9e0CHqzs5Cdd + koZYjvIJaGDGIhuZS06u8CYZ4IUvR+kHe2DyOf1oRTE5CQ8uQawlGWhDGZYpSiQcQ5H5U+U0HVmMaC3j + DbnohvQSkgEzSMAMpSDICCTATUrWgAgk/rgMMcAxTk+conUNqIEBBkqAeEokA00ogEIzATpSJOEW9XxA + IM7wTKD44xT9BEc8lkIKRgz0o4wJACka0AwQNKMBlkmDQld6gaVQgBpRiKgtTlBRmyBDmuMcBSzsEgwR + fHSgErjbCBDBBCYMQlikEMJKFfoAdhwkA5jYZj37wA7dJUQA5MDlOFuxzoVA4qcDTcNCBiGCsopgEAMZ + w1IVqoGaDgQdSIgoAXqwgpacg5/jlEfrClINCIA1EYdJBhPMKgImFCUDUVjrFVBgnWJcIqKAIINbKeKP + TY4THMBAT0DBagBTDIQChC3rM/ex1gI0QZgJoQAanhDRQ/xidgIQ/uc0dWquHnBWFc9sQGhFgNoAJMMX + pQXC25bBhxvU8wZzECNCatFPcXT1UAUAqxdiYK3dNoMgwYjFWstQAooEYxARhRlFiJlLvVaOFDzgLAA2 + pdvQguB7OigtBoyXjHSwgpuXqBx5qUmO2UXDC2DtAjSwtNv3EmQZqVjrKqZhkVQRwJeXMB45OjmKcfis + IhTYAWdVsJT2EtbABOFAaScw2YSMwAjGFaUaLFILeIxCHCO7yDU4a4HrEti9BqEAIEpbDYyQwhyHuEEC + MHHKozQAC2CFwAIO4mGzgpggSigtFHrb4IYU+SiY4OwGJtvksj4ZbBMobSOs+pwRBAGsItBC/lAKjBB0 + rGKtqXjGH0nhBM5+4Mq7YjPJIlHaPOCZReb4Ali5QA/56PkgyyDAWmWRBfxRAAqcTQF3Dn0Qta4VDyWG + EDY4WwEqy4nSOd5Bac9gvwYoAqxgYOykcayQJFxhrZ2u3w8464NMC6TLIviyQUixh9IugcxHQYdPf6oA + GEwE17o+1APWigWnsigZuOBsCMyFbIpcoLQ8AOdRlBAGsG6hrscGdVCesFZaSKNDyYgCZx1RkWpT5Bql + 5QWwccKMbv9UD7auLqsnkoxbrDUW87YJKZgx7IF6QQx+EXdCojGLpd4g4PlrQHo/2ofZuZsipIjvSikB + cZsw4weoSAUdS8B5cYpQgAhQgMIj/vybkWo7z/vGMEQAyZTdeprmPsF1vnFOkgYYg7BMYDnPe44Iwg6i + 40OHjAmIyoQJNC3pP6EAM0AwAJPu/C4BAQAh+QQABQAAACwAAAAAZABkAIf2hub+/v7+/P7y4PD+TOT+ + HNz+FNz+DNr+nPD8INzy0O78xvT8pPD29Pb+bOj+Htz+Gtz+Dtr+rPL8eOj8+vz6+Pr6cub6SOD6sPD4 + quz8+vr4+Pj4eOb4bOT4YuL4mOr8+Pz8+Pr49vj49Pj2xvD2muj89vz88fr61/b29vb02PDw8PD0vOz0 + tOz25fT09PT04PDy7PLy5vD08vT08PT08PL+XOb+Ftz8WOT+fOz+LOD8pO78ou78nu78nO78mO78lu78 + jOz6guj6YuT6oO74huj4duT2kOb4ouz07vT2ruz0wu7y2u782/b8gur6muz6luz4muz+cun8e+r4pur4 + mur2qur8fOj8euj6euj6eOb6dub+UuT8ROL6WOT6UuD8NuD41PL8Mt7+NN7+Lt7+Kt7+KN7+JN7+MeD8 + Ktz+Etz+ENz03PL+Ftr8z/X6vPD2z/D+POL8yvT8yPT4vvD+Q+L+SuT7tfD6tPD8sPD6svD8rvD8qvD+ + qvL+pPD6kOr6bOb4juj6fOj6eub6XOL6VOL+hOz+gur6h+j8lO76hOj8hez8fuj+jO78fur8iOz8aeb8 + TuL8PuD8Pt78LNz8iuz+k+7+mu7+Y+f+VuT8luz8YuX6pu74puz2tuz6nOz6mOz8mOz6ru76oOz8XOT6 + qO72xO7+VOT6qu74su7+svL6sO7+vPT8SeL4tu78RuL0yuz4vO7+v/X4wO78/Pz+2Pj8OuD8QN78QuD8 + ROD6cOT8UuT6cOb8VOL69Pj++vz++Pz67Pf+5vr8k+z6w/H6fub+y/b8juz6fuj64vb05PL85Pj6rO73 + 8PX+fur6zPL+6vry8vL6+vr89Pz63/b36vb83fj42fT81fb7vvL4zPL8zPT4xPD8tPL4uPD8svD8rPD+ + qPL+uPT+xfb+/Pz+3vj69vr68Pn+6Pr7xfP+0ff65vf85/n48ff60fT+9vz07PT4xvD8sPL6o+z+eur2 + 2vH+OOD+SuL+jOz8cOf33vT8v/P40PL7t/L6x/QAAAAI/gADCBxIsKDBgwgTCqRQYUOKDRVoKZxIsaLF + ixgFVshjyFA3ERIzihxJUqSqHChzdAtZsqXLlwIMpcxhiOXLmzgtUpiJkkLOn0ARVuCZw2fQowopULBZ + UQRRoxmZIq1IYRsDP26kKnTKE2pFAawaNdpXYerFBWLFLsDIdaZXit0cyHXQ761ZhJbSNrJ04mLblHYT + rpMy1wE9aXcp5tWryu9Ti9J+FHYgBXFihar0il1m8W9Pi2EmO/BxeWIwzY38BDbouWjTRZOlsCs9MRtq + N00fU8wmOg/tib6AaAZigmLr1QSb0ZtMz9fviW5QyzKuOyEFPqLTPZ9YwQ9qaBOP/k+kRrhwJeTbBVJD + LUHrQPEJK1gS3ST9RAp5UNdPCB/hM9E9uGffQMEslhYCyPXHmhOxBTPgRLTsg5ox/FVXEC3eiNbNgxT5 + colmlkyDkIIEVVPeXM4Ux+FE2KDGyogWDiQNA6LJQdtSI0njnWazsRajQE2Idgx6CNEigAAYCeCGHwiw + 0ldGyaDWh1QkBlBBEKJRkxEF1ASBiQN7IFkRNgiUWeYCKurUDWoo+NiVQfGIxgBGtFRDBCY25GlDPxap + YqaZfmBDJEEFanYJCAWR2M4UsZ1zUQMZ6qmnE4MG4OefZkrAmUVoabYWQQrSoodo2VhUAT8TSCqpE5ax + iCmm/k42hQCIT2pkoYmTHVLWfS4co6qqb1QagBuvYmoMohOhgNqGAw3FE0sU9CCaOxA2wwmev+aJCQbC + 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 + /Gjm/Gbm+mjk+Hzm/lbk/kjk+mLi+lzi+krg9OLy/Dbe+kjg/Cre/CLc9tTw/CDc+sXx/hLa/hDa/g7a + /gza/gra+Mzy/jbg/MHz9s7w+Mbw/j7h/kzi+MLw/Lby+rzw+L7w+rTw/qby/LPw+rLw/K7w/Krw/qbw + /p3w/KTw/KPu/Ibq/HTo/Gzm/J3t/Jjt+qft+pns+oXp/JXt/n7q/nTo/JTs+n7m+I3p+Irn+nLm+lDi + /IPq/obs/IDq+nzo/Hzq+ojo/ozt/pru/HLo+mzk+K7u9rbs9Mzu+Jzq9qTo+pzs/l/m/FLk/lTk/Dze + /DLe/Cje+J7s+KDs+qju+qru+qzu/lDk+q7u/E3i/k7k+Lju+rLu/kzk/Eri/krk+rbu/rjz9rru+qXt + +Lru/vr89NLu+L7u+qLs+qDu+lLi9sbu/Crc/Dje/Gzo+pLr/DDe/Hbo/DTe/Hbq/Hjo/uT6/Hro/uL6 + /D7g/PL6/tj4/iTe/ELg/iDc9rLs/Ebg/h7c/Irr/hze/hzc/Ejg/Izs/hre+tf1/Nv3+Krs/rr0/I7s + /r70/JDs+Kbs/sj1+O73/jTg+Nby/Mz0/kji/kbi/q7x+n7o/pju/Fzk/Ob4+oDo/Pr7/Nb2+fT59vb2 + 9vP2+uf39u30+OT09tny+s70+NHy/MT0+Mjy/Lvy+r3y+rfw/LLyAAAACP4AAwgcSLCgwYMIEwp0x6Ah + gwkKI0qcSLGixYEe4FCi1A/DxY8gQ4ac9ajko33uRKpcyXIhJZOPvqVsSbPmxAkwS860ybPnQAw5H+30 + SdSgu6EUGQRFSpFp0YgCPqBD98FpQqU5rSIUoObGATVanxYsd6DsgXIWscIMa3BWjrc5UIqVqMfsgUrL + Kqo1yZbgPENwczyCOFdhJbsHZuldSnHCrsA5DPUVOwvxAXZJGU/MBjnHgsIRxVnGM3mvzokMrnXGDFph + PssfUGuOKKZzndYR4dW1q8eDRNNCJYoDHPhRXtwKP1jO93s2QncWOqNFrnACHsvHrzo/WK/zjcm4Yf5Y + hhMReNgJlTproy7RXWXEMBSaV3iv8x7wyMUdtktH63yESkFmiDjsURQObNpl9Rw/ne1TIEUYLICYHtkV + 9J9Bw0H2iEcPbmaZGgBut1AEnd3T2lEgTYCOZaxZKGIAMHSmDn4ECSCARVGhQ4caFVI0j2X+MHXhQBMU + 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 + 7vai6vaY6PTI7vS+7Pry+Pfi9PT09PLy8vTy9P4Q2vrX9fTw9PTo8v5Q5P4u4PyS7PyO7PyM7PyK7PyG + 7PxM4v5l5v444P4O2vxy6Pp+6PpY4vig7Ph65vhq5PiW6vaw6vae6v4Y2v4i3PyG6vyA6vx+6vqM6vqG + 6vam6vPf8P4g3Pxq5vqI6PiQ6Pxo5vpo5PbC7v5O5P5C4vh25Pw84PpO4vwy3vpQ4PfM8f4i3vww3vwq + 3vTU8P4c3P4a3P4Y3P4W3P4U3P4S3PrA8fy78vLO7v484Piy7vqx8Pa+7vyn7/qs7vqm7vyi7vqk7v6V + 7vye7vyc7v5w6vyW7vqQ7Pxt5vyI7PyJ6vxu6Pxw5v526f6A7PqD6PiC5viA5vpw5PpW4vqA6P6G6/6O + 7Pio7PiY6va27PqO6vqQ6vqS6vqY7Pxg5Pw+4Pw44Pia6vqc7Ppg5Pqe7P5W5f5M5Pqg7P608/xX5Piu + 7PxP4v5K5P5K4vz8/P76/v7E9P5G4v5E5P5E4v7c+P4s3vw64P4q3vzy+v4o3v4m3vxA4PTm8v4k3vxC + 4PxE4Pil7PxG4v72/PxK4vii7Pzb9vrn9vfD7vx66PbY8vrN8/p45v7O9vbw9fzF9Pyy8P6w8vz6+/r3 + +fzW9/r0+vjn9vrg9vTt8/Lq8PfQ8vTa8PrE8vy+8/TO7vi67vq48Pyr8Pqu7vqo7vym7v6g8Pyg7vya + 7v649P78/P7J9v7k+vz1/P76/Pzj+Prt+PjA8Pfb8/rV9P7W+Pfy9/zL9fy38vqs8Pyi8P6u8v5w6Px2 + 6Pzf+P6x8/zo+fTk8vTc8vq68Pyu8PjG8Pq+8PbK8Pq88vbo9Prb9fx06Pq28P6e7v638/74/Pbs9vrj + 9/699AAAAAj+AAMIHEiwoMGDCBMK7ERBg0MKCiNKnEixosWBHszEibMM4sWPIEOC9Heg5IFlnUSqXMly + YRyTB+KkbEmz5kRgMEvOtMmz58BgOQ/s9EnUYKehFIHmREqRadGIAoRduiTMaUKlMK0iFNCrWrVeHp9K + 3DWt7LQPFrGa1HpwWZ+3fVCKlRjB7LQIpyqq1VmxFty3M4DNjWi3rD+9QdkSBMbmbx9Cggcn9Fd4GjuK + GhJTZOG4TwTJCrtVXqdYYOalEzXE6XwZdMJelcOl1ixRWWczrhUOq2s3gjaJp7NKNEHD8YxhuRUKq4wN + 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 + N4yoAT3QxAtLMMAwjVf2Ak1oAjTCXRSGRPnZEnGav99oEECfm+YqAbSOcQ4SCuCgsGhgOc895YfConXo + PAEGL4KKhiIUFek1AUYDUDAAL+78KQEBACH5BAAFAAAALAAAAABkAGQAh/aI5v7+/v78/vLq8v5K5P4a + 3P4K2v6a8Pyo7vyO7Pwu3vTC7vy+9Pyk8Pb29v5q6P4q3v4W2v6q8vym7vx26Pz6/Pr4+fpu5vpO4Pqs + 8PqU6vz6+vj1+Ph45vhs5Pia6vz4/Pz4+vaS6Pai6vac6vzl+Prw+PTk8vTW8PTK7vfk9PT09PTa8PTy + 9P5a5v4c3Pyi7vyg7vye7vyc7vya7vyY7vyW7vyS7vxa5P5w6P4v4P4Y3PyG7PqI6Ppm5Pqg7viG6Phw + 5Piq7Paq6vTQ7vTu8vyW7PyU7PyS7PyQ7PqY7PqW7Pic6vx+6vqW6vam6vvB8vx66Px46Pp+5vTo8v5W + 5v4Y2vpw5vh+5vTm9P5M5PxK4vpY4vxE4PpW4vw04PzS9vwy3vbT8v4W3P464vzK9PnA8PzE9PzC9P4/ + 4fzA9Py28f5G4vqy8Pyq8Pqw8P6q8P6h8Pyo8Pym8PqM6vpo5PiQ6vqE6Pp05vpg5P566vqI6vqo7vyC + 6vyA6P6A6/6K7vp+6PyE6vyG6vyI6vxu5/xW4vxG4Pw44P6Q7f6c7v5i5vxi5fi47vbA7va07Pqe7Pig + 7P5Y5Pxc5Pik7P5W5P5U5PxT4vqq7v6x8v5S5Pqs7v5Q5P669Pqw7v5O5PxM4vqy7vi87vxE4v699Pz8 + /P7X+Pw64PbE7vqk7vxI4vpu5Pa87vrY9f4m3viU6P4k3v76/v7p+v4i3P4g3Pi07vp45v4e3v4e3Piy + 7vxY5PTe8PyN6/7F9fTg8Pz2+vfv9frK8vTk8Pzs+vri9v76/P464Pbe8v7g+f72/P7K9vLy8vr6+vj4 + +Pzn+vr2+vfp9vrE8vza9/jU9PzO9vjI8PzG9Pu88vq28Pys8P6o8P629P7B9P78/P7b+Prd9v7s+v7H + 9vz2/Pfx9/rS9Pzx+vrm9/jb9P7k+v74/P7Q9vjM8Pq68Pyu8PjM8vyw8Pyy8v424PzW9vjE8P5E4v6K + 7P6a7vze9/bK8PrG8vq+8vy+8vrA8vy89Pr2+AAAAAj+AAMIHEiwoMGDCBMKLMXMQjMLFRRKnEixosWL + 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 + efNU41wQa1j2DlPmUDRW64MlUznM0ImCZddxvGdwIsgoqI6fE/INLWT1AZbuTipW24NI9xfT6qmBZUMh + VJ6YgAxNJQMVdjPqhJigCmZGc4NKcYfVPgG+vU5IGyyrBDv3pBV5luclUr2lZPfnAVn9BF/rU4oHrBYV + k7E2QoJh2T4IyALRhsAVMitqC7etD1nVRK1ZUopm4LqhOjhGXfxLEWI816IumPdKsAfehiohtPymSCpu + WkgBNYMSOHABDZGS8IlU4A05yIGOOcQQYSo6thipgAUzXO3GolW85DN5rWRJjnKVMIMedGWDs1u+PDrQ + lRc030kFxhFTNvBirDm3nQOQGEUU/yUgACH5BAAFAAAALAAAAABkAGQAh/Z+5P7+/v78/vLY7v5E4v4i + 3P4E2v6U7vyU7vww3Pwe3PLO7PyY7vj4+P5m5v404P4G2v6k8Px26PqM6vr6+vo+3vqk7viW6vr4+vr4 + +Ph+5vhu4vhk4viu7vaU6Pz4/PzR9vaK5vbA7vLy8v4U3Pjv9/b29vT09PTm8vTM7vS46vSk6PbY8vLq + 8PLk8PLe8PTy9PTu9PTW8PLu8v5U5v4y4Pxc5Pp65v506v464f4S2vyO7PqU6/pW4vig7PiE5vh25va2 + 7P4X3PS+7PSu6vyB6vbO8P5k6Px66vqQ6vna9Pii6via6viW6Px46P4g3Ppu5v5o5v5I4vxI4vpw5Pw2 + 4PpS4v4k3vpE4P4i3vw43vwy3vwk3Pwi3P4Q2v4O2v4M2v4K2v4I2vq28PfO8fTS7vy+8vbG7v5C4vjC + 8PbE7vi87vi27vyu8Pqw7/qr7vyp8Pye7vqo7vqm7v6i8P6g8P6a7vyc7vya7v576v5r6P5y6vpe4viK + 6PpU4vpK4PqH6fpy5Pwo3PyY7PyI6vx86vx66P6E7PyA6Pp+6Pp86Pp85v6L7P6S7vxu5vxO4vxC4vw6 + 3vw23vyK7PyM6vqa7PqS6vyM7Pik7PTG7Pau6vxk5f5b5v5a5Pqc7Pxf5fqe7Pqg7Pqi7Ppa4v6p8f5Y + 5Pim7P609Pio7Piq7P5O5P5M5P5M4vxM4v629Pz8/P76/v7G9vw74P7Q9v4r3vpo5PxE4P7c+Pzu+fiQ + 6vxQ4vxS4vxU5PrK8vxW4vyU7PTs8v7m+vxW5Pxa5Pbu9fyS7PTq8vzZ9/Tc8vp05vp25vjj9P76/PTg + 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 + hBoOUCsnnlERbGqTlYewUSgwSYsfGCN20lDEX4NgEAIX1sACycZif+Hcg1DDHvXsZDZwyIMc0EIPLCjx + Qdrx102AOHntvWYhFquNj2QnCjgYQ+xeQeQ6SsMGfy3DQTxc1hsHQAmLRYJ5tUWBhzjGDX/9AV+ZTFYn + v+IXi/WGIEsgha+iAQUI4TL+AZwcAFxwQq0OoKr5ePDXJZxRzWwOgBsWawGaygcb//3pLmBwvRznRw9q + 7cQyzJfYv4pAIXhWSC8WO4Epd6cbf4XClCOdEGkwVK2fHV8DShFdFkSE0wnBxmJxkOfoyOGvlT61oRHy + ijssVsjIwwUBvkoAd0gE1Xc5ApybqqBXTOCvqdAQsBMyhsWC4ps0UUKgQzqMVg9k2QjBwB4WizD5vMIR + fz0DRbCNkHYsdhL/acC0DSoBS1971kKZhIKhfaNdhzQH1WAXvBUSjzer9Aj0XkkD0BvSoOq7wBZ5r0o1 + mW45fGITbxAfuTsdDiQgYQx+TlwdJ97phggyNLt198ctQ0KBkI98J9IgbFlVkfGTrygJhf2Fy3UijXcQ + VRWW0OfMVRLDE4zgBCn6T0AAACH5BAAFAAAALAAAAABkAGQAh/aO5v7+/v78/vLe8P5M5P4c3P4a3P4M + 2v6c8Pwm3PLY7vzE9Pyi8Pb29v5s6P4e3P4O2v6x8vxy5vz6/Pr4+vpu5vpG4Pqs8PqY7Pz6+vj4+PiE + 5vh45vhq4via6vz4/Pz4+vj2+PbC8PaY6Pz2/Pzy+vrh9vTU8PLq8vTA7vSw6vfq9fTw9PTW8PLk8P5c + 5vxW5P587P4s4Pyo7vyk7vyi7vyg7vye7vya7vyI7PqC6Ppc5Pqk7vqe7PiE6Phy5Pik7PTs8vao7PTI + 7vS67Pza9/qa7P5y6fx86vx76fie6vao6vx26Pxy6Pp66Pp85vp05vjV8/Tm8v5Q5PxC4Ppa4vpQ4Pw6 + 4PbO8Pww3v403v4u3v4q3v4o3v4y4Pwo3Pu98f4g3P4U3P4S3P4Y2v4W2v4Q2vzO9fi/8P484vzL9PbK + 8PzG9P5B4v5K4vq68Pq08Pyy8Pqy8Pyv8P6o8fyq8Pym8P6g8Pyk8PqS6/pm5PiL6Pp86Ppk4vpY4v6G + 7P6C6vqK6fyW7fx+6vyC6Px66Px06PyA6vyC6v6M7vyG6vyI6vxr5vxK4vxA4Pw43vws3P6S7f6a7v5i + 5v5W5PyY7Pxi5fqq7vqg7Pim7Pay7PyY7vq07vpg5Pxb5Pqk7Pi67viu7P5U5Pqs7vqw7v689PbG7vi+ + 7vxK4Pa67vxE4vTQ7v7A9Pw+4Pz8/P7V+Pw83vps5viS6PxM4v76/v7l+vxO4vyU7fvE8vxQ4vyR7PxU + 4vTa8vyO7PxU5Pqy7vTe8vr0+PqA6PrM9P7K9v74/PTi8vrr+Pzn+P6A6vjc9P5+6vbS8PjI8PLy8vqo + 7vrQ9P649Pr6+vz0/Prl9/jx+PT09PXq9Pzg+Pja9Pbk9PzA8/zU9vjC8PzM9vzI9Pu28vq48Pyw8v6s + 8vys8Pyo8P6k8P7D9v78/P7f+f7p+vrJ8/r2+vrO9P7R9v76/Prw+Pzr+vji9Pbg9PjM8vrX9fi68Pbw + 9fqm7v566v444P5I4v7b+Pj0+Pf09gAAAAj+AAMIHEiwoMGDCBMOnCCt4QSFECNKnEix4sIzESKceWix + o8ePHs+MGznOHciTKFMSjEByXASVMGNSnNBypMybOA3SrJmzJ86dLV35HEqwFjFi2CwCJSm04quM3DgS + hZiulNVSSWfWHNd0ojcEYBGc6ToV4bmrpViR0FqTLER4d8IioOO2bEFWaEuZnLh0ZF2ErkrJRXDnr12B + r/KWijeRwlbDBrENRlDqcMISiokJkOi4Ledxg++UsJywiOJanz1H5DaZGOmEH86iPSc1YeegEYOZG0zn + w+uEtRTzi3ibKURXCyYX+Z1wAjHFoxUW9wvx2OQIm5kjjKd4r+3HCif+gJuMTLtCd4rVSQefUPLgyuYT + TsOLVvN31Qcp0AkdPT5Cfoqlcx9uCJ0xmTf+hSfbVef4lh97BsHDAG+1DQUZRMHl9QpC03F1kCusKGcX + BQ00IE1HrjyXV38EdfgXMtddGBMFPjwghh/MVBgRZnm5phOEA0kTwWSMVeTKMREkIggbFOVxwJMHjAHF + ChYllpd6BfXlYUFFTLaARdWUksiYY54hkTSOQAllGB6EQBE79DGY3UJABqDfYAxMQxEFwwhCJpl0RTRB + mmpCecUpJ0qETYBZ1qnGZN5BNIEJd/z5px06HlRJoYXuoIyMAUywoFVqEaRlQXANNk6mBR0pjqX+lo4l + 0QSBiMEplGIIU01EyCi2IZ08DRTYZFkpFMw2kcBKZiTfgCpsCzvcCmUCQFCgkCtnQAdsSwRRM1k0EFEA + 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 + WOT+SuT6auT8Ot76UuL6ROD6zPP8NN78Mt78Jtz8JNz+ENr+Dtr+DNr+Ctr02PD20PD+NuD4zvL2zPD6 + wPL4xvD8uPL+PeH4wPD+SOL6uPD4vvD+pvL8tfH6tvD8r/D8pvD+pvD8ou78luz8juz8iOr+nPD8pPD8 + n+78nO7+fur+duj8lO76re76ien6XuT4jOj4eub8gur8euj8cuj6gOj6dOb6VuL6TOD8iuz+huz8fOj8 + gOj6fub6muz+je3+mu78ZuX6mOz2tOz0yOz4ouz4mOr2tOr8QuD8ON78LN7+X+b+VOT4qOz4oOr6nOz6 + nuz8V+T8U+P4quz8UOL+UuT4rOz6sO74sO7+TuT+TOT4su74tO72vO74vu7+uPT+/Pz+x/X8QOD2xO76 + qe76pe7++vz0yu7+2vj00u76YOL6YuT6ZuT8ROD83/f4juj8RuL8SOL+JN78SuL+Itz+4vr+INz+Htz+ + HN7+HNz+Gtz6eub04/H87Pn++Pz62vX58fj8gOr81vb+NOD45fX8wfP+RuL+rvH8lu7+mO7+1Pj8nez+ + tvT8+vv8+Pz69vr39fb29Pb66vj8zvb28Pb06vT43PT24vP60fT21vL40/P6yPL4yvD4w/D6u/H8tvL8 + svD8q/D+pPD+vfT+0fcAAAAI/gADCBxIsKDBgwgTDtR1bsI5XQojSpxIsaLFgQLI9evH7dzFjyBDhiSX + q2QuciJTqlxJ0GTJfixjyqwowGXJmThzGjxnM5fOnwnPqWPA4CJPm0CTEmQwpEsXKkUrHnX5UVhJYRCV + RtSFwIBXAxmyTpxq8qI/O2jtoNSq8ByNrwbKwJPa0+K1tGjDiWW70wxcAz8mUCR7k6KuXXjR7uVLUBer + vwY4DK5LcUViO7sYJ1Rz5m8YEWMpS5xw57IHzQgnaIDMKTTSidwu+0Od0AS1v9RGSCTsU6KHcInDoaON + 8FwIyFg8thWt0MVlYcQTlgjz9wy9iLwlsrsMQUD0hCgg/ueIWpz5wXP9Ll/7nlBdJMi1lr9OiOxyZvYJ + FfiFSwp0+fnnmWMafgkxsAhkiiw2UHYJxZbYbAQm5EQCfxXgzX9UIfRbcMOxpeBEunQAGQXKFcSgQYdd + hgxfEwwl2EUkVPDXA/acZ95A2yUGE1sYIOKANInMUyJFREAmCnkL3hjAORCo99E15hwAiAsVcVLAlQVI + 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 + 9PL09Mru8O7w9MDs9Lzs+fH49tjx8/Dy8uzy8ujw8uDw8tzu8PDw+tf1/lDk/i7g/JDs/Ibq/Ezi/mfn + /jjg/g7a+pTq+mzm+mTk+lTi+Yrq/hLa+H7m+Izo9qzq9OXy9MTs/iLc/Hbo+ojq9qDq/iDc/HDo/Gzm + +oTo+Jbo+Ijo+Ibo/GTm/GDm/k7k/kLi+nDk+mrk+l7k/EDg+l7i9Nbu/Drg+lLg/iLe/C7e/Cze/h7c + /hrc/hjc/hbc/hTc/hLc/gza/gra/gja+rzy9sjv9NLu9Mzu/jzi/LXx+Lfu+rDw+LDu/KLu+qzu+K7u + +qju/pbu+qTu/KDu/Jzu/nDq+prs+pLq/Gbm/Gjm/I7r/H7q/HTo/G7o/njp/Irr/oDs/Ijs+nvn+IDm + +mLi/ofs/Hrq/Hro/o7s+JTq9q7s+obo+ojo/HLo+m7m+Jzq+pzs+KLs+qPs/Fnj/ELg/Dzg/Dre/lbl + /kzk+qjs+KTs+Krs/rjz/FDj/E7i9rbs/kri/kjk/kji/kbi/kTi/Pz8/vr+/vr8/uL6+pTs/jDe/N33 + +o7q/ize+orq/ire/ije/ibe/r70/iTe/HTm99/z/sH0/sj2/ETg/Ov6+un2/Ejg+sLy/vj+9uTy/sr2 + /Eji/Eri/tT4+uP2/Lzy/qDw/IDq+sny/rDy/Pr7+vb5/Pj8/ND2+vT6+Nj08/Hz8u7w+t329Ozy9Nrw + +sDy987x/Lny9sDu+rnw+LLu/KLw+q7u+qru/pzu/KDw/J7u/rz0/vz8/uT6/OT4+N/1/sX2/PP6+u34 + +Ob2/tr4+uX4/MHz/qLw+tDz+L/w+LTu/q7y/M31/nDo/KXu+OT0/IPq/rPz/Nb2/K3w9u319N/wAAAA + CP4AAwgcSLCgwYMIEwrUMA3GABgkKCicSLGixYsYBabgUaZMBWsSM4ocSTJjqgkGUhrYEbKky5cwKZhR + aeBHS5g4c1psQNMABA06gwpFuKLnz6FIFTZroOFmRWpGGySdWpCCjiuYmgC9CJUmBKkY29nS9SoV1Yqp + jmxYu8GHWYtdVX7FuI+ZXWbtzlJsVoTthjDSuEa9qE7fXWag3upF2EyM3w1WnCaMm3Iu2l2HmelbrDDV + pccb5Fk0Mbiiu8zMdnFWmG3M40zOKpL2CnZiM1uZ9albnZACB9CUZJee+Aq1Md4Kh3B5zMUXxdlyayd0 + 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 + ullDRAEKP9V/Fon3RbSBV3NmvA1GEIQ2bCorkAMXG0MEhr2TsueMqJxHIEzlXgAsdBWad+JFF0kzXjvZ + G+w86UqJ7GTjAHWhpCIEd5hrHLJXdapRlQRJClFAAAAh+QQABQAAACwAAAAAZABkAIf4buT+/v7+/P7y + 7PL+SuT+Gtz+Ctr+mvD8qO78iur8MN7u7u78wPT8pPD29vb+auj+Kt7+DNr+qvL8pu78buj8+vz68vj6 + cub6ZOT6SuD6lOr8+vr4hOb4eub4wvD4uvD4kur8+Pz8+Pr49vj2nOj87Pn0zvDw8PD0xu70vuz49Pj4 + 8Pb09PTz8fL27PXy7vD+GNr+Wub+HNz8pO78ou78oO78nu78lu78WOT6wvL+cOj+L9/+FNz8hOr6fuj6 + bub6YOT6muz4hOj4qOz2sOr06vL0zO78juz8jOz8iuz6luz43fT4nOr8jOr8eOr6lur4mur2pur8dej8 + cuj6dub+Vub+TOT81Pb6dOb22vL8SuL6buT6bOT03vD8QuL6UuL8POD8NOD+Ftr40vL21PL01PD8zfX+ + OuL2zvL4xvD8xvT2yPD8xPT4xPD8wvT+QuH7tvD+RuT7rPD8qPD+pvD8pvD+n/D8nO78kOz+eur6jOn6 + cOb6YuT4juj6euj6ou38fOr8euj6huj+f+v8gOj+iu76gOj8fur8gOr8gur+kO3+mu78bOb8VOT8SOL8 + QuD4ru72wu72suz02O74oOz+Yub+VuT8YuT8XOT6p+74pOr6qu7+VOT+sfL+Uub+UuT8UeL6rO7+UOT+ + uvT6sO7+TuT4su72uu74tO74tu74uO74vO7+vPT8/Pz+0Pf8iOr4kuj64vb8ROD++Pz+KN7+Jtz+JN7+ + It7+IN7+INz+Ht7+Htz6zvL8VuT+3fj8hur6fOb6oOz8WuL8lOz83Pb+OuD+OOD45PX+RuL+iuz+wvT8 + luz59fn89vz48vj29Pb08fT28fby7vL8xPL06/T44PX81/f23PL05PD41/T80fb4zPL8yfT8uvL7sPD+ + pPD+uPT+v/T+/Pz+2Pj66fj++v761/X+4/n84fj46/b+yPb04vL6svD8rvL89Pr+NuD+iOz65vb++P76 + 0vT67fj62/b6xvL25vL8vPL8sPL6yvL24vT6vvL6yfT8vvMAAAAI/gADCBxIsKDBgwgTChzB4sSCEyw2 + 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 + bAH6qrUQAmZQkv1qiiIOOiEMOG034mzLDgd1CvHdcYITEgJFnJgcDmzFETjLZw5tOiGITWwD3FyeeXA2 + Afd99GZscNp6MLACUuBslk094onE47SKSHRqgOpXGrBa1gphx2ntUT8/+/UBbVUIshPijh4kFh5zlU8r + asDZDzh42glRw2nhQOmkoIOzTsg0QsC9FE+rdRAuAlArkMDZJlH41BSpx2ldDKANcBYPrK4bvifSigac + mEEI/moljnERdidkvmqFB8IJHFRvYMThCcGHWrGJcDgcYhDdQKlyQwdOkY3ewMuwTQqcRYLxzXBQkyW/ + rrBhLhLr9jbgNK85aAmrg3LnvOQICO0cfi6UVqhgAmSdw1mJflNmLLIpKd9JQAAAIfkEAAUAAAAsAAAA + AGQAZACH9nrk/v7+/vz+7u7u/kTi/iTe/gTa/pTu/JTu/FDi/B7c8srs/Jru+Pj4/mbm/jTg/gba/qTw + /JLs+obq+vr6+mDi+kTg+jre+qDu+Iro+vj6+Gri+Fbg+LLu+ITm/Pj8/Mz19rbu8PDw9oTm/hTc+PH3 + 9u709PT09N7w9Nbw9MDu9LTq9Krq8u7w8uLw8tzw9PL09PD09O/y/lTm/jLg/Hbo/Eji/nTq/jrg/hLa + /Jrs+prs+mzm+lLi+HTk+Jjq9rLs9qDo/hvc9Nrw9NTw9MLs8ujy/Jbs+pLq+tf1/mTo+pDq+o7q+ozq + +JDq9qrq9qDq9OTy/Gbm/iLc/Fzk+mrm9o7m/kji/Fjk+m7k99by/FLi/DTe+mLi/ibe+lDg+kjg/Cjc + /CTc/CDc/hDa/g7a/gza/gra/gja9s3w+rrx9M7u/Lvy9sfu/kLi9r7u+rLw+Lju/K3w+qru/Kjv/Kbu + +qbu/qLw/p7w/J7u/pju/Jzu/nvq/mvo/nLq/Izr/HTm/GHl/Frk/ITq/oTs+n3n/Drg/Czc+nbm+lTi + +H/m+nLk/Hvo+njo+KTq9rrs+JLo/ovs+p7s+pTs/pLu+nHm/G/n+JTq/Gjm+pjs/lvm/lrk/F7l+qDs + +qTs/qvx/ljk+Kbs/DLe+Kjs/rT0+LDs/E3i/k7k/kzk/kzi/D7e/rfz/Pz8/EDg/vr8/sv2/Ebi/Dbe + /jDe/GTm/OL4/tr4/izg/ize+lrk/ire/ije/uL6+J/q/HTo+svz+Oj2/PH6+pzs/vj8/NX2/h7e+OL2 + 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 + MqcG+qxBNEQVADhoFg7oUa9up5LWxH6zIrRQrTzJeN9uKiEOarGIP74KVj60VcCfnYoGKJFYtl5EAMMY + LjUZTBFrCBMUGDBBa3HLAM2upiAYJuxYEKvWeuT0d9M85jAsIgA5KEETfyjGjQWSDs3+YSwxHuxYWMGN + 0lorI+NYhR7wAIIV47Z5hNDsPg6SZPYepLeJxQN/l4LU0khDsxiITv6XO/ON0ppDlMkwL1jRi5A1I8S9 + iaUEXTfoY7/eY012Rog8SvsOK0vnHBQeKh8izOX1kuaPHTYuAFlxAM3arM6OVgiNlyqHIZdmGJqVwG4C + PZ6XJvZEAKQAH/zqAG7hJdOoLa03PL0WNmjWDr4idULYUNqH2Q8YfvCrH9ra6AFTBB3vVWsk9nyjOmj2 + HZuC9URAUFo2qI8WDvDrIESla7wwoLT+MJIENDucaBu7IukoLYkbRIFsg/UIOe22Qj7R4RtRINhDpTNV + pE2RceTZ3v4d6v72fW6LgEOtB84mrwkxjZamiN8UYcU68EBlQ2OF1mWEeMRLOEqyaLzjMekyxiRBfiTP + EpYPFid5xOXwWcOq/J8NkENZ5fDRl5eEKQ2wpJVQFBAAIfkEAAUAAAAsAAAAAGQAZACH9ojm/v7+/vz+ + 8PDw/kzk/iDe/gza/pzw/KDu/Ebg/Cbc9Kzq/Lz0/Kbw+Pb4/mzo/jzi/g7a/qzy/I7s+pbq/Pr8+uf3 + +lzi+kLg+I7o+H7k+HDk+Gbi+Mby+Lzw/Pj8/Pj68vLy9qTq9pLm/Pb8/PL6+PT49PT09ODw9NTw9MTu + +PL29PD09PDy8ury8uLw8tzw9t/z+OL09O708u7y/lzm/izg/G7m+oDo/nzs/kPi/hzc/hrc/Jzu/Jju + /JXs+qHt+nLm+lrk+KTs+Hrm9rbs9qLo9O7y9Mzu/h7c/Nj2/JDs+pzs+pbs+Jzq9qzs9Ozy/nLp+I7q + 9Oby/D7g/GDk+nDm/lDk/Fjk/E7i+mjk9OTy/Eji+rnw+k7g/Ezg/DTe/i7e/ize/ire/ije/ibe/iLe + /DDe/Cze9tDw/hTc/hLc/hDc+Nbz/hja/hba/hTa/hDa9Njw/Mz0+Mry/MT0+MLw/MTy/MDy/kzi/L70 + +L7w+7Pw/Kzw+qzw/qry/qTw/Krw/Kjw/p7w+o7q+nzo/Dze/oXs/oDq/Hvp/GTm/Fbk/Ezi+nbm+Ijo + +Ibo+lbi+oPo/ozu/Hbo/pLt/G7o/Jru/Jbu+LLu9rju9NDu/JLs+J7s+Jbo/pru/mPn/lbk+qbu+qDs + +pjs/Jjs/GLl+KLs/Dzg+qju/lTk+qru/FLi+q7u/rPy+rLu/rz0+MDu9r7u/ELg/r/0/Pz8/jPg9sTu + /uL59sru+s30/Gbm/Gjm/Grm+Krs/sn2/vj8+Pj4+n7o/vb8+tT0/Ob49vL0+vP4+Ob2/n7q/H/p+njm + /s72+77x+nrm+u74/PT89vX2+PL49PL09un0+OP2/N349Or0+rzw9tzw+Nv0/NH2+NDy/Mf09sjw/ML0 + +rbw+rDw/qrw/rj0/sP2/vz8/ur6+tL0/sz2+vn6+tz1/Ov69vD2+vT5+Oz2/tP3+sXz+rry9tjy/Lbw + /LDw/LTy/LDy/krk/kri+qTu/nrq9tjw/jzg/jrg/Ibr+nroAAAACP4AAwgcSLCgwYMIEwpEdyLEgBDQ + KiicSLGixYsYBUZrNGYMERYSM4ocSTIjrUk7Uu4Ihq6ky5cwBVYgo3JHmJYxc+qsaKLmjgI4dwodOrCY + T6BEkyKkBQxYUIssjgITWQEdLaUY0QHBgmXIU4pRaxaYehHdplNUKDjAarGJjbc2KGAMq3LsxQqRDOg1 + 4AjYVbYJK0CAa6MWtYt0U9q1mELNXgNmoP0FfLBCLcI2goQEK9UiNEiPDbxZSznhDcw2Olhs0blikdAG + hGwubTDeZcKpSE9kLZbsRBZgQqtpQzvwI9SoKvKu6zvwCNjNZhcvOG0wYQjtKC5X3BzhiyShzf5kn75U + BGpHkxFu/9ndILANsDmRV3giFeZa2Xa3ToiETWhDus13kC2oafFVQesthhA0X8Bmh4AKAWMFaswolGB7 + A9GyAGxVHAghQdjcBhcBASK4n0EzhBHaG+l8qBA6UqAmSkIXIoQOALBFkp6LBkEhC2b8WKDeiQTB4EZo + YzjD40RPoIbDjgLVaJADHMDWS2noOACMdBVBIwRqwxwkZUEqRBBaAhgOhQ4TBOiQSDZQToQLah0alBh7 + Bp2AQWgR0CFSCe7MMs4tF6ECwaEQ6LCEkhcBowxq39hJJC0iwKYFlwp9oMQsr3T6CqEUVaAIoogSwEqJ + CqEg4ls6GFPQnf4KCuSCAqHxcJhFFdwyjqeenlMRLbqQSuondGBqUAWdoNbEqyeiowFsGcR5kDq+8Mqr + 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 + 9Pba8v5m6Px+6vqQ6viW6viU6vaW6PTq8vyA6PqA6Pp45vpu5viG5v5c5P5I5PxC4vpu5PpQ4vw23vpC + 4Pwu3vrN8/wk3P4Q2v4O2v4M2v4K2vjO8vbU8f424PbO8Py+8vjJ8PbK8Pq/8f494fjE8P5I4vi88Pq6 + 8Py48vq28P6m8vy08Pyw8Pys8Pyo8P6m8Pym8P6d8P596v5u6P526PqH6Ppg4viO6PiC5vp86Pp05vpS + 4vpK4P6G7Pqm7fyW7PyE7PyC6vxu5/xN4vw83vww3vwm3Pp66PyI7P6P7fyK6v6a7viz7va67PTI7vTA + 7Piq7PqU6viY6vam6vxk5fig7Pqc7Pic6v5e5vxg5f5a5Pqe7Ppd4vqg7Pxa5Pqq7v5U5P5S5Pis7Pqs + 7v5Q5Pqw7v5O5P5M5Pwy3v5K5Pi27v648/xF4f78/Pa67v76/v76/PbA7vw84PpU4vw63vTO7vxU5Ppi + 4vpm5Pzu+v7a+PxW4vpq5P4k3vuf7f4i3Pqa7Paw7P4g3P4e3P4c3PyZ7v4a3v4a3Pin7P669Pzg9/6+ + 9PyS7Piw7P7K9fLq8vrb9fzK9Pbs9Pp+5vjv9/404Pjn9fbq9P5G4v6t8fx36P6Y7vz2+/z7/PzW9vr2 + +vb29vXy9PTw8vro+PTm8vjf9Pbg9PrU9PjX8/bV8vbS8PzA9PjE8vrF8vi+8Pq78fy68vq48Pyy8v6k + 8AAAAAj+AAMIHEiwoMGDCBMKZFCixYAW69ApnEixosWLGAWSIAQOXBB2EjOKHEkyIzoJDlI66DGhpMuX + MAVOgKTSAZqWMXPqrEiipgMdDHYKHVqQnU+gRJMiRMeAAc6LJo4GNTkhpFKLExKRIpXoacWoNZFeRKfo + 1jgh665aDIWmLZpQGMGqFFsRHaACeAtcYGBV7UF0BNyicQMDqtSLZ5zlLQC0r9+C6NwIRlPOcUK5KelO + ZHBqcYFmUx8jzDEZDRiLIg5XxOS5wCfLogcqkSwYS2iFqcPevtzL8zMlsROiu1DaVcXcc3f/jdJ6Cuzg + ArsFFkzgG0XkmZUblOHAsw7r0Jf+bip94flA7D+1E2SwoTWS8ArZYZnsZttE9JoPAovm+RYG+AqFUJoV + XhmEn3oClWBIa/gAqBAD5ZTWYEIHCsdMa8MU6GBBM9DmFg3qUKjaQSZ04Vkz8Gyo0ARQlJaIiLohNIEg + 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 + + + \ No newline at end of file From cc9998e2a4fdc8009f6143a1fe2248fa3b1454e2 Mon Sep 17 00:00:00 2001 From: James Friel Date: Fri, 13 Oct 2023 10:39:03 +0100 Subject: [PATCH 19/26] revert test db --- Tests.Common/TestDatabases.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests.Common/TestDatabases.txt b/Tests.Common/TestDatabases.txt index 168e37154c..bc2a95b27d 100644 --- a/Tests.Common/TestDatabases.txt +++ b/Tests.Common/TestDatabases.txt @@ -6,7 +6,7 @@ #To achieve this, you can run DatabaseCreation.exe with argument 1 being your ServerName #You can apply a prefix e.g. TEST_ as an argument to DatabaseCreation.exe and include that prefix below if you like -Prefix: RDMP_ +Prefix: TEST_ ServerName: (localdb)\MSSQLLocalDB #Uncomment to run tests with a YamlRepository #UseFileSystemRepo: true From d16ff3bd5ad3b9f434a12623f6b36d136275f5dc Mon Sep 17 00:00:00 2001 From: James Friel Date: Fri, 13 Oct 2023 10:52:19 +0100 Subject: [PATCH 20/26] updates from codeql --- .../DataExport/DataExtraction/ExtractionHoldoutTests.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Rdmp.Core.Tests/DataExport/DataExtraction/ExtractionHoldoutTests.cs b/Rdmp.Core.Tests/DataExport/DataExtraction/ExtractionHoldoutTests.cs index 2c2b6e463d..5770a7a6d8 100644 --- a/Rdmp.Core.Tests/DataExport/DataExtraction/ExtractionHoldoutTests.cs +++ b/Rdmp.Core.Tests/DataExport/DataExtraction/ExtractionHoldoutTests.cs @@ -26,12 +26,6 @@ namespace Rdmp.Core.Tests.DataExport.DataExtraction; internal class ExtractionHoldoutTests: TestsRequiringAnExtractionConfiguration { - private SimpleFileExtractor _extractor; - private DirectoryInfo _inDir; - private DirectoryInfo _outDir; - private DirectoryInfo _inDirSub1; - private DirectoryInfo _inDirSub2; - private ExtractionHoldout _holdout; private DirectoryInfo _holdoutDir; private DataTable _toProcess; From e9f77df235558189faa18e13c20e1f7954b5d5b2 Mon Sep 17 00:00:00 2001 From: James Friel Date: Fri, 13 Oct 2023 11:11:04 +0100 Subject: [PATCH 21/26] tidy up from ql --- .../Pipeline/ExtractionHoldout.cs | 36 ++++++++----------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs b/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs index 09f5d697a3..2e59e7b4e9 100644 --- a/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs +++ b/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs @@ -52,7 +52,7 @@ public class ExtractionHoldout : IPluginDataFlowComponent, IPipelineR //Currently only support writting holdback data to a CSV - private string holdoutColumnName = "_isValidHoldout"; + private readonly string holdoutColumnName = "_isValidHoldout"; public IExtractDatasetCommand Request { get; private set; } @@ -73,21 +73,15 @@ private bool validateIfRowShouldBeFiltered(DataRow row, DataTable toProcess) dateCell = DateTime.Parse(row.Field(dateColumn), CultureInfo.InvariantCulture); } - if (afterDate != DateTime.MinValue) + if (afterDate != DateTime.MinValue && dateCell <= afterDate) { //has date - if (dateCell <= afterDate) - { - return false; - } + return false; } - if (beforeDate != DateTime.MinValue) + if (beforeDate != DateTime.MinValue && dateCell >= beforeDate) { //has date - if (dateCell >= beforeDate) - { - return false; - } + return false; } } if (!string.IsNullOrWhiteSpace(whereCondition)) @@ -97,6 +91,7 @@ private bool validateIfRowShouldBeFiltered(DataRow row, DataTable toProcess) DataView dv = new DataView(dt); dv.RowFilter = whereCondition; DataTable dt2 = dv.ToTable(); + dv.Dispose(); if (dt2.Rows.Count < 1) { return false; @@ -161,17 +156,16 @@ private void writeDataTabletoCSV(DataTable dt) } holdoutStorageLocation.TrimEnd('/'); holdoutStorageLocation.TrimEnd('\\'); - - if (File.Exists(path)) + + if (File.Exists(path) && !overrideFile) { - if (!overrideFile) + + using (StreamWriter sw = File.AppendText(path)) { - using (StreamWriter sw = File.AppendText(path)) - { - sw.WriteLine(sb.ToString()); - } - return; + sw.WriteLine(sb.ToString()); } + return; + } File.WriteAllText(path, sb.ToString()); @@ -192,12 +186,12 @@ public DataTable ProcessPipelineData(DataTable toProcess, IDataLoadEventListener } DataTable holdoutData = toProcess.Clone(); - int holdoutCount = getHoldoutRowCount(toProcess, listener); + int foundHoldoutCount = getHoldoutRowCount(toProcess, listener); var rand = new Random(); holdoutData.BeginLoadData(); toProcess.BeginLoadData(); - var rowsToMove = toProcess.AsEnumerable().Where(row => !toProcessDTModified || row[holdoutColumnName] is true).OrderBy(r => rand.Next()).Take(holdoutCount); + var rowsToMove = toProcess.AsEnumerable().Where(row => !toProcessDTModified || row[holdoutColumnName] is true).OrderBy(r => rand.Next()).Take(foundHoldoutCount); if (rowsToMove.Count() < 1) { listener.OnNotify(this, new NotifyEventArgs(ProgressEventType.Warning, "No valid holdout rows were found. Please check your settings.")); From 2b46c78413db114deef21a05facc251bc21ed35a Mon Sep 17 00:00:00 2001 From: James Friel Date: Fri, 13 Oct 2023 11:26:36 +0100 Subject: [PATCH 22/26] rename to c# style naming scheme --- .../DataExtraction/ExtractionHoldoutTests.cs | 5 ----- .../DataExtraction/Pipeline/ExtractionHoldout.cs | 16 ++++++++-------- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/Rdmp.Core.Tests/DataExport/DataExtraction/ExtractionHoldoutTests.cs b/Rdmp.Core.Tests/DataExport/DataExtraction/ExtractionHoldoutTests.cs index 5770a7a6d8..15b17e8094 100644 --- a/Rdmp.Core.Tests/DataExport/DataExtraction/ExtractionHoldoutTests.cs +++ b/Rdmp.Core.Tests/DataExport/DataExtraction/ExtractionHoldoutTests.cs @@ -9,15 +9,10 @@ using Rdmp.Core.DataFlowPipeline; using System; using System.IO; -using System.Linq; using Rdmp.Core.ReusableLibraryCode.Checks; using Rdmp.Core.ReusableLibraryCode.Progress; using System.Data; using NUnit.Framework.Internal; -using Rdmp.Core.CohortCommitting.Pipeline; -using Rdmp.Core.DataExport.Data; -using Rdmp.Core.Repositories; -using Terminal.Gui; using Rdmp.Core.DataExport.DataExtraction.Commands; using Rdmp.Core.DataExport.DataExtraction.UserPicks; using Tests.Common.Scenarios; diff --git a/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs b/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs index 2e59e7b4e9..a5566aa7c3 100644 --- a/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs +++ b/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs @@ -58,7 +58,7 @@ public class ExtractionHoldout : IPluginDataFlowComponent, IPipelineR public IExtractDatasetCommand Request { get; private set; } - private bool validateIfRowShouldBeFiltered(DataRow row, DataTable toProcess) + private bool ValidateIfRowShouldBeFiltered(DataRow row, DataTable toProcess) { if (!string.IsNullOrWhiteSpace(dateColumn)) { @@ -100,16 +100,16 @@ private bool validateIfRowShouldBeFiltered(DataRow row, DataTable toProcess) return true; } - private void filterRowsBasedOnHoldoutDates(DataTable toProcess) + private void FilterRowsBasedOnHoldoutDates(DataTable toProcess) { toProcess.Columns.Add(holdoutColumnName, typeof(bool)); foreach (DataRow row in toProcess.Rows) { - row[holdoutColumnName] = validateIfRowShouldBeFiltered(row, toProcess); + row[holdoutColumnName] = ValidateIfRowShouldBeFiltered(row, toProcess); } } - private int getHoldoutRowCount(DataTable toProcess, IDataLoadEventListener listener) + private int GetHoldoutRowCount(DataTable toProcess, IDataLoadEventListener listener) { float rowCount = (float)holdoutCount; @@ -138,7 +138,7 @@ public void PreInitialize(IExtractCommand request, IDataLoadEventListener listen return; } - private void writeDataTabletoCSV(DataTable dt) + private void WriteDataTabletoCSV(DataTable dt) { StringBuilder sb = new(); string filename = Request.ToString(); @@ -181,12 +181,12 @@ public DataTable ProcessPipelineData(DataTable toProcess, IDataLoadEventListener if (dateColumn is not null && (afterDate != DateTime.MinValue || beforeDate != DateTime.MinValue)) { //we only want to check for valid rows if dates are set, otherwise all rows are valid - filterRowsBasedOnHoldoutDates(toProcess); + FilterRowsBasedOnHoldoutDates(toProcess); toProcessDTModified = true; } DataTable holdoutData = toProcess.Clone(); - int foundHoldoutCount = getHoldoutRowCount(toProcess, listener); + int foundHoldoutCount = GetHoldoutRowCount(toProcess, listener); var rand = new Random(); holdoutData.BeginLoadData(); toProcess.BeginLoadData(); @@ -211,7 +211,7 @@ public DataTable ProcessPipelineData(DataTable toProcess, IDataLoadEventListener { holdoutData.Columns.Remove(holdoutColumnName); } - writeDataTabletoCSV(holdoutData); + WriteDataTabletoCSV(holdoutData); } if (toProcessDTModified) { From 4fc22a37ae2ae63730ce0e74ef00a37ce4434210 Mon Sep 17 00:00:00 2001 From: James Friel Date: Fri, 13 Oct 2023 11:42:17 +0100 Subject: [PATCH 23/26] add copyright --- .../DataExtraction/Pipeline/ExtractionHoldout.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs b/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs index a5566aa7c3..15a52f820f 100644 --- a/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs +++ b/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs @@ -1,7 +1,9 @@ -using NPOI.SS.Formula.Functions; -using Org.BouncyCastle.Asn1.X509.Qualified; -using Rdmp.Core.CommandExecution; -using Rdmp.Core.CommandExecution.AtomicCommands.CatalogueCreationCommands; +// Copyright (c) The University of Dundee 2018-2023 +// This file is part of the Research Data Management Platform (RDMP). +// RDMP is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +// RDMP is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +// You should have received a copy of the GNU General Public License along with RDMP. If not, see . + using Rdmp.Core.Curation.Data; using Rdmp.Core.DataExport.DataExtraction.Commands; using Rdmp.Core.DataFlowPipeline; @@ -15,7 +17,6 @@ using System.IO; using System.Linq; using System.Text; -using System.Threading.Tasks; namespace Rdmp.Core.DataExport.DataExtraction.Pipeline; From 3e78d49b419adebaae9af957f1a76085145cb64a Mon Sep 17 00:00:00 2001 From: James Friel Date: Fri, 13 Oct 2023 12:58:49 +0100 Subject: [PATCH 24/26] add summary --- .../DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs | 5 +++++ Tests.Common/TestDatabases.txt | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs b/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs index 15a52f820f..b18c3b3f5b 100644 --- a/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs +++ b/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs @@ -20,6 +20,11 @@ namespace Rdmp.Core.DataExport.DataExtraction.Pipeline; +/// +/// +/// Component for extracting a subset of the extraction data into a holdoput dataset +/// +/// public class ExtractionHoldout : IPluginDataFlowComponent, IPipelineRequirement { [DemandsInitialization("The % of the data you want to be kelp as holdout data")] diff --git a/Tests.Common/TestDatabases.txt b/Tests.Common/TestDatabases.txt index bc2a95b27d..168e37154c 100644 --- a/Tests.Common/TestDatabases.txt +++ b/Tests.Common/TestDatabases.txt @@ -6,7 +6,7 @@ #To achieve this, you can run DatabaseCreation.exe with argument 1 being your ServerName #You can apply a prefix e.g. TEST_ as an argument to DatabaseCreation.exe and include that prefix below if you like -Prefix: TEST_ +Prefix: RDMP_ ServerName: (localdb)\MSSQLLocalDB #Uncomment to run tests with a YamlRepository #UseFileSystemRepo: true From 945588af9c5ca1d1d9f0743d556c58acc736b61a Mon Sep 17 00:00:00 2001 From: James Friel Date: Fri, 13 Oct 2023 13:28:36 +0100 Subject: [PATCH 25/26] Update TestDatabases.txt --- Tests.Common/TestDatabases.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests.Common/TestDatabases.txt b/Tests.Common/TestDatabases.txt index 168e37154c..bc2a95b27d 100644 --- a/Tests.Common/TestDatabases.txt +++ b/Tests.Common/TestDatabases.txt @@ -6,7 +6,7 @@ #To achieve this, you can run DatabaseCreation.exe with argument 1 being your ServerName #You can apply a prefix e.g. TEST_ as an argument to DatabaseCreation.exe and include that prefix below if you like -Prefix: RDMP_ +Prefix: TEST_ ServerName: (localdb)\MSSQLLocalDB #Uncomment to run tests with a YamlRepository #UseFileSystemRepo: true From 381441e680a5b2cb0415eb209ffae9e96649f6f5 Mon Sep 17 00:00:00 2001 From: James Friel Date: Fri, 27 Oct 2023 08:19:06 +0100 Subject: [PATCH 26/26] updates from codeql --- .../DataExtraction/Pipeline/ExtractionHoldout.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs b/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs index b18c3b3f5b..4a9682f3fd 100644 --- a/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs +++ b/Rdmp.Core/DataExport/DataExtraction/Pipeline/ExtractionHoldout.cs @@ -93,11 +93,15 @@ private bool ValidateIfRowShouldBeFiltered(DataRow row, DataTable toProcess) if (!string.IsNullOrWhiteSpace(whereCondition)) { DataTable dt = toProcess.Clone(); + DataTable dt2 = new(); dt.ImportRow(row); - DataView dv = new DataView(dt); - dv.RowFilter = whereCondition; - DataTable dt2 = dv.ToTable(); - dv.Dispose(); + using (DataView dv = new DataView(dt)) + { + dv.RowFilter = whereCondition; + dt2 = dv.ToTable(); + dv.Dispose(); + + } if (dt2.Rows.Count < 1) { return false;