using ZB.MOM.WW.ScadaBridge.Commons.Messages.Health; namespace ZB.MOM.WW.ScadaBridge.HealthMonitoring; /// /// In-memory state for a single site's health, stored by the central aggregator. /// Immutable: every state transition produces a new instance which the aggregator /// installs into its ConcurrentDictionary via an atomic compare-and-swap. /// This makes handing the reference straight to UI callers safe — a consumer can /// never observe a torn or half-applied update. /// public sealed record SiteHealthState { /// Gets the unique identifier of the site this state record belongs to. public required string SiteId { get; init; } /// /// The latest full received for the site, or /// null if the site is known only via heartbeats and has not yet sent /// a report. /// public SiteHealthReport? LatestReport { get; init; } /// /// Time the latest full was processed, or /// null if the site is known only via heartbeats and has not yet sent /// a report. Used by the UI to surface report staleness during failover; /// the null case must be rendered as "no report yet" rather than as a /// timestamp (a default sentinel would display as year-0001). /// public DateTimeOffset? LastReportReceivedAt { get; init; } /// /// Time the most recent signal of any kind (full report OR heartbeat) was /// received. Drives offline detection — heartbeats from the standby keep the /// site marked online even when the active node is unable to produce a report /// (mid-failover, brief stalls). Heartbeat cadence is owned by the Cluster /// Infrastructure / SiteCommunicationActor (every /// CommunicationOptions.TransportHeartbeatInterval — 5s by default). /// public DateTimeOffset LastHeartbeatAt { get; init; } /// /// Gets the instant this site was first observed by the aggregator (via a /// report or a heartbeat); null only for states not built by the /// aggregator (hand-constructed fixtures). Anchors metrics-staleness for a /// site that has NEVER delivered a report (review 01 round-2 N3): with /// null the staleness check previously /// skipped the site forever. cannot anchor /// this — every heartbeat advances it. /// public DateTimeOffset? FirstSeenAt { get; init; } /// Gets the sequence number of the last accepted health report, used to reject out-of-order duplicates. public long LastSequenceNumber { get; init; } /// Gets a value indicating whether the site is currently considered online. public bool IsOnline { get; init; } /// /// Gets a value indicating whether the site is online (heartbeats still /// arriving) but has sent no full within /// . This is a signal /// distinct from liveness: a site whose HealthReportSender died keeps /// heartbeating and would otherwise show "online with frozen metrics forever". /// Cleared whenever a fresh report is processed. Heartbeats never set or clear /// it — they say nothing about the metrics pipeline. /// public bool IsMetricsStale { get; init; } /// /// Gets the instant of the last online↔offline flip, or null if the /// site has never changed status since it was first observed. Answers "when /// did this site drop / come back". Not moved by report/heartbeat traffic that /// leaves the online/offline status unchanged, nor by the metrics-stale flag. /// public DateTimeOffset? LastStatusChangeAt { get; init; } }