101 public static IReadOnlyList<SolutionMethodRecommendation>
105 double recommendedScoreThreshold =
107 bool includeIncompatibleMethods =
true)
109 ArgumentNullException.ThrowIfNull(instance);
110 ArgumentNullException.ThrowIfNull(catalog);
112 ValidateRecommendedScoreThreshold(
113 recommendedScoreThreshold);
117 string supplyChainFingerprint =
122 IReadOnlyList<SolutionMethodDefinition>
126 var recommendations =
127 new List<SolutionMethodRecommendation>(
128 methodDefinitions.Count);
131 in methodDefinitions)
144 methodCatalogVersion:
145 catalog.CatalogVersion,
147 supplyChainFingerprint:
148 supplyChainFingerprint,
150 recommendedScoreThreshold:
151 recommendedScoreThreshold);
153 if (includeIncompatibleMethods ||
192 string methodCatalogName =
"",
193 string methodCatalogVersion =
"",
194 double recommendedScoreThreshold =
197 ArgumentNullException.ThrowIfNull(instance);
198 ArgumentNullException.ThrowIfNull(methodDefinition);
200 ValidateRecommendedScoreThreshold(
201 recommendedScoreThreshold);
205 string supplyChainFingerprint =
220 methodCatalogVersion:
221 methodCatalogVersion,
223 supplyChainFingerprint:
224 supplyChainFingerprint,
226 recommendedScoreThreshold:
227 recommendedScoreThreshold);
258 public static IReadOnlyList<SolutionMethodRecommendation>
260 IEnumerable<SolutionMethodRecommendation>
263 ArgumentNullException.ThrowIfNull(
267 recommendations.ToArray();
269 if (materialized.Any(
271 recommendation is
null))
273 throw new ArgumentException(
274 "The recommendation collection cannot " +
275 "contain a null element.",
276 nameof(recommendations));
283 recommendation.CompatibilityLevel)
286 recommendation.Score)
289 recommendation.MethodName,
290 StringComparer.OrdinalIgnoreCase)
293 recommendation.MethodCode,
294 StringComparer.OrdinalIgnoreCase)
297 AssignSequentialRanks(
306 string methodCatalogName,
307 string methodCatalogVersion,
308 string supplyChainFingerprint,
309 double recommendedScoreThreshold)
325 methodCatalogName?.Trim() ??
328 MethodCatalogVersion =
329 methodCatalogVersion?.Trim() ??
332 SupplyChainFingerprint =
333 supplyChainFingerprint
337 new List<MethodCompatibilityEvidence>();
345 bool supportsAlternativeScope =
346 methodDefinition.SupportsRelaxations ||
349 AddClassificationStatusWarnings(
353 EvaluateProblemFamily(
360 supportsAlternativeScope:
361 supportsAlternativeScope,
369 EvaluateProductStructure(
376 supportsAlternativeScope:
377 supportsAlternativeScope,
385 EvaluateRequiredFeatures(
398 EvaluateUnsupportedFeatures(
405 supportsAlternativeScope:
406 supportsAlternativeScope,
417 EvaluatePartiallySupportedFeatures(
433 EvaluateSupportedFeatures(
446 EvaluatePreferredFeatures(
466 supportsAlternativeScope:
467 supportsAlternativeScope,
478 recommendation.ReplaceEvidence(
481 recommendation.ReplaceRequiredAdaptations(
484 recommendation.ReplaceWarnings(
487 recommendation.Scope =
501 recommendation.ScopeDescription =
502 CreateScopeDescription(
503 recommendation.Scope);
505 recommendation.UpdateCompatibilityFromEvidence(
506 recommendedScoreThreshold:
507 recommendedScoreThreshold,
509 updateEvaluationDate:
512 recommendation.Summary =
517 return recommendation;
520 private static IReadOnlyList<SolutionMethodRecommendation>
522 IEnumerable<SolutionMethodRecommendation>
524 IEnumerable<SolutionMethodDefinition>
527 Dictionary<string, int> priorityByMethodCode =
531 method is not
null &&
532 method.HasMethodCode)
536 StringComparer.OrdinalIgnoreCase)
542 group.First().Priority,
544 StringComparer.OrdinalIgnoreCase);
546 SolutionMethodRecommendation[] ordered =
550 recommendation.CompatibilityLevel)
553 recommendation.Score)
556 priorityByMethodCode.TryGetValue(
557 recommendation.MethodCode,
563 recommendation.MethodName,
564 StringComparer.OrdinalIgnoreCase)
567 recommendation.MethodCode,
568 StringComparer.OrdinalIgnoreCase)
571 AssignSequentialRanks(
577 private static void EvaluateProblemFamily(
579 SolutionMethodDefinition methodDefinition,
580 bool supportsAlternativeScope,
581 ICollection<MethodCompatibilityEvidence> evidence,
582 ICollection<string> adaptations)
584 if (methodDefinition.SupportsAnyProblemFamily)
589 "FAMILY.ANY_SUPPORTED",
592 "The method is independent from a " +
593 "specific problem-family code.",
608 GetObservedProblemFamilyDescription(
617 IReadOnlyList<string> candidateCodes =
618 GetCandidateProblemTypeCodes(
621 if (candidateCodes.Count == 0)
623 bool supportsUnclassified =
625 .SupportsUnclassifiedProblems;
630 "FAMILY.CLASSIFICATION_AVAILABLE",
634 ?
"The method accepts problems " +
635 "without a recognized family."
636 :
"The method requires a " +
637 "recognized supported problem " +
641 supportsUnclassified,
647 !supportsUnclassified &&
648 !supportsAlternativeScope,
652 ?
"classified or unclassified"
656 .SupportedProblemTypeCodes),
664 if (!supportsUnclassified &&
665 supportsAlternativeScope)
668 "Identify a supported relaxation or " +
669 "subproblem before applying the method.");
676 classification.Status ==
677 ProblemClassificationStatus.Ambiguous ||
678 candidateCodes.Count > 1;
682 .SupportsAmbiguousClassifications)
687 "FAMILY.AMBIGUOUS_CLASSIFICATION",
690 "The method definition does not " +
691 "accept an ambiguous problem-family " +
701 !supportsAlternativeScope,
704 "unambiguous classification",
714 if (supportsAlternativeScope)
717 "Resolve the family ambiguity or apply " +
718 "the method only to a supported " +
723 string[] supportedCandidateCodes =
727 .SupportsProblemTypeCode)
730 bool familySupported =
731 supportedCandidateCodes.Length > 0;
740 ?
"The method supports at least one " +
741 "recognized problem family."
742 :
"The method does not directly " +
743 "support the recognized problem " +
754 !supportsAlternativeScope,
760 .SupportedProblemTypeCodes),
770 if (!familySupported &&
771 supportsAlternativeScope)
774 "Apply the method to a supported relaxation " +
775 "or subproblem rather than to the complete " +
779 string? preferredCandidateCode =
780 supportedCandidateCodes
783 .PrefersProblemTypeCode);
785 if (!
string.IsNullOrWhiteSpace(
786 preferredCandidateCode))
794 "The recognized problem family is " +
795 "identified as particularly " +
796 "appropriate for the method.",
811 .PreferredProblemTypeCodes),
814 preferredCandidateCode,
821 private static void EvaluateProductStructure(
822 LotSizingProblemFeatures features,
823 SolutionMethodDefinition methodDefinition,
824 bool supportsAlternativeScope,
825 ICollection<MethodCompatibilityEvidence> evidence,
826 ICollection<string> adaptations)
831 if (methodDefinition.SupportsAnyProductStructure)
836 "STRUCTURE.ANY_SUPPORTED",
839 "The method supports every valid " +
840 "product-structure type.",
854 "any valid structure",
865 bool structureSupported =
867 ProductStructureType.Unknown &&
869 .SupportsProductStructureType(
875 "STRUCTURE.SUPPORTED",
879 ?
"The method supports the detected " +
881 :
"The method does not directly " +
882 "support the detected product " +
892 !structureSupported &&
893 !supportsAlternativeScope,
899 .SupportedProductStructureTypes),
907 if (!structureSupported &&
908 supportsAlternativeScope)
911 "Decompose or relax the product structure " +
912 "before applying the method.");
916 private static void EvaluateRequiredFeatures(
917 LotSizingProblemFeatures features,
918 SolutionMethodDefinition methodDefinition,
919 ICollection<MethodCompatibilityEvidence> evidence,
920 ICollection<string> warnings)
922 foreach (
string featureCode
925 .RequiredFeatureCodes))
927 if (!TryGetBooleanFeature(
930 out
bool observedValue))
935 CreateFeatureCriterionCode(
940 $
"Required feature " +
941 $
"'{featureCode}' could not be " +
966 $
"Required feature '{featureCode}' " +
967 "could not be read from the feature " +
976 CreateFeatureCriterionCode(
982 ? $
"Required feature " +
983 $
"'{featureCode}' is active."
984 : $
"The method requires feature " +
985 $
"'{featureCode}', but it is " +
1011 private static void EvaluateUnsupportedFeatures(
1012 LotSizingProblemFeatures features,
1013 SolutionMethodDefinition methodDefinition,
1014 bool supportsAlternativeScope,
1015 ICollection<MethodCompatibilityEvidence> evidence,
1016 ICollection<string> adaptations,
1017 ICollection<string> warnings)
1019 foreach (
string featureCode
1022 .UnsupportedFeatureCodes))
1024 if (!TryGetBooleanFeature(
1027 out
bool observedValue))
1030 $
"Unsupported feature criterion " +
1031 $
"'{featureCode}' could not be read from " +
1032 "the feature profile.");
1037 bool criterionSatisfied =
1043 CreateFeatureCriterionCode(
1049 ? $
"Unsupported feature " +
1050 $
"'{featureCode}' is absent."
1051 : $
"Feature '{featureCode}' is " +
1052 "active but is not supported " +
1062 !criterionSatisfied &&
1063 !supportsAlternativeScope,
1077 if (!criterionSatisfied &&
1078 supportsAlternativeScope)
1081 $
"Remove, relax or separately handle " +
1082 $
"feature '{featureCode}'.");
1088 EvaluatePartiallySupportedFeatures(
1089 LotSizingProblemFeatures features,
1090 SolutionMethodDefinition methodDefinition,
1091 ICollection<MethodCompatibilityEvidence> evidence,
1092 ICollection<string> adaptations,
1093 ICollection<string> warnings)
1095 foreach (
string featureCode
1098 .PartiallySupportedFeatureCodes))
1100 if (!TryGetBooleanFeature(
1103 out
bool observedValue))
1106 $
"Partially supported feature " +
1107 $
"'{featureCode}' could not be read from " +
1108 "the feature profile.");
1121 CreateFeatureCriterionCode(
1126 $
"Feature '{featureCode}' is active " +
1127 "and requires an adaptation of the " +
1140 "absent or adapted",
1152 $
"Use an adapted implementation for feature " +
1153 $
"'{featureCode}'.");
1157 private static void EvaluateSupportedFeatures(
1158 LotSizingProblemFeatures features,
1159 SolutionMethodDefinition methodDefinition,
1160 ICollection<MethodCompatibilityEvidence> evidence,
1161 ICollection<string> warnings)
1163 HashSet<string> requiredFeatures =
1165 .RequiredFeatureCodes
1167 StringComparer.OrdinalIgnoreCase);
1169 foreach (
string featureCode
1172 .SupportedFeatureCodes))
1174 if (requiredFeatures.Contains(
1180 if (!TryGetBooleanFeature(
1183 out
bool observedValue))
1186 $
"Supported feature '{featureCode}' " +
1187 "could not be read from the feature " +
1201 CreateFeatureCriterionCode(
1206 $
"Active feature '{featureCode}' is " +
1207 "supported by the method.",
1232 private static void EvaluatePreferredFeatures(
1233 LotSizingProblemFeatures features,
1234 SolutionMethodDefinition methodDefinition,
1235 ICollection<MethodCompatibilityEvidence> evidence,
1236 ICollection<string> warnings)
1238 foreach (
string featureCode
1241 .PreferredFeatureCodes))
1243 if (!TryGetBooleanFeature(
1246 out
bool observedValue))
1249 $
"Preferred feature '{featureCode}' " +
1250 "could not be read from the feature " +
1259 CreateFeatureCriterionCode(
1265 ? $
"Preferred feature " +
1266 $
"'{featureCode}' is active."
1267 : $
"Preferred feature " +
1268 $
"'{featureCode}' is not active.",
1293 private static void EvaluateSizeLimits(
1294 LotSizingProblemFeatures features,
1295 SolutionMethodDefinition methodDefinition,
1296 bool supportsAlternativeScope,
1297 ICollection<MethodCompatibilityEvidence> evidence,
1298 ICollection<string> adaptations,
1299 ICollection<string> warnings)
1303 methodDefinition.MaximumItemCount,
1306 supportsAlternativeScope,
1314 methodDefinition.MaximumPlanningHorizon,
1315 "LIMIT.MAX_HORIZON",
1317 supportsAlternativeScope,
1325 methodDefinition.MaximumPlantCount,
1328 supportsAlternativeScope,
1336 methodDefinition.MaximumWorkCenterCount,
1337 "LIMIT.MAX_WORK_CENTERS",
1338 "work-center count",
1339 supportsAlternativeScope,
1347 methodDefinition.MaximumWarehouseCount,
1348 "LIMIT.MAX_WAREHOUSES",
1350 supportsAlternativeScope,
1358 methodDefinition.MaximumSupplierCount,
1359 "LIMIT.MAX_SUPPLIERS",
1361 supportsAlternativeScope,
1369 methodDefinition.MaximumTransportResourceCount,
1370 "LIMIT.MAX_TRANSPORT_RESOURCES",
1371 "transport-resource count",
1372 supportsAlternativeScope,
1376 "TransportResourceCount");
1381 .MaximumBillOfMaterialsRelationshipCount,
1382 "LIMIT.MAX_BOM_RELATIONSHIPS",
1383 "bill-of-materials relationship count",
1384 supportsAlternativeScope,
1388 "ProductStructureRelationshipCount",
1389 "BillOfMaterialsRelationshipCount",
1390 "ComponentRequirementCount");
1394 methodDefinition.MaximumProductStructureDepth,
1395 "LIMIT.MAX_STRUCTURE_DEPTH",
1396 "product-structure depth",
1397 supportsAlternativeScope,
1401 "ProductStructureDepth",
1402 "MaximumProductStructureDepth",
1403 "BillOfMaterialsDepth");
1405 EvaluateRecommendedLimit(
1407 methodDefinition.RecommendedMaximumItemCount,
1408 "LIMIT.RECOMMENDED_MAX_ITEMS",
1409 "recommended item count",
1414 EvaluateRecommendedLimit(
1417 .RecommendedMaximumPlanningHorizon,
1418 "LIMIT.RECOMMENDED_MAX_HORIZON",
1419 "recommended planning horizon",
1424 EvaluateRecommendedLimit(
1427 .RecommendedMaximumBillOfMaterialsRelationshipCount,
1428 "LIMIT.RECOMMENDED_MAX_BOM_RELATIONSHIPS",
1429 "recommended bill-of-materials relationship count",
1432 "ProductStructureRelationshipCount",
1433 "BillOfMaterialsRelationshipCount",
1434 "ComponentRequirementCount");
1436 EvaluateRecommendedLimit(
1439 .RecommendedMaximumProductStructureDepth,
1440 "LIMIT.RECOMMENDED_MAX_STRUCTURE_DEPTH",
1441 "recommended product-structure depth",
1444 "ProductStructureDepth",
1445 "MaximumProductStructureDepth",
1446 "BillOfMaterialsDepth");
1449 private static void EvaluateHardLimit(
1450 LotSizingProblemFeatures features,
1452 string criterionCode,
1454 bool supportsAlternativeScope,
1455 ICollection<MethodCompatibilityEvidence> evidence,
1456 ICollection<string> adaptations,
1457 ICollection<string> warnings,
1458 params
string[] featurePropertyNames)
1460 if (!maximumValue.HasValue)
1465 if (!TryGetIntegerFeature(
1467 featurePropertyNames,
1468 out
int observedValue,
1469 out
string resolvedFeatureCode))
1472 $
"Hard limit '{criterionCode}' could not be " +
1473 "evaluated because the corresponding " +
1474 "feature value is unavailable.");
1479 bool limitSatisfied =
1490 ? $
"The {description} is within the " +
1491 "method's hard limit."
1492 : $
"The {description} exceeds the " +
1493 "method's hard limit.",
1503 !supportsAlternativeScope,
1506 $
"<= {maximumValue.Value.ToString(
1507 CultureInfo.InvariantCulture)}",
1513 resolvedFeatureCode,
1518 if (!limitSatisfied &&
1519 supportsAlternativeScope)
1522 $
"Decompose the instance so that the " +
1523 $
"{description} does not exceed " +
1524 $
"{maximumValue.Value.ToString(
1525 CultureInfo.InvariantCulture)}.");
1529 private static void EvaluateRecommendedLimit(
1530 LotSizingProblemFeatures features,
1532 string criterionCode,
1534 ICollection<MethodCompatibilityEvidence> evidence,
1535 ICollection<string> warnings,
1536 params
string[] featurePropertyNames)
1538 if (!maximumValue.HasValue)
1543 if (!TryGetIntegerFeature(
1545 featurePropertyNames,
1546 out
int observedValue,
1547 out
string resolvedFeatureCode))
1550 $
"Recommended limit '{criterionCode}' could " +
1551 "not be evaluated because the corresponding " +
1552 "feature value is unavailable.");
1557 bool limitSatisfied =
1568 ? $
"The {description} is within the " +
1569 "effective operating range."
1570 : $
"The {description} exceeds the " +
1571 "method's recommended operating " +
1584 $
"<= {maximumValue.Value.ToString(
1585 CultureInfo.InvariantCulture)}",
1591 resolvedFeatureCode,
1596 if (!limitSatisfied)
1599 $
"The {description} exceeds the recommended " +
1600 $
"maximum of {maximumValue.Value.ToString(
1601 CultureInfo.InvariantCulture)} for this " +
1607 LotSizingProblemFeatures features,
1608 SolutionMethodDefinition methodDefinition,
1609 SolutionMethodRecommendation recommendation,
1610 ICollection<string> adaptations)
1612 bool directCompleteProblemUse =
1613 methodDefinition.SupportsCompleteProblems &&
1614 !recommendation.HasBlockingMismatches &&
1615 !recommendation.HasRequiredMismatches &&
1616 adaptations.Count == 0;
1618 if (directCompleteProblemUse)
1623 if (recommendation.HasBlockingMismatches)
1625 return methodDefinition.SupportsCompleteProblems
1626 ? ProblemClassificationScope.CompleteProblem
1630 if (methodDefinition.SupportsRelaxations)
1632 if (!adaptations.Contains(
1633 "Apply the method to a relaxation of the " +
1634 "complete problem.",
1635 StringComparer.OrdinalIgnoreCase))
1638 "Apply the method to a relaxation of the " +
1639 "complete problem.");
1646 if (methodDefinition.SupportsSubproblems)
1648 if (!adaptations.Contains(
1649 "Apply the method to suitable " +
1651 StringComparer.OrdinalIgnoreCase))
1654 "Apply the method to suitable " +
1658 return features.IsMultiItem
1659 ? ProblemClassificationScope.SingleItem
1663 return methodDefinition.SupportsCompleteProblems
1664 ? ProblemClassificationScope.CompleteProblem
1668 private static string CreateScopeDescription(
1673 ProblemClassificationScope.CompleteProblem =>
1676 ProblemClassificationScope.ProblemRelaxation =>
1677 "Relaxation of the complete problem",
1679 ProblemClassificationScope.SingleItem =>
1680 "Single-item subproblems",
1682 ProblemClassificationScope.ItemSubset =>
1685 ProblemClassificationScope.Plant =>
1686 "Plant-level subproblem",
1688 ProblemClassificationScope.WorkCenter =>
1689 "Work-center subproblem",
1691 ProblemClassificationScope.Warehouse =>
1692 "Warehouse subproblem",
1694 ProblemClassificationScope.TransportResource =>
1695 "Transport-resource subproblem",
1697 ProblemClassificationScope.TransportLane =>
1698 "Transport-lane subproblem",
1700 ProblemClassificationScope.Supplier =>
1701 "Supplier subproblem",
1703 ProblemClassificationScope.DistributionCenter =>
1704 "Distribution-center subproblem",
1706 ProblemClassificationScope.SupplyChainSegment =>
1707 "Supply-chain segment",
1709 ProblemClassificationScope.CustomSubset =>
1710 "Custom problem subset",
1717 private static string CreateSummary(
1718 SolutionMethodRecommendation recommendation,
1719 LotSizingProblemClassification classification)
1721 string familyDescription =
1722 classification.HasPrimaryProblemType
1724 $
"'{classification.PrimaryProblemTypeCode}'"
1727 return recommendation.CompatibilityLevel
switch
1729 MethodCompatibilityLevel.Recommended =>
1730 "The method is recommended for the complete " +
1731 $
"problem{familyDescription}.",
1733 MethodCompatibilityLevel.Compatible =>
1734 "The method is compatible with the complete " +
1735 $
"problem{familyDescription}.",
1737 MethodCompatibilityLevel.PartiallyCompatible =>
1738 $
"The method is partially compatible and " +
1739 $
"should be applied to: " +
1740 $
"{recommendation.ScopeDescription}.",
1742 MethodCompatibilityLevel.Incompatible =>
1743 CreateIncompatibilitySummary(
1747 "The method compatibility has not been " +
1752 private static string CreateIncompatibilitySummary(
1753 SolutionMethodRecommendation recommendation)
1755 MethodCompatibilityEvidence? principalMismatch =
1756 recommendation.Evidence
1760 item.IsBlockingMismatch)
1762 recommendation.Evidence
1766 item.IsRequiredMismatch);
1768 return principalMismatch is
null
1769 ?
"The method is incompatible with the complete " +
1771 :
"The method is incompatible with the complete " +
1772 $
"problem: {principalMismatch.Description}";
1775 private static void AddClassificationStatusWarnings(
1776 LotSizingProblemClassification classification,
1777 ICollection<string> warnings)
1779 switch (classification.
Status)
1783 "The problem has not been automatically " +
1788 .PartiallyClassified:
1790 "The problem classification is partial.");
1795 "The problem-family classification is " +
1801 "No known problem family was identified.");
1806 "The problem classification is invalid.");
1811 "The problem classification is outdated.");
1815 foreach (
string classificationWarning
1818 if (!
string.IsNullOrWhiteSpace(
1819 classificationWarning))
1823 classificationWarning.Trim());
1828 private static IReadOnlyList<string>
1829 GetCandidateProblemTypeCodes(
1830 LotSizingProblemClassification classification)
1836 classification.PrimaryProblemTypeCode
1840 return classification.Matches
1843 match is not
null &&
1844 match.IsDirectMatch &&
1845 match.AppliesToCompleteProblem &&
1846 !
string.IsNullOrWhiteSpace(
1847 match.ProblemTypeCode))
1850 match.ProblemTypeCode.Trim())
1852 StringComparer.OrdinalIgnoreCase)
1856 StringComparer.OrdinalIgnoreCase)
1860 private static string
1861 GetObservedProblemFamilyDescription(
1862 LotSizingProblemClassification classification)
1864 IReadOnlyList<string> codes =
1865 GetCandidateProblemTypeCodes(
1868 return codes.Count > 0
1872 : classification.
Status.ToString();
1875 private static MethodCompatibilityEvidence
1877 string criterionCode,
1882 object? expectedValue,
1883 object? observedValue,
1884 string featureCode =
"",
1885 double weight = 1.0)
1888 new MethodCompatibilityEvidence(
1908 featureCode?.Trim() ??
1912 evidence.SetComparedValues(
1919 private static bool TryGetBooleanFeature(
1920 LotSizingProblemFeatures features,
1927 if (
string.IsNullOrWhiteSpace(
1933 PropertyInfo?
property =
1934 typeof(LotSizingProblemFeatures)
1937 BindingFlags.Instance |
1938 BindingFlags.Public |
1939 BindingFlags.IgnoreCase);
1941 if (property is
null ||
1942 !property.CanRead ||
1943 property.GetIndexParameters().Length != 0 ||
1944 property.PropertyType != typeof(
bool))
1949 object? propertyValue =
1953 if (propertyValue is not
bool booleanValue)
1964 private static bool TryGetIntegerFeature(
1965 LotSizingProblemFeatures features,
1966 IEnumerable<string> candidatePropertyNames,
1968 out
string resolvedFeatureCode)
1973 resolvedFeatureCode =
1976 foreach (
string candidatePropertyName
1977 in candidatePropertyNames)
1979 if (
string.IsNullOrWhiteSpace(
1980 candidatePropertyName))
1985 PropertyInfo?
property =
1986 typeof(LotSizingProblemFeatures)
1988 candidatePropertyName.Trim(),
1989 BindingFlags.Instance |
1990 BindingFlags.Public |
1991 BindingFlags.IgnoreCase);
1993 if (property is
null ||
1994 !property.CanRead ||
1995 property.GetIndexParameters().Length != 0)
2004 if (!TryConvertToInt32(
2006 out
int convertedValue))
2014 resolvedFeatureCode =
2023 private static bool TryConvertToInt32(
2025 out
int convertedValue)
2032 case int integerValue:
2037 case short shortValue:
2042 case byte byteValue:
2048 when longValue >= int.MinValue &&
2049 longValue <=
int.MaxValue:
2054 case uint unsignedIntegerValue
2055 when unsignedIntegerValue <=
int.MaxValue:
2057 (int)unsignedIntegerValue;
2065 private static IReadOnlyList<string> DistinctCodes(
2066 IEnumerable<string> codes)
2071 !
string.IsNullOrWhiteSpace(code))
2076 StringComparer.OrdinalIgnoreCase)
2080 StringComparer.OrdinalIgnoreCase)
2084 private static string CreateFeatureCriterionCode(
2088 char[] normalizedCharacters =
2094 char.IsLetterOrDigit(character)
2100 $
"FEATURE.{category.ToUpperInvariant()}." +
2102 normalizedCharacters);
2105 private static void AssignSequentialRanks(
2106 IReadOnlyList<SolutionMethodRecommendation>
2110 index < recommendations.Count;
2113 recommendations[index].Rank =
2119 ValidateRecommendedScoreThreshold(
2120 double recommendedScoreThreshold)
2122 if (!
double.IsFinite(
2123 recommendedScoreThreshold) ||
2124 recommendedScoreThreshold < 0.0 ||
2125 recommendedScoreThreshold > 1.0)
2127 throw new ArgumentOutOfRangeException(
2128 nameof(recommendedScoreThreshold),
2129 recommendedScoreThreshold,
2130 "The recommended-score threshold must be " +