| 1 | using System.Globalization; |
| 2 | using CascadeIDE.Cockpit.DataBus; |
| 3 | using CascadeIDE.Contracts; |
| 4 | using CascadeIDE.Models; |
| 5 | |
| 6 | namespace CascadeIDE.Features.HybridIndex.Application; |
| 7 | |
| 8 | /// <summary>Статические строки лампы/сводки HIS (MFD) по событию DataBus без VM.</summary> |
| 9 | [PresentationProjection("hybrid-index-his")] |
| 10 | public static class HybridIndexHisPresentationProjection |
| 11 | { |
| 12 | public static string LampText(HybridIndexStateChanged? last) => |
| 13 | last is null |
| 14 | ? "NO DATA" |
| 15 | : string.IsNullOrWhiteSpace(last.LastError) |
| 16 | ? "OK" |
| 17 | : "CAUTION"; |
| 18 | |
| 19 | public static string StateShort(HybridIndexStateChanged? last) => |
| 20 | last is null |
| 21 | ? "—" |
| 22 | : string.IsNullOrWhiteSpace(last.LastError) |
| 23 | ? "IDLE" |
| 24 | : "ERROR"; |
| 25 | |
| 26 | /// <param name="lastErrorBannerText">Текст ошибки для баннера HIS или «—», если пусто.</param> |
| 27 | public static string SecondMessageLine(string lastErrorBannerText) => |
| 28 | string.IsNullOrWhiteSpace(lastErrorBannerText) || lastErrorBannerText == "—" |
| 29 | ? "NO FAILURES" |
| 30 | : lastErrorBannerText; |
| 31 | |
| 32 | /// <summary>Шкала 0..1 по числу документов (верхняя граница — выбор UX).</summary> |
| 33 | public static double DocsGauge01(int documentCount, double maxDocs = 3000.0) |
| 34 | { |
| 35 | if (documentCount <= 0 || maxDocs <= 0) |
| 36 | return 0; |
| 37 | return Math.Clamp(documentCount / maxDocs, 0, 1); |
| 38 | } |
| 39 | |
| 40 | public static double FreshnessTotalMinutes(string? indexedAtIso, DateTimeOffset utcNow) |
| 41 | { |
| 42 | if (string.IsNullOrWhiteSpace(indexedAtIso)) |
| 43 | return 0; |
| 44 | if (!DateTimeOffset.TryParse(indexedAtIso, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var ts)) |
| 45 | return 0; |
| 46 | var age = utcNow - ts; |
| 47 | if (age < TimeSpan.Zero) |
| 48 | age = TimeSpan.Zero; |
| 49 | return age.TotalMinutes; |
| 50 | } |
| 51 | |
| 52 | public static string FreshnessMinutesRoundedText(double totalMinutes) |
| 53 | { |
| 54 | if (totalMinutes <= 0.5) |
| 55 | return "0"; |
| 56 | if (totalMinutes >= 10_000) |
| 57 | return "9999"; |
| 58 | return Math.Floor(totalMinutes).ToString(CultureInfo.InvariantCulture); |
| 59 | } |
| 60 | |
| 61 | public static string FreshnessEcamText(double totalMinutes) |
| 62 | { |
| 63 | if (totalMinutes <= 0.5) |
| 64 | return "0m"; |
| 65 | |
| 66 | if (totalMinutes < 60) |
| 67 | return $"{Math.Floor(totalMinutes).ToString(CultureInfo.InvariantCulture)}m"; |
| 68 | |
| 69 | var h = totalMinutes / 60.0; |
| 70 | if (h < 24) |
| 71 | return $"{Math.Floor(h).ToString(CultureInfo.InvariantCulture)}h"; |
| 72 | |
| 73 | var d = h / 24.0; |
| 74 | if (d >= 100) |
| 75 | return "99d"; |
| 76 | return $"{Math.Floor(d).ToString(CultureInfo.InvariantCulture)}d"; |
| 77 | } |
| 78 | |
| 79 | public static string IndexedAtOrDash(string? indexedAtIso) => |
| 80 | string.IsNullOrWhiteSpace(indexedAtIso) ? "—" : indexedAtIso; |
| 81 | |
| 82 | public static string FreshnessColonLine(string? indexedAtIso, DateTimeOffset utcNow) |
| 83 | { |
| 84 | if (string.IsNullOrWhiteSpace(indexedAtIso)) |
| 85 | return "freshness: —"; |
| 86 | if (!DateTimeOffset.TryParse(indexedAtIso, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var ts)) |
| 87 | return "freshness: ?"; |
| 88 | var age = utcNow - ts; |
| 89 | if (age < TimeSpan.Zero) |
| 90 | age = TimeSpan.Zero; |
| 91 | if (age.TotalHours >= 24) |
| 92 | return $"freshness: {Math.Floor(age.TotalDays)}d"; |
| 93 | if (age.TotalMinutes >= 60) |
| 94 | return $"freshness: {Math.Floor(age.TotalHours)}h"; |
| 95 | return $"freshness: {Math.Floor(age.TotalMinutes)}m"; |
| 96 | } |
| 97 | |
| 98 | public static string OptionalFieldOrDash(string? value) => |
| 99 | string.IsNullOrWhiteSpace(value) ? "—" : value; |
| 100 | |
| 101 | public static string LastErrorOrDash(string? lastError) => |
| 102 | string.IsNullOrWhiteSpace(lastError) ? "—" : lastError!; |
| 103 | |
| 104 | public static AnnunciatorLampItem LampItem(HybridIndexStateChanged? last) |
| 105 | { |
| 106 | if (last is null) |
| 107 | { |
| 108 | return new AnnunciatorLampItem( |
| 109 | Id: "hci", |
| 110 | Title: "HCI", |
| 111 | Detail: "No data yet.", |
| 112 | Level: AnnunciatorLampLevel.Advisory, |
| 113 | LampShortLabel: "HCI"); |
| 114 | } |
| 115 | |
| 116 | var level = string.IsNullOrWhiteSpace(last.LastError) |
| 117 | ? AnnunciatorLampLevel.Ok |
| 118 | : AnnunciatorLampLevel.Caution; |
| 119 | |
| 120 | var detail = string.IsNullOrWhiteSpace(last.LastError) |
| 121 | ? "OK" |
| 122 | : last.LastError!; |
| 123 | |
| 124 | return new AnnunciatorLampItem( |
| 125 | Id: "hci", |
| 126 | Title: "HCI", |
| 127 | Detail: detail, |
| 128 | Level: level, |
| 129 | LampShortLabel: "HCI"); |
| 130 | } |
| 131 | } |
| 132 | |