LotSizingDataModel.Instance 2.0.1
Lot-sizing instance representation, descriptors and problem characterization.
Loading...
Searching...
No Matches
ProductStructureDescriptor.cs
Go to the documentation of this file.
1using System;
2using System.Collections.Generic;
3using System.Linq;
4using System.Xml.Serialization;
5using LotSizingDataModel.Core.Common;
7
9
10/// <summary>
11/// Describes the declared and automatically detected structure
12/// of the product bill-of-materials graph of an instance.
13/// </summary>
14/// <remarks>
15/// The declared type represents information supplied by an
16/// author, publication or data provider.
17///
18/// The detected type represents the result of an automatic
19/// analysis of the supply-chain component relationships.
20///
21/// Both values are retained when they differ.
22/// </remarks>
23[Serializable]
24[XmlType(TypeName = "productStructureDescriptor")]
25public sealed class ProductStructureDescriptor :
26 ModelObject
27{
28 private ProductStructureType _declaredType =
30
31 private ProductStructureType _detectedType =
33
34 private ProductStructureCheckStatus _checkStatus =
36
37 private bool _hasCycle;
38 private int _maximumDepth;
39
40 private DateTime? _analyzedAtUtc;
41
42 private string _analyzerVersion =
43 string.Empty;
44
45 private string _supplyChainFingerprint =
46 string.Empty;
47
48 private string _analysisComment =
49 string.Empty;
50
51 /// <summary>
52 /// Initializes an empty product-structure descriptor.
53 /// </summary>
55 {
56 }
57
58 /// <summary>
59 /// Initializes a descriptor with a declared
60 /// product-structure type.
61 /// </summary>
62 /// <param name="declaredType">
63 /// Product-structure type declared by an author,
64 /// publication or data provider.
65 /// </param>
67 ProductStructureType declaredType)
68 {
69 DeclaredType = declaredType;
70
72 declaredType ==
75 : ProductStructureCheckStatus.DeclaredOnly;
76 }
77
78 /// <summary>
79 /// Gets or sets the product-structure type declared
80 /// by an author, publication or data provider.
81 /// </summary>
82 /// <remarks>
83 /// This value must not be silently replaced when an
84 /// automatic analysis produces a different result.
85 /// </remarks>
86 [XmlAttribute("declaredType")]
88 {
89 get => _declaredType;
90 set
91 {
92 if (SetProperty(
93 ref _declaredType,
94 value))
95 {
96 NotifyDerivedProperties();
97 }
98 }
99 }
100
101 /// <summary>
102 /// Gets or sets the product-structure type detected
103 /// automatically from the supply-chain data.
104 /// </summary>
105 [XmlAttribute("detectedType")]
107 {
108 get => _detectedType;
109 set
110 {
111 if (SetProperty(
112 ref _detectedType,
113 value))
114 {
115 NotifyDerivedProperties();
116 }
117 }
118 }
119
120 /// <summary>
121 /// Gets or sets the current status of the product-structure
122 /// declaration and automatic verification.
123 /// </summary>
124 [XmlAttribute("checkStatus")]
126 {
127 get => _checkStatus;
128 set
129 {
130 if (SetProperty(
131 ref _checkStatus,
132 value))
133 {
134 NotifyDerivedProperties();
135 }
136 }
137 }
138
139 /// <summary>
140 /// Gets or sets a value indicating whether the analyzed
141 /// bill-of-materials graph contains at least one cycle.
142 /// </summary>
143 /// <remarks>
144 /// A cyclic product structure is considered invalid.
145 /// </remarks>
146 [XmlAttribute("hasCycle")]
147 public bool HasCycle
148 {
149 get => _hasCycle;
150 set
151 {
152 if (SetProperty(
153 ref _hasCycle,
154 value))
155 {
156 NotifyDerivedProperties();
157 }
158 }
159 }
160
161 /// <summary>
162 /// Gets or sets the maximum number of bill-of-materials
163 /// relationships on a path from a leaf item to a root item.
164 /// </summary>
165 /// <remarks>
166 /// A value of zero represents independent items or an
167 /// analysis for which no product-structure arc exists.
168 /// </remarks>
169 [XmlAttribute("maximumDepth")]
170 public int MaximumDepth
171 {
172 get => _maximumDepth;
173 set
174 {
175 if (value < 0)
176 {
177 throw new ArgumentOutOfRangeException(
178 nameof(value),
179 value,
180 "The maximum depth cannot be negative.");
181 }
182
183 SetProperty(
184 ref _maximumDepth,
185 value);
186 }
187 }
188
189 /// <summary>
190 /// Gets the identifiers of root items in the
191 /// bill-of-materials graph.
192 /// </summary>
193 /// <remarks>
194 /// A root item is not consumed as a component by another
195 /// item in the analyzed product structure.
196 /// </remarks>
197 [XmlArray("rootItemIds")]
198 [XmlArrayItem("itemId")]
199 public List<int> RootItemIds { get; } =
200 new();
201
202 /// <summary>
203 /// Gets the identifiers of leaf items in the
204 /// bill-of-materials graph.
205 /// </summary>
206 /// <remarks>
207 /// A leaf item does not consume another component item.
208 /// </remarks>
209 [XmlArray("leafItemIds")]
210 [XmlArrayItem("itemId")]
211 public List<int> LeafItemIds { get; } =
212 new();
213
214 /// <summary>
215 /// Gets the identifiers of components used by more than
216 /// one immediate parent item.
217 /// </summary>
218 [XmlArray("sharedComponentItemIds")]
219 [XmlArrayItem("itemId")]
220 public List<int> SharedComponentItemIds { get; } =
221 new();
222
223 /// <summary>
224 /// Gets or sets the UTC date and time at which the
225 /// automatic analysis was performed.
226 /// </summary>
227 /// <remarks>
228 /// A null value means that no automatic analysis date
229 /// has been recorded.
230 /// </remarks>
231 [XmlElement("analyzedAtUtc", IsNullable = true)]
232 public DateTime? AnalyzedAtUtc
233 {
234 get => _analyzedAtUtc;
235 set
236 {
237 DateTime? utcValue =
238 value.HasValue
239 ? ConvertToUtc(value.Value)
240 : null;
241
242 if (SetProperty(
243 ref _analyzedAtUtc,
244 utcValue))
245 {
246 NotifyDerivedProperties();
247 }
248 }
249 }
250
251 /// <summary>
252 /// Gets or sets the version of the component that
253 /// performed the product-structure analysis.
254 /// </summary>
255 [XmlAttribute("analyzerVersion")]
256 public string AnalyzerVersion
257 {
258 get => _analyzerVersion;
259 set => SetProperty(
260 ref _analyzerVersion,
261 value?.Trim() ?? string.Empty);
262 }
263
264 /// <summary>
265 /// Gets or sets the fingerprint of the supply-chain data
266 /// used during the product-structure analysis.
267 /// </summary>
268 /// <remarks>
269 /// This value allows a validator to detect that the
270 /// supply chain has changed since the analysis.
271 /// </remarks>
272 [XmlAttribute("supplyChainFingerprint")]
274 {
275 get => _supplyChainFingerprint;
276 set => SetProperty(
277 ref _supplyChainFingerprint,
278 value?.Trim() ?? string.Empty);
279 }
280
281 /// <summary>
282 /// Gets or sets an optional human-readable comment
283 /// about the product-structure analysis.
284 /// </summary>
285 [XmlElement("analysisComment")]
286 public string AnalysisComment
287 {
288 get => _analysisComment;
289 set => SetProperty(
290 ref _analysisComment,
291 value ?? string.Empty);
292 }
293
294 /// <summary>
295 /// Gets a value indicating whether an author or source
296 /// declared a product-structure type.
297 /// </summary>
298 [XmlIgnore]
299 public bool HasDeclaredType =>
300 DeclaredType !=
301 ProductStructureType.Unknown;
302
303 /// <summary>
304 /// Gets a value indicating whether an automatic analysis
305 /// detected a product-structure type.
306 /// </summary>
307 [XmlIgnore]
308 public bool HasDetectedType =>
309 DetectedType !=
310 ProductStructureType.Unknown;
311
312 /// <summary>
313 /// Gets a value indicating whether an automatic analysis
314 /// date has been recorded.
315 /// </summary>
316 [XmlIgnore]
317 public bool HasBeenAnalyzed =>
318 AnalyzedAtUtc.HasValue;
319
320 /// <summary>
321 /// Gets a value indicating whether at least one component
322 /// is shared by several immediate parent items.
323 /// </summary>
324 [XmlIgnore]
325 public bool HasSharedComponents =>
326 SharedComponentItemIds.Count > 0;
327
328 /// <summary>
329 /// Gets a value indicating whether the declared and
330 /// detected structure types contradict each other.
331 /// </summary>
332 [XmlIgnore]
334 CheckStatus ==
336 .DeclaredAndContradicted;
337
338 /// <summary>
339 /// Gets the product-structure type that should normally
340 /// be used by higher-level classification services.
341 /// </summary>
342 /// <remarks>
343 /// A valid automatically detected type takes priority
344 /// over a declaration.
345 ///
346 /// An invalid or outdated analysis produces
347 /// <see cref="ProductStructureType.Unknown"/>.
348 /// </remarks>
349 [XmlIgnore]
369
370 /// <summary>
371 /// Gets a value indicating whether the recorded structure
372 /// type may currently be used for problem classification.
373 /// </summary>
374 [XmlIgnore]
378 CheckStatus !=
380 CheckStatus !=
382
383 /// <summary>
384 /// Marks the recorded automatic analysis as outdated.
385 /// </summary>
386 /// <remarks>
387 /// The previously detected information is retained for
388 /// traceability, but it must not be used until the product
389 /// structure is analyzed again.
390 /// </remarks>
391 public void MarkAsOutdated()
392 {
393 if (!HasBeenAnalyzed &&
395 {
396 return;
397 }
398
401 }
402
403 /// <summary>
404 /// Removes all automatically detected product-structure
405 /// information while preserving the declared type.
406 /// </summary>
408 {
410 ProductStructureType.Unknown;
411
412 HasCycle = false;
413 MaximumDepth = 0;
414
415 RootItemIds.Clear();
416 LeafItemIds.Clear();
418
419 AnalyzedAtUtc = null;
420 AnalyzerVersion = string.Empty;
421 SupplyChainFingerprint = string.Empty;
422 AnalysisComment = string.Empty;
423
427 : ProductStructureCheckStatus.NotAnalyzed;
428
429 NotifyDerivedProperties();
430 }
431
432 /// <summary>
433 /// Replaces the item sets generated by the automatic
434 /// product-structure analysis.
435 /// </summary>
436 /// <param name="rootItemIds">
437 /// Identifiers of root items.
438 /// </param>
439 /// <param name="leafItemIds">
440 /// Identifiers of leaf items.
441 /// </param>
442 /// <param name="sharedComponentItemIds">
443 /// Identifiers of shared component items.
444 /// </param>
446 IEnumerable<int> rootItemIds,
447 IEnumerable<int> leafItemIds,
448 IEnumerable<int> sharedComponentItemIds)
449 {
450 ArgumentNullException.ThrowIfNull(rootItemIds);
451 ArgumentNullException.ThrowIfNull(leafItemIds);
452 ArgumentNullException.ThrowIfNull(
453 sharedComponentItemIds);
454
455 ReplaceItemIds(
457 rootItemIds,
458 nameof(rootItemIds));
459
460 ReplaceItemIds(
462 leafItemIds,
463 nameof(leafItemIds));
464
465 ReplaceItemIds(
467 sharedComponentItemIds,
468 nameof(sharedComponentItemIds));
469
470 OnPropertyChanged(
471 nameof(RootItemIds));
472
473 OnPropertyChanged(
474 nameof(LeafItemIds));
475
476 OnPropertyChanged(
477 nameof(SharedComponentItemIds));
478
479 NotifyDerivedProperties();
480 }
481
482 /// <inheritdoc/>
483 public override string ToString()
484 {
485 return
486 $"Declared: {DeclaredType}; " +
487 $"detected: {DetectedType}; " +
488 $"status: {CheckStatus}; " +
489 $"depth: {MaximumDepth}";
490 }
491
492 private static void ReplaceItemIds(
493 ICollection<int> destination,
494 IEnumerable<int> source,
495 string parameterName)
496 {
497 int[] itemIds =
498 source
499 .Distinct()
500 .OrderBy(
501 itemId =>
502 itemId)
503 .ToArray();
504
505 if (itemIds.Any(
506 itemId =>
507 itemId <= 0))
508 {
509 throw new ArgumentException(
510 "Every item identifier must be " +
511 "strictly positive.",
512 parameterName);
513 }
514
515 destination.Clear();
516
517 foreach (int itemId in itemIds)
518 {
519 destination.Add(itemId);
520 }
521 }
522
523 private static DateTime ConvertToUtc(
524 DateTime value)
525 {
526 return value.Kind switch
527 {
528 DateTimeKind.Utc =>
529 value,
530
531 DateTimeKind.Local =>
532 value.ToUniversalTime(),
533
534 _ =>
535 DateTime.SpecifyKind(
536 value,
537 DateTimeKind.Utc)
538 };
539 }
540
541 private void NotifyDerivedProperties()
542 {
543 OnPropertyChanged(
544 nameof(HasDeclaredType));
545
546 OnPropertyChanged(
547 nameof(HasDetectedType));
548
549 OnPropertyChanged(
550 nameof(HasBeenAnalyzed));
551
552 OnPropertyChanged(
553 nameof(HasSharedComponents));
554
555 OnPropertyChanged(
556 nameof(HasDeclarationConflict));
557
558 OnPropertyChanged(
559 nameof(EffectiveType));
560
561 OnPropertyChanged(
563 }
564}
List< int > RootItemIds
Gets the identifiers of root items in the bill-of-materials graph.
ProductStructureType DeclaredType
Gets or sets the product-structure type declared by an author, publication or data provider.
string AnalysisComment
Gets or sets an optional human-readable comment about the product-structure analysis.
ProductStructureDescriptor(ProductStructureType declaredType)
Initializes a descriptor with a declared product-structure type.
bool HasSharedComponents
Gets a value indicating whether at least one component is shared by several immediate parent items.
bool HasCycle
Gets or sets a value indicating whether the analyzed bill-of-materials graph contains at least one cy...
bool HasDeclaredType
Gets a value indicating whether an author or source declared a product-structure type.
List< int > LeafItemIds
Gets the identifiers of leaf items in the bill-of-materials graph.
void MarkAsOutdated()
Marks the recorded automatic analysis as outdated.
string SupplyChainFingerprint
Gets or sets the fingerprint of the supply-chain data used during the product-structure analysis.
ProductStructureType EffectiveType
Gets the product-structure type that should normally be used by higher-level classification services.
DateTime? AnalyzedAtUtc
Gets or sets the UTC date and time at which the automatic analysis was performed.
bool CanBeUsedForClassification
Gets a value indicating whether the recorded structure type may currently be used for problem classif...
ProductStructureDescriptor()
Initializes an empty product-structure descriptor.
void ClearDetectedAnalysis()
Removes all automatically detected product-structure information while preserving the declared type.
void ReplaceAnalyzedItemSets(IEnumerable< int > rootItemIds, IEnumerable< int > leafItemIds, IEnumerable< int > sharedComponentItemIds)
Replaces the item sets generated by the automatic product-structure analysis.
int MaximumDepth
Gets or sets the maximum number of bill-of-materials relationships on a path from a leaf item to a ro...
ProductStructureCheckStatus CheckStatus
Gets or sets the current status of the product-structure declaration and automatic verification.
bool HasDetectedType
Gets a value indicating whether an automatic analysis detected a product-structure type.
ProductStructureType DetectedType
Gets or sets the product-structure type detected automatically from the supply-chain data.
string AnalyzerVersion
Gets or sets the version of the component that performed the product-structure analysis.
bool HasBeenAnalyzed
Gets a value indicating whether an automatic analysis date has been recorded.
List< int > SharedComponentItemIds
Gets the identifiers of components used by more than one immediate parent item.
bool HasDeclarationConflict
Gets a value indicating whether the declared and detected structure types contradict each other.
ProductStructureCheckStatus
Indicates the current status of the declaration, automatic detection and verification of a product bi...
@ DetectedOnly
A product-structure type has been detected automatically, but no declared type is available for compa...
@ Invalid
The bill-of-materials graph is invalid and cannot be assigned a valid product-structure type.
@ DeclaredAndConfirmed
The declared product-structure type is consistent with the automatically detected type.
@ NotAnalyzed
No product-structure type has been declared and no automatic analysis has been performed.
@ DeclaredOnly
A product-structure type has been declared, but the supply-chain bill of materials has not yet been a...
ProductStructureType
Identifies the structural category of a product bill-of-materials graph.
@ Unknown
The product-structure type is unknown or has not yet been analyzed.