2using System.Collections.Generic;
3using System.Globalization;
5using System.Xml.Serialization;
6using LotSizingDataModel.Core.Common;
44[XmlType(TypeName =
"solutionMethodRecommendationReport")]
48 private string _advisorVersion =
51 private string _catalogName =
54 private string _catalogVersion =
57 private string _supplyChainFingerprint =
60 private DateTime? _generatedAtUtc;
62 private double _recommendedScoreThreshold =
66 private string _comment =
108 string advisorVersion,
110 string catalogVersion,
111 string supplyChainFingerprint,
112 double recommendedScoreThreshold =
114 .DefaultRecommendedScoreThreshold)
116 if (
string.IsNullOrWhiteSpace(advisorVersion))
118 throw new ArgumentException(
119 "An advisor version is required.",
120 nameof(advisorVersion));
123 if (
string.IsNullOrWhiteSpace(catalogName))
125 throw new ArgumentException(
126 "A solution-method catalog name is required.",
127 nameof(catalogName));
130 if (
string.IsNullOrWhiteSpace(catalogVersion))
132 throw new ArgumentException(
133 "A solution-method catalog version is " +
135 nameof(catalogVersion));
138 if (
string.IsNullOrWhiteSpace(
139 supplyChainFingerprint))
141 throw new ArgumentException(
142 "A supply-chain fingerprint is required.",
143 nameof(supplyChainFingerprint));
156 supplyChainFingerprint;
159 recommendedScoreThreshold;
169 [XmlAttribute(
"advisorVersion")]
172 get => _advisorVersion;
177 value?.Trim() ??
string.Empty))
182 NotifyValidityProperties();
191 [XmlAttribute(
"catalogName")]
199 value?.Trim() ??
string.Empty))
204 NotifyValidityProperties();
213 [XmlAttribute(
"catalogVersion")]
216 get => _catalogVersion;
221 value?.Trim() ??
string.Empty))
226 NotifyValidityProperties();
235 [XmlAttribute(
"supplyChainFingerprint")]
238 get => _supplyChainFingerprint;
242 ref _supplyChainFingerprint,
243 value?.Trim() ??
string.Empty))
248 NotifyValidityProperties();
257 [XmlElement(
"generatedAtUtc", IsNullable =
true)]
260 get => _generatedAtUtc;
263 DateTime? normalizedValue =
265 ? ConvertToUtc(value.Value)
278 NotifyValidityProperties();
291 [XmlAttribute(
"recommendedScoreThreshold")]
294 get => _recommendedScoreThreshold;
297 if (!
double.IsFinite(value) ||
301 throw new ArgumentOutOfRangeException(
304 "The recommended-score threshold must " +
305 "be finite and lie between zero and one.");
309 ref _recommendedScoreThreshold,
312 NotifyValidityProperties();
320 [XmlArray(
"recommendations")]
321 [XmlArrayItem(
"recommendation")]
322 public List<SolutionMethodRecommendation>
330 [XmlElement(
"comment")]
338 value?.Trim() ??
string.Empty))
352 !
string.IsNullOrWhiteSpace(
361 !
string.IsNullOrWhiteSpace(
363 !
string.IsNullOrWhiteSpace(
372 !
string.IsNullOrWhiteSpace(
400 Recommendations.Count > 0;
416 recommendation is not
null &&
417 recommendation.HasBeenEvaluated);
426 recommendation is not
null &&
427 recommendation.IsRecommended);
437 recommendation is not
null &&
438 recommendation.IsCompatible);
447 recommendation is not
null &&
448 recommendation.IsPartiallyCompatible);
457 recommendation is not
null &&
458 recommendation.IsIncompatible);
501 recommendation is not
null &&
503 recommendation.IsCompatible ||
505 .IsPartiallyCompatible
509 recommendation.Rank ??
517 recommendation.Score)
520 recommendation.MethodCode,
521 StringComparer.OrdinalIgnoreCase)
538 !
string.IsNullOrWhiteSpace(
566 ArgumentNullException.ThrowIfNull(
571 throw new ArgumentException(
572 "The solution-method recommendation is " +
574 nameof(recommendation));
580 throw new ArgumentException(
581 $
"A recommendation already exists for method " +
582 $
"'{recommendation.MethodCode}'.",
583 nameof(recommendation));
591 NotifyRecommendationProperties();
609 IEnumerable<SolutionMethodRecommendation>
612 ArgumentNullException.ThrowIfNull(
616 recommendations.ToArray();
618 if (materialized.Any(
620 recommendation is
null))
622 throw new ArgumentException(
623 "The recommendation collection cannot " +
624 "contain a null element.",
625 nameof(recommendations));
629 materialized.FirstOrDefault(
632 .IsValidRecommendation);
634 if (invalidRecommendation is not
null)
636 throw new ArgumentException(
637 $
"Recommendation for method " +
638 $
"'{invalidRecommendation.MethodCode}' is " +
640 nameof(recommendations));
643 string[] duplicateMethodCodes =
647 recommendation.MethodCode,
648 StringComparer.OrdinalIgnoreCase)
658 StringComparer.OrdinalIgnoreCase)
661 if (duplicateMethodCodes.Length > 0)
663 throw new ArgumentException(
664 "Duplicate recommendation method codes: " +
667 duplicateMethodCodes) +
669 nameof(recommendations));
679 NotifyRecommendationProperties();
699 if (
string.IsNullOrWhiteSpace(methodCode))
704 string normalizedMethodCode =
711 recommendation is not
null &&
713 recommendation.MethodCode,
714 normalizedMethodCode,
715 StringComparison.OrdinalIgnoreCase))
719 return matches.Length
switch
728 throw new InvalidOperationException(
729 $
"Method code '{methodCode}' is " +
730 "ambiguous in the recommendation report.")
749 methodCode) is not
null;
770 if (recommendation is
null)
783 NotifyRecommendationProperties();
802 NotifyRecommendationProperties();
834 NotifyRecommendationProperties();
847 recommendation is not
null)
854 recommendation.Score)
857 recommendation.MethodName,
858 StringComparer.OrdinalIgnoreCase)
861 recommendation.MethodCode,
862 StringComparer.OrdinalIgnoreCase)
868 index < ordered.Length;
871 ordered[index].
Rank =
878 NotifyRecommendationProperties();
893 string currentFingerprint)
896 string.IsNullOrWhiteSpace(
902 return string.Equals(
904 currentFingerprint.Trim(),
905 StringComparison.Ordinal);
926 "The recommendation report advisor " +
927 "version is missing.");
933 "The recommendation report catalog " +
934 "information is incomplete.");
940 "The recommendation report supply-chain " +
941 "fingerprint is missing.");
947 "The recommendation report generation " +
952 ValidateRecommendations(
955 ValidateMethodCodeUniqueness(
961 ValidateRecommendationMetadata(
967 !
string.IsNullOrWhiteSpace(error))
972 StringComparer.Ordinal)
976 StringComparer.Ordinal)
989 IReadOnlyList<string> errors =
992 if (errors.Count == 0)
997 throw new InvalidOperationException(
998 "The solution-method recommendation report is " +
1000 Environment.NewLine +
1002 Environment.NewLine,
1011 string generationDescription =
1013 ? generatedAtUtc.ToString(
1015 CultureInfo.InvariantCulture)
1019 $
"{RecommendationCount} recommendation(s); " +
1020 $
"{RecommendedMethodCount} recommended; " +
1021 $
"{CompatibleMethodCount} compatible; " +
1022 $
"{generationDescription}";
1025 private void ValidateRecommendations(
1026 ICollection<string> errors)
1035 if (recommendation is
null)
1038 $
"Recommendation at index {index} is " +
1047 $
"Recommendation for method " +
1048 $
"'{DisplayMethodCode(recommendation)}' " +
1054 private void ValidateMethodCodeUniqueness(
1055 ICollection<string> errors)
1057 string[] duplicateMethodCodes =
1061 recommendation is not
null &&
1066 StringComparer.OrdinalIgnoreCase)
1076 StringComparer.OrdinalIgnoreCase)
1079 if (duplicateMethodCodes.Length > 0)
1082 "Duplicate recommendation method codes: " +
1085 duplicateMethodCodes) +
1090 private void ValidateRanks(
1091 ICollection<string> errors)
1093 SolutionMethodRecommendation[] rankedRecommendations =
1097 recommendation is not
null &&
1098 recommendation.
Rank.HasValue)
1101 int[] duplicateRanks =
1102 rankedRecommendations
1105 recommendation.
Rank!.Value)
1117 if (duplicateRanks.Length > 0)
1120 "Duplicate recommendation ranks: " +
1127 if (rankedRecommendations.Length > 0 &&
1128 rankedRecommendations.Length !=
1132 "Recommendation ranks are only partially " +
1137 private void ValidateRecommendationMetadata(
1138 ICollection<string> errors)
1140 foreach (SolutionMethodRecommendation recommendation
1143 recommendation is not
null))
1152 StringComparison.Ordinal))
1155 $
"Recommendation for method " +
1156 $
"'{DisplayMethodCode(recommendation)}' " +
1157 "uses a different supply-chain " +
1165 recommendation.AdvisorVersion,
1167 StringComparison.Ordinal))
1170 $
"Recommendation for method " +
1171 $
"'{DisplayMethodCode(recommendation)}' " +
1172 "uses a different advisor version.");
1176 .HasMethodCatalogInformation &&
1180 recommendation.MethodCatalogName,
1182 StringComparison.Ordinal) ||
1184 recommendation.MethodCatalogVersion,
1186 StringComparison.Ordinal)
1190 $
"Recommendation for method " +
1191 $
"'{DisplayMethodCode(recommendation)}' " +
1192 "uses different catalog information.");
1197 private static string DisplayMethodCode(
1198 SolutionMethodRecommendation recommendation)
1200 return recommendation.HasMethodCode
1201 ? recommendation.MethodCode
1205 private static DateTime ConvertToUtc(
1208 return value.Kind
switch
1213 DateTimeKind.Local =>
1214 value.ToUniversalTime(),
1217 DateTime.SpecifyKind(
1223 private void NotifyRecommendationProperties()
1264 NotifyValidityProperties();
1267 private void NotifyValidityProperties()
Evaluates and ranks solution methods for lot-sizing problem instances.
const double DefaultRecommendedScoreThreshold
Gets the default score above which a directly compatible method may be marked as recommended.
Represents the evaluated compatibility of one solution method with a specific lot-sizing problem inst...
bool HasMethodCode
Gets a value indicating whether a stable method code has been recorded.
bool IsValidRecommendation
Gets a value indicating whether the recommendation contains the minimum coherent information required...
int? Rank
Gets or sets the rank assigned to the recommendation.
string MethodCode
Gets or sets the stable code identifying the evaluated solution method.
int PartiallyCompatibleMethodCount
Gets the number of partially compatible methods.
void EnsureValid()
Validates the report and throws an exception when at least one error is found.
void ClearRecommendations()
Removes every recommendation while preserving the report-generation metadata.
bool HasCatalogInformation
Gets a value indicating whether complete catalog information has been recorded.
bool HasComment
Gets a value indicating whether an explanatory comment has been recorded.
SolutionMethodRecommendation? BestRecommendation
Gets the best applicable recommendation.
DateTime? GeneratedAtUtc
Gets or sets the UTC date and time at which the report was generated.
bool HasBeenGenerated
Gets a value indicating whether this report has been generated.
int RecommendedMethodCount
Gets the number of methods marked as recommended.
void ClearReport()
Clears the recommendations and all generation metadata.
bool HasRecommendedMethod
Gets a value indicating whether at least one method is directly recommended.
SolutionMethodRecommendationReport(string advisorVersion, string catalogName, string catalogVersion, string supplyChainFingerprint, double recommendedScoreThreshold=SolutionMethodAdvisor .DefaultRecommendedScoreThreshold)
Initializes a solution-method recommendation report.
double RecommendedScoreThreshold
Gets or sets the score threshold used to mark a directly compatible method as recommended.
bool RemoveRecommendation(string methodCode)
Removes a recommendation from the report.
bool HasBestRecommendation
Gets a value indicating whether a best applicable recommendation exists.
List< SolutionMethodRecommendation > Recommendations
Gets the ranked solution-method recommendations.
bool HasAdvisorVersion
Gets a value indicating whether an advisor version has been recorded.
int EvaluatedRecommendationCount
Gets the number of evaluated recommendations.
bool HasDirectlyCompatibleMethod
Gets a value indicating whether at least one directly compatible method exists.
int IncompatibleMethodCount
Gets the number of incompatible methods.
bool HasSupplyChainFingerprint
Gets a value indicating whether a supply-chain fingerprint has been recorded.
int CompatibleMethodCount
Gets the number of directly compatible methods, including methods marked as recommended.
void AddRecommendation(SolutionMethodRecommendation recommendation)
Adds a recommendation to the report.
bool HasRecommendations
Gets a value indicating whether at least one recommendation has been recorded.
bool HasGenerationDate
Gets a value indicating whether a generation date has been recorded.
string CatalogName
Gets or sets the name of the solution-method catalog used during evaluation.
string AdvisorVersion
Gets or sets the version of the advisor used to produce the recommendations.
int RecommendationCount
Gets the total number of recommendations.
string Comment
Gets or sets an optional explanatory comment.
bool MatchesSupplyChainFingerprint(string currentFingerprint)
Determines whether the report was generated for the supplied supply-chain fingerprint.
bool ContainsMethodCode(string methodCode)
Determines whether a recommendation exists for a method code.
override string ToString()
void ReplaceRecommendations(IEnumerable< SolutionMethodRecommendation > recommendations)
Replaces all recommendations contained in the report.
bool HasApplicableMethod
Gets a value indicating whether at least one method can be applied directly or after adaptation.
bool IsValidReport
Gets a value indicating whether the report is structurally valid.
void RefreshRanks()
Reorders the recommendations and assigns sequential ranks starting at one.
string CatalogVersion
Gets or sets the version of the solution-method catalog used during evaluation.
SolutionMethodRecommendationReport()
Initializes an empty solution-method recommendation report.
SolutionMethodRecommendation? FindRecommendation(string methodCode)
Finds the recommendation associated with a method code.
IReadOnlyList< string > Validate()
Validates the structural consistency of the report.
string SupplyChainFingerprint
Gets or sets the fingerprint of the supply chain that was evaluated.