LotSizingDataModel.Checker 2.0.1
Reusable validation engine for structural, mathematical and objective consistency checks.
Loading...
Searching...
No Matches
SolutionCheckTextReportFormatter.cs
Go to the documentation of this file.
1using System.Globalization;
2using System.Text;
6
8
9/// <summary>
10/// Produces deterministic plain-text reports for checker results.
11/// </summary>
14{
15 private static readonly CultureInfo ReportCulture =
16 CultureInfo.InvariantCulture;
17
18 /// <inheritdoc/>
19 public string Format(
21 string? candidateName = null,
22 SolutionCheckReportOptions? options = null)
23 {
24 ArgumentNullException.ThrowIfNull(result);
25
26 options ??=
28
29 options.EnsureValid();
30
31 SolutionCheckSummary summary =
33
34 var builder =
35 new StringBuilder();
36
37 builder.AppendLine("Lot-sizing solution check");
38 builder.AppendLine(new string('=', 50));
39
40 if (!string.IsNullOrWhiteSpace(candidateName))
41 {
42 builder.Append("Candidate : ");
43 builder.AppendLine(candidateName);
44 }
45
46 builder.Append("Result : ");
47 builder.AppendLine(
48 summary.IsValid
49 ? "VALID"
50 : "INVALID");
51 builder.Append("Level : ");
52 builder.AppendLine(summary.Level.ToString());
53 builder.Append("Issues : ");
54 builder.Append(summary.IssueCount);
55 builder.Append(" (errors=");
56 builder.Append(summary.ErrorCount);
57 builder.Append(", warnings=");
58 builder.Append(summary.WarningCount);
59 builder.Append(", information=");
60 builder.Append(summary.InformationCount);
61 builder.AppendLine(")");
62
63 if (options.IncludeStageDetails)
64 {
65 AppendStages(builder, summary);
66 }
67
68 if (options.IncludeFeasibilityMetrics &&
69 summary.FeasibilityStatus !=
70 SolutionCheckStageStatus.NotRequested)
71 {
72 AppendFeasibility(builder, summary);
73 }
74
75 if (options.IncludeObjectiveMetrics &&
76 summary.ObjectiveStatus !=
77 SolutionCheckStageStatus.NotRequested)
78 {
79 AppendObjective(builder, summary);
80 }
81
82 AppendIssues(
83 builder,
84 result,
85 options);
86
87 return builder
88 .ToString()
89 .TrimEnd();
90 }
91
92 /// <inheritdoc/>
93 public string FormatBatch(
95 SolutionCheckReportOptions? options = null)
96 {
97 ArgumentNullException.ThrowIfNull(summary);
98
99 options ??=
101
102 options.EnsureValid();
103
104 var builder =
105 new StringBuilder();
106
107 builder.AppendLine("Lot-sizing solution-check batch");
108 builder.AppendLine(new string('=', 50));
109 builder.Append("Candidates : ");
110 builder.AppendLine(summary.CandidateCount.ToString(ReportCulture));
111 builder.Append("Valid : ");
112 builder.AppendLine(summary.ValidCandidateCount.ToString(ReportCulture));
113 builder.Append("Invalid : ");
114 builder.AppendLine(summary.InvalidCandidateCount.ToString(ReportCulture));
115 builder.Append("Errors : ");
116 builder.AppendLine(summary.ErrorCount.ToString(ReportCulture));
117 builder.Append("Warnings : ");
118 builder.AppendLine(summary.WarningCount.ToString(ReportCulture));
119 builder.Append("Information: ");
120 builder.AppendLine(summary.InformationCount.ToString(ReportCulture));
121 builder.Append("With constraint violations : ");
122 builder.AppendLine(
124 .ToString(ReportCulture));
125 builder.Append("With objective mismatch : ");
126 builder.AppendLine(
128 .ToString(ReportCulture));
129 builder.Append("Maximum constraint violation: ");
130 builder.AppendLine(
131 FormatDouble(summary.MaximumConstraintViolation));
132
133 if (summary.IssueCountsByKind.Count > 0)
134 {
135 builder.AppendLine();
136 builder.AppendLine("Issue categories");
137 builder.AppendLine("----------------");
138
139 foreach (SolutionCheckIssueCount issueCount in
140 summary.IssueCountsByKind
141 .OrderByDescending(item => item.Count)
142 .ThenBy(item => item.Kind))
143 {
144 builder.Append(" ");
145 builder.Append(issueCount.Kind);
146 builder.Append(" : ");
147 builder.AppendLine(
148 issueCount.Count.ToString(ReportCulture));
149 }
150 }
151
152 if (summary.Items.Count > 0)
153 {
154 builder.AppendLine();
155 builder.AppendLine("Candidates");
156 builder.AppendLine("----------");
157
158 foreach (SolutionCheckBatchItemSummary item in summary.Items)
159 {
160 builder.Append(" ");
161 builder.Append(
162 item.Summary.IsValid
163 ? "PASS"
164 : "FAIL");
165 builder.Append(" ");
166 builder.Append(item.CandidateKey);
167
168 if (!string.IsNullOrWhiteSpace(item.CandidateName))
169 {
170 builder.Append(" - ");
171 builder.Append(item.CandidateName);
172 }
173
174 builder.Append(" errors=");
175 builder.Append(item.Summary.ErrorCount);
176 builder.Append(" warnings=");
177 builder.Append(item.Summary.WarningCount);
178
179 if (item.Summary.ViolatedConstraintCount > 0)
180 {
181 builder.Append(" violatedConstraints=");
182 builder.Append(
184 }
185
186 if (item.Summary.ObjectiveStatus ==
188 {
189 builder.Append(" objectiveMismatch=true");
190 }
191
192 builder.AppendLine();
193 }
194 }
195
196 return builder
197 .ToString()
198 .TrimEnd();
199 }
200
201 private static void AppendStages(
202 StringBuilder builder,
203 SolutionCheckSummary summary)
204 {
205 builder.AppendLine();
206 builder.AppendLine("Stages");
207 builder.AppendLine("------");
208 AppendStage(builder, "Structure", summary.StructuralStatus);
209 AppendStage(builder, "Variable domains", summary.VariableDomainStatus);
210 AppendStage(builder, "Feasibility", summary.FeasibilityStatus);
211 AppendStage(builder, "Objective", summary.ObjectiveStatus);
212 }
213
214 private static void AppendStage(
215 StringBuilder builder,
216 string name,
218 {
219 builder.Append(" ");
220 builder.Append(name.PadRight(18));
221 builder.Append(" : ");
222 builder.AppendLine(FormatStageStatus(status));
223 }
224
225 private static string FormatStageStatus(
227 {
228 return status switch
229 {
231 "NOT REQUESTED",
233 "NOT COMPLETED",
235 "PASS",
237 "FAIL",
238 _ =>
239 "UNKNOWN"
240 };
241 }
242
243 private static void AppendFeasibility(
244 StringBuilder builder,
245 SolutionCheckSummary summary)
246 {
247 builder.AppendLine();
248 builder.AppendLine("Feasibility metrics");
249 builder.AppendLine("-------------------");
250 builder.Append(" Violated constraints : ");
251 builder.AppendLine(
252 summary.ViolatedConstraintCount.ToString(ReportCulture));
253 builder.Append(" Maximum violation : ");
254 builder.AppendLine(
255 FormatDouble(summary.MaximumConstraintViolation));
256 builder.Append(" Total violation : ");
257 builder.AppendLine(
258 FormatDouble(summary.TotalConstraintViolation));
259 }
260
261 private static void AppendObjective(
262 StringBuilder builder,
263 SolutionCheckSummary summary)
264 {
265 builder.AppendLine();
266 builder.AppendLine("Objective metrics");
267 builder.AppendLine("-----------------");
268 AppendNullableDouble(
269 builder,
270 "Reported objective",
271 summary.ReportedObjectiveValue);
272 AppendNullableDouble(
273 builder,
274 "Recomputed objective",
275 summary.RecomputedObjectiveValue);
276 AppendNullableDouble(
277 builder,
278 "Absolute difference",
279 summary.ObjectiveDifference);
280 AppendNullableDouble(
281 builder,
282 "Relative difference",
283 summary.ObjectiveRelativeDifference);
284 AppendNullableDouble(
285 builder,
286 "Comparison tolerance",
287 summary.ObjectiveComparisonTolerance);
288 }
289
290 private static void AppendNullableDouble(
291 StringBuilder builder,
292 string label,
293 double? value)
294 {
295 builder.Append(" ");
296 builder.Append(label.PadRight(24));
297 builder.Append(" : ");
298 builder.AppendLine(
299 value.HasValue
300 ? FormatDouble(value.Value)
301 : "n/a");
302 }
303
304 private static void AppendIssues(
305 StringBuilder builder,
306 SolutionCheckResult result,
307 SolutionCheckReportOptions options)
308 {
309 IEnumerable<SolutionCheckIssue> issues =
310 result.Issues.Where(
311 issue =>
312 ShouldIncludeIssue(
313 issue,
314 options));
315
316 if (options.SortIssuesBySeverity)
317 {
318 issues =
319 issues
320 .OrderByDescending(issue => issue.Severity)
321 .ThenBy(issue => issue.Kind)
322 .ThenBy(issue => issue.DomainKey ?? string.Empty)
323 .ThenBy(issue => issue.ConstraintName ?? string.Empty)
324 .ThenBy(issue => issue.Message);
325 }
326
327 List<SolutionCheckIssue> materializedIssues =
328 issues.ToList();
329
330 if (materializedIssues.Count == 0)
331 {
332 return;
333 }
334
335 int displayedCount =
336 options.MaximumDetailedIssues == 0
337 ? materializedIssues.Count
338 : Math.Min(
339 options.MaximumDetailedIssues,
340 materializedIssues.Count);
341
342 builder.AppendLine();
343 builder.Append("Diagnostics (");
344 builder.Append(materializedIssues.Count);
345 builder.AppendLine(")");
346 builder.AppendLine("-----------");
347
348 for (int index = 0;
349 index < displayedCount;
350 index++)
351 {
352 AppendIssue(
353 builder,
354 materializedIssues[index]);
355 }
356
357 int omittedCount =
358 materializedIssues.Count - displayedCount;
359
360 if (omittedCount > 0)
361 {
362 builder.Append(" ... ");
363 builder.Append(omittedCount);
364 builder.AppendLine(" additional diagnostic(s) omitted.");
365 }
366 }
367
368 private static bool ShouldIncludeIssue(
369 SolutionCheckIssue issue,
370 SolutionCheckReportOptions options)
371 {
372 return issue.Severity switch
373 {
374 SolutionCheckSeverity.Information =>
375 options.IncludeInformationIssues,
376 SolutionCheckSeverity.Warning =>
377 options.IncludeWarningIssues,
378 SolutionCheckSeverity.Error =>
379 options.IncludeErrorIssues,
380 _ =>
381 true
382 };
383 }
384
385 private static void AppendIssue(
386 StringBuilder builder,
387 SolutionCheckIssue issue)
388 {
389 builder.Append(" [");
390 builder.Append(issue.Severity.ToString().ToUpperInvariant());
391 builder.Append("][");
392 builder.Append(issue.Kind);
393 builder.Append("] ");
394 builder.Append(issue.Message);
395
396 if (!string.IsNullOrWhiteSpace(issue.DomainKey))
397 {
398 builder.Append(" | domain=");
399 builder.Append(issue.DomainKey);
400 }
401
402 if (!string.IsNullOrWhiteSpace(issue.ConstraintName))
403 {
404 builder.Append(" | constraint=");
405 builder.Append(issue.ConstraintName);
406 }
407
408 if (issue.ActualValue.HasValue)
409 {
410 builder.Append(" | actual=");
411 builder.Append(FormatDouble(issue.ActualValue.Value));
412 }
413
414 if (issue.ExpectedValue.HasValue)
415 {
416 builder.Append(" | expected=");
417 builder.Append(FormatDouble(issue.ExpectedValue.Value));
418 }
419
420 if (issue.Violation.HasValue)
421 {
422 builder.Append(" | violation=");
423 builder.Append(FormatDouble(issue.Violation.Value));
424 }
425
426 builder.AppendLine();
427 }
428
429 private static string FormatDouble(
430 double value)
431 {
432 return value.ToString(
433 "G17",
434 ReportCulture);
435 }
436}
Associates a batch candidate identifier with its compact checker summary.
string CandidateKey
Gets or sets the caller-defined candidate identifier.
SolutionCheckSummary Summary
Gets or sets the compact checker summary for this candidate.
string? CandidateName
Gets or sets an optional human-readable candidate name.
Contains aggregated metrics for a collection of checked solutions.
int CandidateWithObjectiveMismatchCount
Gets or sets the number of candidates for which objective checking completed and detected an inconsis...
int ValidCandidateCount
Gets or sets the number of candidates that passed all requested checks.
int InvalidCandidateCount
Gets or sets the number of candidates that did not pass all requested checks.
List< SolutionCheckBatchItemSummary > Items
Gets or sets the per-candidate summaries in insertion order.
List< SolutionCheckIssueCount > IssueCountsByKind
Gets or sets issue counts aggregated by category.
double MaximumConstraintViolation
Gets or sets the largest constraint violation observed in the batch.
int CandidateWithConstraintViolationCount
Gets or sets the number of candidates with at least one violated mathematical constraint.
int InformationCount
Gets or sets the total number of informational diagnostics.
int CandidateCount
Gets or sets the number of candidates represented by the summary.
Associates an issue category with its occurrence count.
int Count
Gets or sets the number of issues in this category.
SolutionCheckIssueKind Kind
Gets or sets the issue category.
Builds compact summaries from complete checker results.
static SolutionCheckSummary Create(SolutionCheckResult result)
Creates a compact machine-oriented summary.
Provides a compact machine-oriented summary of one solution check.
SolutionCheckLevel Level
Gets or sets the checking level requested for the candidate.
SolutionCheckStageStatus ObjectiveStatus
Gets or sets the objective-check-stage status.
int InformationCount
Gets or sets the number of informational diagnostics.
SolutionCheckStageStatus StructuralStatus
Gets or sets the structural-stage status.
int ErrorCount
Gets or sets the number of error diagnostics.
int IssueCount
Gets or sets the total number of diagnostics.
int ViolatedConstraintCount
Gets or sets the number of violated mathematical constraints.
int WarningCount
Gets or sets the number of warning diagnostics.
SolutionCheckStageStatus FeasibilityStatus
Gets or sets the mathematical-feasibility-stage status.
bool IsValid
Gets or sets whether every requested check passed.
SolutionCheckStageStatus VariableDomainStatus
Gets or sets the variable-domain-stage status.
Produces deterministic plain-text reports for checker results.
string Format(SolutionCheckResult result, string? candidateName=null, SolutionCheckReportOptions? options=null)
Formats one checker result.A deterministic human-readable report.
string FormatBatch(SolutionCheckBatchSummary summary, SolutionCheckReportOptions? options=null)
Formats an aggregated batch summary.A deterministic human-readable batch report.
SolutionCheckIssueKind Kind
Gets or sets the issue category.
string? DomainKey
Gets or sets the related mathematical-domain key, when available.
SolutionCheckSeverity Severity
Gets or sets the issue severity.
double? Violation
Gets or sets the numerical violation magnitude, when meaningful.
double? ActualValue
Gets or sets the actual value involved in the issue, when meaningful.
double? ExpectedValue
Gets or sets an expected or reference value, when meaningful.
string? ConstraintName
Gets or sets the related mathematical constraint name, when available.
string Message
Gets or sets a human-readable diagnostic message.
Contains the complete result of checking one lot-sizing solution.
IReadOnlyList< SolutionCheckIssue > Issues
Gets all checker diagnostics.
Formats independent solution-check results for human consumption.
SolutionCheckStageStatus
Describes the execution and outcome of one logical checker stage.
@ Passed
The stage completed and its checked conditions were satisfied.
@ NotCompleted
The stage was requested but could not be executed to completion.
@ NotRequested
The stage was not requested by the selected checking level.
@ Failed
The stage completed and at least one checked condition failed.