diff --git a/ThingConnect.Pulse.Server/Data/Entities.cs b/ThingConnect.Pulse.Server/Data/Entities.cs
index 29a344d..4e588a2 100644
--- a/ThingConnect.Pulse.Server/Data/Entities.cs
+++ b/ThingConnect.Pulse.Server/Data/Entities.cs
@@ -1,9 +1,27 @@
-// ThingConnect Pulse - EF Core Entities (v1)
+// ThingConnect Pulse - EF Core Entities (v2)
+// Updated for ICMP Fallback + Outage Classification
 namespace ThingConnect.Pulse.Server.Data;
 
 public enum ProbeType { icmp, tcp, http }
 public enum UpDown { up, down }
 
+/// 
+/// Status classification for failed probe analysis.
+/// 
+public enum Classification
+{
+    None = -1, // Explicitly healthy, no outage detected
+    Unknown = 0, // Not enough information to classify
+    Network = 1, // Host unreachable (ICMP + service fail)
+    Service = 2, // Service down, host reachable via ICMP
+    Intermittent = 3, // Flapping / unstable
+    Performance = 4, // RTT above threshold
+    PartialService = 5, // HTTP error, TCP works
+    DnsResolution = 6, // DNS fails, IP works
+    Congestion = 7, // Correlated latency
+    Maintenance = 8 // Planned downtime
+}
+
 public record GroupVm(string Id, string Name, string? ParentId, string? Color);
 public record EndpointVm(Guid Id, string Name, GroupVm Group, ProbeType Type, string Host,
                          int? Port, string? HttpPath, string? HttpMatch,
@@ -50,6 +68,13 @@ public sealed class CheckResultRaw
     public UpDown Status { get; set; }
     public double? RttMs { get; set; }
     public string? Error { get; set; }
+
+    // ๐น New fields for fallback probe
+    public bool? FallbackAttempted { get; set; }
+    public UpDown? FallbackStatus { get; set; }
+    public double? FallbackRttMs { get; set; }
+    public string? FallbackError { get; set; }
+    public Classification? Classification { get; set; }
 }
 
 public sealed class Outage
@@ -61,6 +86,7 @@ public sealed class Outage
     public long? EndedTs { get; set; }
     public int? DurationSeconds { get; set; }
     public string? LastError { get; set; }
+    public Classification? Classification { get; set; }
 
     /// 
     /// Gets or sets timestamp when monitoring was lost during this outage (service downtime).
diff --git a/ThingConnect.Pulse.Server/Data/PulseDbContext.cs b/ThingConnect.Pulse.Server/Data/PulseDbContext.cs
index ebbaf7c..f21ad85 100644
--- a/ThingConnect.Pulse.Server/Data/PulseDbContext.cs
+++ b/ThingConnect.Pulse.Server/Data/PulseDbContext.cs
@@ -65,6 +65,17 @@ protected override void OnModelCreating(ModelBuilder b)
             e.HasKey(x => x.Id);
             e.Property(x => x.Status).HasConversion().IsRequired();
             e.Property(x => x.RttMs).HasColumnType("double precision");
+
+            // New Fallback fields
+            e.Property(x => x.FallbackAttempted);
+            e.Property(x => x.FallbackStatus).HasConversion();
+            e.Property(x => x.FallbackRttMs).HasColumnType("double precision");
+            e.Property(x => x.FallbackError);
+
+            // Classification field
+            e.Property(x => x.Classification)
+            .HasConversion();
+
             e.HasIndex(x => new { x.EndpointId, x.Ts });
         });
 
@@ -74,6 +85,10 @@ protected override void OnModelCreating(ModelBuilder b)
             e.HasKey(x => x.Id);
             e.HasIndex(x => new { x.EndpointId, x.StartedTs });
             e.HasIndex(x => new { x.EndpointId, x.EndedTs });
+
+            // New Classification field
+            e.Property(x => x.Classification)
+            .HasConversion();
         });
 
         b.Entity(e =>
diff --git a/ThingConnect.Pulse.Server/Migrations/20250926070803_AddFallbackAndOutageClassification.Designer.cs b/ThingConnect.Pulse.Server/Migrations/20250926070803_AddFallbackAndOutageClassification.Designer.cs
new file mode 100644
index 0000000..ae19086
--- /dev/null
+++ b/ThingConnect.Pulse.Server/Migrations/20250926070803_AddFallbackAndOutageClassification.Designer.cs
@@ -0,0 +1,768 @@
+๏ปฟ// 
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using ThingConnect.Pulse.Server.Data;
+
+#nullable disable
+
+namespace ThingConnect.Pulse.Server.Migrations
+{
+    [DbContext(typeof(PulseDbContext))]
+    [Migration("20250926070803_AddFallbackAndOutageClassification")]
+    partial class AddFallbackAndOutageClassification
+    {
+        /// 
+        protected override void BuildTargetModel(ModelBuilder modelBuilder)
+        {
+#pragma warning disable 612, 618
+            modelBuilder.HasAnnotation("ProductVersion", "8.0.0");
+
+            modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
+                {
+                    b.Property("Id")
+                        .HasColumnType("TEXT");
+
+                    b.Property("ConcurrencyStamp")
+                        .IsConcurrencyToken()
+                        .HasColumnType("TEXT");
+
+                    b.Property("Name")
+                        .HasMaxLength(256)
+                        .HasColumnType("TEXT");
+
+                    b.Property("NormalizedName")
+                        .HasMaxLength(256)
+                        .HasColumnType("TEXT");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("NormalizedName")
+                        .IsUnique()
+                        .HasDatabaseName("RoleNameIndex");
+
+                    b.ToTable("AspNetRoles", (string)null);
+                });
+
+            modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
+                {
+                    b.Property("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property("ClaimType")
+                        .HasColumnType("TEXT");
+
+                    b.Property("ClaimValue")
+                        .HasColumnType("TEXT");
+
+                    b.Property("RoleId")
+                        .IsRequired()
+                        .HasColumnType("TEXT");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("RoleId");
+
+                    b.ToTable("AspNetRoleClaims", (string)null);
+                });
+
+            modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
+                {
+                    b.Property("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property("ClaimType")
+                        .HasColumnType("TEXT");
+
+                    b.Property("ClaimValue")
+                        .HasColumnType("TEXT");
+
+                    b.Property("UserId")
+                        .IsRequired()
+                        .HasColumnType("TEXT");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("UserId");
+
+                    b.ToTable("AspNetUserClaims", (string)null);
+                });
+
+            modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
+                {
+                    b.Property("LoginProvider")
+                        .HasColumnType("TEXT");
+
+                    b.Property("ProviderKey")
+                        .HasColumnType("TEXT");
+
+                    b.Property("ProviderDisplayName")
+                        .HasColumnType("TEXT");
+
+                    b.Property("UserId")
+                        .IsRequired()
+                        .HasColumnType("TEXT");
+
+                    b.HasKey("LoginProvider", "ProviderKey");
+
+                    b.HasIndex("UserId");
+
+                    b.ToTable("AspNetUserLogins", (string)null);
+                });
+
+            modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b =>
+                {
+                    b.Property("UserId")
+                        .HasColumnType("TEXT");
+
+                    b.Property("RoleId")
+                        .HasColumnType("TEXT");
+
+                    b.HasKey("UserId", "RoleId");
+
+                    b.HasIndex("RoleId");
+
+                    b.ToTable("AspNetUserRoles", (string)null);
+                });
+
+            modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
+                {
+                    b.Property("UserId")
+                        .HasColumnType("TEXT");
+
+                    b.Property("LoginProvider")
+                        .HasColumnType("TEXT");
+
+                    b.Property("Name")
+                        .HasColumnType("TEXT");
+
+                    b.Property("Value")
+                        .HasColumnType("TEXT");
+
+                    b.HasKey("UserId", "LoginProvider", "Name");
+
+                    b.ToTable("AspNetUserTokens", (string)null);
+                });
+
+            modelBuilder.Entity("ThingConnect.Pulse.Server.Data.ApplicationUser", b =>
+                {
+                    b.Property("Id")
+                        .HasColumnType("TEXT");
+
+                    b.Property("AccessFailedCount")
+                        .HasColumnType("INTEGER");
+
+                    b.Property("ConcurrencyStamp")
+                        .IsConcurrencyToken()
+                        .HasColumnType("TEXT");
+
+                    b.Property("CreatedAt")
+                        .HasColumnType("TEXT");
+
+                    b.Property("Email")
+                        .HasMaxLength(256)
+                        .HasColumnType("TEXT");
+
+                    b.Property("EmailConfirmed")
+                        .HasColumnType("INTEGER");
+
+                    b.Property("IsActive")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER")
+                        .HasDefaultValue(true);
+
+                    b.Property("LastLoginAt")
+                        .HasColumnType("TEXT");
+
+                    b.Property("LockoutEnabled")
+                        .HasColumnType("INTEGER");
+
+                    b.Property("LockoutEnd")
+                        .HasColumnType("TEXT");
+
+                    b.Property("NormalizedEmail")
+                        .HasMaxLength(256)
+                        .HasColumnType("TEXT");
+
+                    b.Property("NormalizedUserName")
+                        .HasMaxLength(256)
+                        .HasColumnType("TEXT");
+
+                    b.Property("PasswordHash")
+                        .HasColumnType("TEXT");
+
+                    b.Property("PhoneNumber")
+                        .HasColumnType("TEXT");
+
+                    b.Property("PhoneNumberConfirmed")
+                        .HasColumnType("INTEGER");
+
+                    b.Property("Role")
+                        .IsRequired()
+                        .HasMaxLength(50)
+                        .HasColumnType("TEXT");
+
+                    b.Property("SecurityStamp")
+                        .HasColumnType("TEXT");
+
+                    b.Property("TwoFactorEnabled")
+                        .HasColumnType("INTEGER");
+
+                    b.Property("UpdatedAt")
+                        .HasColumnType("TEXT");
+
+                    b.Property("UserName")
+                        .HasMaxLength(256)
+                        .HasColumnType("TEXT");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("IsActive");
+
+                    b.HasIndex("NormalizedEmail")
+                        .HasDatabaseName("EmailIndex");
+
+                    b.HasIndex("NormalizedUserName")
+                        .IsUnique()
+                        .HasDatabaseName("UserNameIndex");
+
+                    b.HasIndex("Role");
+
+                    b.ToTable("AspNetUsers", (string)null);
+                });
+
+            modelBuilder.Entity("ThingConnect.Pulse.Server.Data.CheckResultRaw", b =>
+                {
+                    b.Property("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property("Classification")
+                        .HasColumnType("INTEGER");
+
+                    b.Property("EndpointId")
+                        .HasColumnType("TEXT");
+
+                    b.Property("Error")
+                        .HasColumnType("TEXT");
+
+                    b.Property("FallbackAttempted")
+                        .HasColumnType("INTEGER");
+
+                    b.Property("FallbackError")
+                        .HasColumnType("TEXT");
+
+                    b.Property("FallbackRttMs")
+                        .HasColumnType("double precision");
+
+                    b.Property("FallbackStatus")
+                        .HasColumnType("TEXT");
+
+                    b.Property("RttMs")
+                        .HasColumnType("double precision");
+
+                    b.Property("Status")
+                        .IsRequired()
+                        .HasColumnType("TEXT");
+
+                    b.Property("Ts")
+                        .HasColumnType("INTEGER");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("EndpointId", "Ts");
+
+                    b.ToTable("check_result_raw", (string)null);
+                });
+
+            modelBuilder.Entity("ThingConnect.Pulse.Server.Data.ConfigVersion", b =>
+                {
+                    b.Property("Id")
+                        .HasMaxLength(40)
+                        .HasColumnType("TEXT");
+
+                    b.Property("Actor")
+                        .HasColumnType("TEXT");
+
+                    b.Property("AppliedTs")
+                        .HasColumnType("INTEGER");
+
+                    b.Property("FileHash")
+                        .IsRequired()
+                        .HasColumnType("TEXT");
+
+                    b.Property("FilePath")
+                        .IsRequired()
+                        .HasColumnType("TEXT");
+
+                    b.Property("Note")
+                        .HasColumnType("TEXT");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("AppliedTs");
+
+                    b.ToTable("config_version", (string)null);
+                });
+
+            modelBuilder.Entity("ThingConnect.Pulse.Server.Data.Endpoint", b =>
+                {
+                    b.Property("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("TEXT");
+
+                    b.Property("Enabled")
+                        .HasColumnType("INTEGER");
+
+                    b.Property("ExpectedRttMs")
+                        .HasColumnType("INTEGER");
+
+                    b.Property("GroupId")
+                        .IsRequired()
+                        .HasMaxLength(64)
+                        .HasColumnType("TEXT");
+
+                    b.Property("Host")
+                        .IsRequired()
+                        .HasMaxLength(253)
+                        .HasColumnType("TEXT");
+
+                    b.Property("HttpMatch")
+                        .HasMaxLength(256)
+                        .HasColumnType("TEXT");
+
+                    b.Property("HttpPath")
+                        .HasMaxLength(512)
+                        .HasColumnType("TEXT");
+
+                    b.Property("IntervalSeconds")
+                        .HasColumnType("INTEGER");
+
+                    b.Property("LastChangeTs")
+                        .HasColumnType("INTEGER");
+
+                    b.Property("LastRttMs")
+                        .HasColumnType("REAL");
+
+                    b.Property("LastStatus")
+                        .HasColumnType("INTEGER");
+
+                    b.Property("Name")
+                        .IsRequired()
+                        .HasMaxLength(200)
+                        .HasColumnType("TEXT");
+
+                    b.Property("Notes")
+                        .HasColumnType("TEXT");
+
+                    b.Property("Port")
+                        .HasColumnType("INTEGER");
+
+                    b.Property("Retries")
+                        .HasColumnType("INTEGER");
+
+                    b.Property("TimeoutMs")
+                        .HasColumnType("INTEGER");
+
+                    b.Property("Type")
+                        .IsRequired()
+                        .HasColumnType("TEXT");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("Host");
+
+                    b.HasIndex("GroupId", "Name");
+
+                    b.ToTable("endpoint", (string)null);
+                });
+
+            modelBuilder.Entity("ThingConnect.Pulse.Server.Data.Group", b =>
+                {
+                    b.Property("Id")
+                        .HasMaxLength(64)
+                        .HasColumnType("TEXT");
+
+                    b.Property("Color")
+                        .HasMaxLength(7)
+                        .HasColumnType("TEXT");
+
+                    b.Property("Name")
+                        .IsRequired()
+                        .HasMaxLength(200)
+                        .HasColumnType("TEXT");
+
+                    b.Property("ParentId")
+                        .HasColumnType("TEXT");
+
+                    b.Property("SortOrder")
+                        .HasColumnType("INTEGER");
+
+                    b.HasKey("Id");
+
+                    b.ToTable("group", (string)null);
+                });
+
+            modelBuilder.Entity("ThingConnect.Pulse.Server.Data.MonitoringSession", b =>
+                {
+                    b.Property("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property("EndedTs")
+                        .HasColumnType("INTEGER");
+
+                    b.Property("LastActivityTs")
+                        .HasColumnType("INTEGER");
+
+                    b.Property("ShutdownReason")
+                        .HasMaxLength(200)
+                        .HasColumnType("TEXT");
+
+                    b.Property("StartedTs")
+                        .HasColumnType("INTEGER");
+
+                    b.Property("Version")
+                        .HasMaxLength(50)
+                        .HasColumnType("TEXT");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("EndedTs");
+
+                    b.HasIndex("StartedTs");
+
+                    b.ToTable("monitoring_session", (string)null);
+                });
+
+            modelBuilder.Entity("ThingConnect.Pulse.Server.Data.Notification", b =>
+                {
+                    b.Property("Id")
+                        .HasMaxLength(64)
+                        .HasColumnType("TEXT");
+
+                    b.Property("ActionText")
+                        .HasMaxLength(100)
+                        .HasColumnType("TEXT");
+
+                    b.Property("ActionUrl")
+                        .HasMaxLength(512)
+                        .HasColumnType("TEXT");
+
+                    b.Property("CreatedTs")
+                        .HasColumnType("INTEGER");
+
+                    b.Property("IsRead")
+                        .HasColumnType("INTEGER");
+
+                    b.Property("IsShown")
+                        .HasColumnType("INTEGER");
+
+                    b.Property("Message")
+                        .IsRequired()
+                        .HasMaxLength(1000)
+                        .HasColumnType("TEXT");
+
+                    b.Property("Priority")
+                        .IsRequired()
+                        .HasColumnType("TEXT");
+
+                    b.Property("ReadTs")
+                        .HasColumnType("INTEGER");
+
+                    b.Property("ShowOnce")
+                        .HasColumnType("INTEGER");
+
+                    b.Property("ShownTs")
+                        .HasColumnType("INTEGER");
+
+                    b.Property("TargetVersions")
+                        .HasMaxLength(200)
+                        .HasColumnType("TEXT");
+
+                    b.Property("Title")
+                        .IsRequired()
+                        .HasMaxLength(200)
+                        .HasColumnType("TEXT");
+
+                    b.Property("Type")
+                        .IsRequired()
+                        .HasColumnType("TEXT");
+
+                    b.Property("ValidFromTs")
+                        .HasColumnType("INTEGER");
+
+                    b.Property("ValidUntilTs")
+                        .HasColumnType("INTEGER");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("ValidFromTs");
+
+                    b.HasIndex("ValidUntilTs");
+
+                    b.HasIndex("IsRead", "ValidFromTs");
+
+                    b.HasIndex("Priority", "ValidFromTs");
+
+                    b.ToTable("notification", (string)null);
+                });
+
+            modelBuilder.Entity("ThingConnect.Pulse.Server.Data.NotificationFetch", b =>
+                {
+                    b.Property("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property("Error")
+                        .HasMaxLength(500)
+                        .HasColumnType("TEXT");
+
+                    b.Property("FetchTs")
+                        .HasColumnType("INTEGER");
+
+                    b.Property("NotificationCount")
+                        .HasColumnType("INTEGER");
+
+                    b.Property("RemoteLastUpdated")
+                        .IsRequired()
+                        .HasMaxLength(50)
+                        .HasColumnType("TEXT");
+
+                    b.Property("RemoteVersion")
+                        .IsRequired()
+                        .HasMaxLength(50)
+                        .HasColumnType("TEXT");
+
+                    b.Property("Success")
+                        .HasColumnType("INTEGER");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("FetchTs");
+
+                    b.HasIndex("Success");
+
+                    b.ToTable("notification_fetch", (string)null);
+                });
+
+            modelBuilder.Entity("ThingConnect.Pulse.Server.Data.Outage", b =>
+                {
+                    b.Property("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("INTEGER");
+
+                    b.Property("Classification")
+                        .HasColumnType("INTEGER");
+
+                    b.Property("DurationSeconds")
+                        .HasColumnType("INTEGER");
+
+                    b.Property("EndedTs")
+                        .HasColumnType("INTEGER");
+
+                    b.Property("EndpointId")
+                        .HasColumnType("TEXT");
+
+                    b.Property("HasMonitoringGap")
+                        .HasColumnType("INTEGER");
+
+                    b.Property("LastError")
+                        .HasColumnType("TEXT");
+
+                    b.Property("MonitoringStoppedTs")
+                        .HasColumnType("INTEGER");
+
+                    b.Property("StartedTs")
+                        .HasColumnType("INTEGER");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("EndpointId", "EndedTs");
+
+                    b.HasIndex("EndpointId", "StartedTs");
+
+                    b.ToTable("outage", (string)null);
+                });
+
+            modelBuilder.Entity("ThingConnect.Pulse.Server.Data.Rollup15m", b =>
+                {
+                    b.Property("EndpointId")
+                        .HasColumnType("TEXT");
+
+                    b.Property("BucketTs")
+                        .HasColumnType("INTEGER");
+
+                    b.Property("AvgRttMs")
+                        .HasColumnType("double precision");
+
+                    b.Property("DownEvents")
+                        .HasColumnType("INTEGER");
+
+                    b.Property("UpPct")
+                        .HasColumnType("REAL");
+
+                    b.HasKey("EndpointId", "BucketTs");
+
+                    b.HasIndex("BucketTs");
+
+                    b.ToTable("rollup_15m", (string)null);
+                });
+
+            modelBuilder.Entity("ThingConnect.Pulse.Server.Data.RollupDaily", b =>
+                {
+                    b.Property("EndpointId")
+                        .HasColumnType("TEXT");
+
+                    b.Property("BucketDate")
+                        .HasColumnType("INTEGER");
+
+                    b.Property("AvgRttMs")
+                        .HasColumnType("double precision");
+
+                    b.Property("DownEvents")
+                        .HasColumnType("INTEGER");
+
+                    b.Property("UpPct")
+                        .HasColumnType("REAL");
+
+                    b.HasKey("EndpointId", "BucketDate");
+
+                    b.HasIndex("BucketDate");
+
+                    b.ToTable("rollup_daily", (string)null);
+                });
+
+            modelBuilder.Entity("ThingConnect.Pulse.Server.Data.Setting", b =>
+                {
+                    b.Property("K")
+                        .HasMaxLength(100)
+                        .HasColumnType("TEXT");
+
+                    b.Property("V")
+                        .IsRequired()
+                        .HasColumnType("TEXT");
+
+                    b.HasKey("K");
+
+                    b.ToTable("setting", (string)null);
+                });
+
+            modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
+                {
+                    b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
+                        .WithMany()
+                        .HasForeignKey("RoleId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+                });
+
+            modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
+                {
+                    b.HasOne("ThingConnect.Pulse.Server.Data.ApplicationUser", null)
+                        .WithMany()
+                        .HasForeignKey("UserId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+                });
+
+            modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
+                {
+                    b.HasOne("ThingConnect.Pulse.Server.Data.ApplicationUser", null)
+                        .WithMany()
+                        .HasForeignKey("UserId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+                });
+
+            modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b =>
+                {
+                    b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
+                        .WithMany()
+                        .HasForeignKey("RoleId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.HasOne("ThingConnect.Pulse.Server.Data.ApplicationUser", null)
+                        .WithMany()
+                        .HasForeignKey("UserId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+                });
+
+            modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
+                {
+                    b.HasOne("ThingConnect.Pulse.Server.Data.ApplicationUser", null)
+                        .WithMany()
+                        .HasForeignKey("UserId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+                });
+
+            modelBuilder.Entity("ThingConnect.Pulse.Server.Data.CheckResultRaw", b =>
+                {
+                    b.HasOne("ThingConnect.Pulse.Server.Data.Endpoint", "Endpoint")
+                        .WithMany()
+                        .HasForeignKey("EndpointId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.Navigation("Endpoint");
+                });
+
+            modelBuilder.Entity("ThingConnect.Pulse.Server.Data.Endpoint", b =>
+                {
+                    b.HasOne("ThingConnect.Pulse.Server.Data.Group", "Group")
+                        .WithMany("Endpoints")
+                        .HasForeignKey("GroupId")
+                        .OnDelete(DeleteBehavior.Restrict)
+                        .IsRequired();
+
+                    b.Navigation("Group");
+                });
+
+            modelBuilder.Entity("ThingConnect.Pulse.Server.Data.Outage", b =>
+                {
+                    b.HasOne("ThingConnect.Pulse.Server.Data.Endpoint", "Endpoint")
+                        .WithMany()
+                        .HasForeignKey("EndpointId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.Navigation("Endpoint");
+                });
+
+            modelBuilder.Entity("ThingConnect.Pulse.Server.Data.Rollup15m", b =>
+                {
+                    b.HasOne("ThingConnect.Pulse.Server.Data.Endpoint", "Endpoint")
+                        .WithMany()
+                        .HasForeignKey("EndpointId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.Navigation("Endpoint");
+                });
+
+            modelBuilder.Entity("ThingConnect.Pulse.Server.Data.RollupDaily", b =>
+                {
+                    b.HasOne("ThingConnect.Pulse.Server.Data.Endpoint", "Endpoint")
+                        .WithMany()
+                        .HasForeignKey("EndpointId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.Navigation("Endpoint");
+                });
+
+            modelBuilder.Entity("ThingConnect.Pulse.Server.Data.Group", b =>
+                {
+                    b.Navigation("Endpoints");
+                });
+#pragma warning restore 612, 618
+        }
+    }
+}
diff --git a/ThingConnect.Pulse.Server/Migrations/20250926070803_AddFallbackAndOutageClassification.cs b/ThingConnect.Pulse.Server/Migrations/20250926070803_AddFallbackAndOutageClassification.cs
new file mode 100644
index 0000000..c8b60c8
--- /dev/null
+++ b/ThingConnect.Pulse.Server/Migrations/20250926070803_AddFallbackAndOutageClassification.cs
@@ -0,0 +1,78 @@
+๏ปฟusing Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace ThingConnect.Pulse.Server.Migrations
+{
+    /// 
+    public partial class AddFallbackAndOutageClassification : Migration
+    {
+        /// 
+        protected override void Up(MigrationBuilder migrationBuilder)
+        {
+            migrationBuilder.AddColumn(
+                name: "Classification",
+                table: "outage",
+                type: "INTEGER",
+                nullable: true);
+
+            migrationBuilder.AddColumn(
+                name: "Classification",
+                table: "check_result_raw",
+                type: "INTEGER",
+                nullable: true);
+
+            migrationBuilder.AddColumn(
+                name: "FallbackAttempted",
+                table: "check_result_raw",
+                type: "INTEGER",
+                nullable: true);
+
+            migrationBuilder.AddColumn(
+                name: "FallbackError",
+                table: "check_result_raw",
+                type: "TEXT",
+                nullable: true);
+
+            migrationBuilder.AddColumn(
+                name: "FallbackRttMs",
+                table: "check_result_raw",
+                type: "double precision",
+                nullable: true);
+
+            migrationBuilder.AddColumn(
+                name: "FallbackStatus",
+                table: "check_result_raw",
+                type: "TEXT",
+                nullable: true);
+        }
+
+        /// 
+        protected override void Down(MigrationBuilder migrationBuilder)
+        {
+            migrationBuilder.DropColumn(
+                name: "Classification",
+                table: "outage");
+
+            migrationBuilder.DropColumn(
+                name: "Classification",
+                table: "check_result_raw");
+
+            migrationBuilder.DropColumn(
+                name: "FallbackAttempted",
+                table: "check_result_raw");
+
+            migrationBuilder.DropColumn(
+                name: "FallbackError",
+                table: "check_result_raw");
+
+            migrationBuilder.DropColumn(
+                name: "FallbackRttMs",
+                table: "check_result_raw");
+
+            migrationBuilder.DropColumn(
+                name: "FallbackStatus",
+                table: "check_result_raw");
+        }
+    }
+}
diff --git a/ThingConnect.Pulse.Server/Migrations/PulseDbContextModelSnapshot.cs b/ThingConnect.Pulse.Server/Migrations/PulseDbContextModelSnapshot.cs
index 51bbbb1..aeccc31 100644
--- a/ThingConnect.Pulse.Server/Migrations/PulseDbContextModelSnapshot.cs
+++ b/ThingConnect.Pulse.Server/Migrations/PulseDbContextModelSnapshot.cs
@@ -238,12 +238,27 @@ protected override void BuildModel(ModelBuilder modelBuilder)
                         .ValueGeneratedOnAdd()
                         .HasColumnType("INTEGER");
 
+                    b.Property("Classification")
+                        .HasColumnType("INTEGER");
+
                     b.Property("EndpointId")
                         .HasColumnType("TEXT");
 
                     b.Property("Error")
                         .HasColumnType("TEXT");
 
+                    b.Property("FallbackAttempted")
+                        .HasColumnType("INTEGER");
+
+                    b.Property("FallbackError")
+                        .HasColumnType("TEXT");
+
+                    b.Property("FallbackRttMs")
+                        .HasColumnType("double precision");
+
+                    b.Property("FallbackStatus")
+                        .HasColumnType("TEXT");
+
                     b.Property("RttMs")
                         .HasColumnType("double precision");
 
@@ -538,6 +553,9 @@ protected override void BuildModel(ModelBuilder modelBuilder)
                         .ValueGeneratedOnAdd()
                         .HasColumnType("INTEGER");
 
+                    b.Property("Classification")
+                        .HasColumnType("INTEGER");
+
                     b.Property("DurationSeconds")
                         .HasColumnType("INTEGER");
 
diff --git a/ThingConnect.Pulse.Server/Models/CheckResult.cs b/ThingConnect.Pulse.Server/Models/CheckResult.cs
index 6a9639e..1c81674 100644
--- a/ThingConnect.Pulse.Server/Models/CheckResult.cs
+++ b/ThingConnect.Pulse.Server/Models/CheckResult.cs
@@ -2,40 +2,35 @@
 
 namespace ThingConnect.Pulse.Server.Models;
 
+public enum StatusType
+{
+    Up,
+    Down,
+    Service,
+    Flapping
+}
+
 /// 
 /// Result of a single probe check (ICMP, TCP, or HTTP).
 /// 
 public sealed class CheckResult
 {
-    /// 
-    /// Gets or sets the endpoint that was checked.
-    /// 
     public Guid EndpointId { get; set; }
-
-    /// 
-    /// Gets or sets timestamp when the check was performed.
-    /// 
     public DateTimeOffset Timestamp { get; set; }
-
-    /// 
-    /// Gets or sets result status: UP or DOWN.
-    /// 
     public UpDown Status { get; set; }
-
-    /// 
-    /// Gets or sets round-trip time in milliseconds. Null if not applicable or failed.
-    /// 
     public double? RttMs { get; set; }
-
-    /// 
-    /// Gets or sets error message if the check failed. Null if successful.
-    /// 
     public string? Error { get; set; }
 
+    // ๐น Fallback probe info
+    public bool FallbackAttempted { get; set; } = false;
+    public UpDown? FallbackStatus { get; set; }
+    public double? FallbackRttMs { get; set; }
+    public string? FallbackError { get; set; }
+    public Classification? Classification { get; set; }
+
     /// 
     /// Creates a successful check result.
     /// 
-    /// 
     public static CheckResult Success(Guid endpointId, DateTimeOffset timestamp, double? rttMs = null)
     {
         return new CheckResult
@@ -44,14 +39,18 @@ public static CheckResult Success(Guid endpointId, DateTimeOffset timestamp, dou
             Timestamp = timestamp,
             Status = UpDown.up,
             RttMs = rttMs,
-            Error = null
+            Error = null,
+            FallbackAttempted = false,
+            FallbackStatus = null,
+            FallbackRttMs = null,
+            FallbackError = null,
+            Classification = Data.Classification.None
         };
     }
 
     /// 
     /// Creates a failed check result.
     /// 
-    /// 
     public static CheckResult Failure(Guid endpointId, DateTimeOffset timestamp, string error)
     {
         return new CheckResult
@@ -60,7 +59,161 @@ public static CheckResult Failure(Guid endpointId, DateTimeOffset timestamp, str
             Timestamp = timestamp,
             Status = UpDown.down,
             RttMs = null,
-            Error = error
+            Error = error,
+            FallbackAttempted = false,
+            FallbackStatus = null,
+            FallbackRttMs = null,
+            FallbackError = null,
+            Classification = Data.Classification.Unknown // ๐น FIXED: Set to unknown
+        };
+    }
+
+    /// 
+    /// Updates the current CheckResult with fallback info.
+    /// 
+    public void ApplyFallback(CheckResult fallback)
+    {
+        if (fallback == null) return;
+
+        FallbackAttempted = true;
+        FallbackStatus = fallback.Status;
+        FallbackRttMs = fallback.RttMs;
+        FallbackError = fallback.Error;
+        Classification = DetermineClassification();
+    }
+
+    /// 
+    /// ๐น Helper to calculate effective status
+    /// 
+    public UpDown GetEffectiveStatus()
+    {
+        // Primary DOWN + Fallback UP = Effective UP (service issue)
+        if (Status == UpDown.down && FallbackAttempted && FallbackStatus == UpDown.up)
+        {
+            return UpDown.up;
+        }
+        return Status;
+    }
+
+    /// 
+    /// ๐น Helper to get effective RTT
+    /// 
+    public double? GetEffectiveRtt()
+    {
+        // Priority 1: Primary RTT if successful
+        if (Status == UpDown.up && RttMs.HasValue)
+        {
+            return RttMs;
+        }
+        // Priority 2: Fallback RTT if primary failed but fallback succeeded
+        if (Status == UpDown.down && FallbackAttempted && FallbackStatus == UpDown.up && FallbackRttMs.HasValue)
+        {
+            return FallbackRttMs;
+        }
+        return null;
+    }
+
+    /// 
+    /// ๐น Auto-classification based on probe results
+    /// 
+    public Classification DetermineClassification()
+    {
+        if (Status == UpDown.up)
+        {
+            return Data.Classification.None; // Healthy
+        }
+
+        if (FallbackAttempted)
+        {
+            if (FallbackStatus == UpDown.up)
+            {
+                return Data.Classification.Service; // Service down, host up
+            }
+
+            return Data.Classification.Network; // Both down
+        }
+
+        return Data.Classification.Unknown; // No fallback info
+    }
+
+    // ๐น StatusType logic (Up / Down / Service / Flapping)
+    public StatusType DetermineStatusType(List recentChecks, TimeSpan interval)
+    {
+        if (recentChecks == null || recentChecks.Count == 0)
+        {
+            return StatusType.Down;
+        }
+
+        // Flapping overrides all
+        if (IsFlapping(recentChecks))
+        {
+            return StatusType.Flapping;
+        }
+
+        // Effective UP
+        if (GetEffectiveStatus() == UpDown.up)
+        {
+            if (Status == UpDown.down && FallbackAttempted && FallbackStatus == UpDown.up)
+            {
+                return StatusType.Service;
+            }
+
+            return StatusType.Up;
+        }
+
+        return StatusType.Down;
+    }
+
+    // ๐น Flapping detection (>= 4 samples, >3 changes in 5 min window)
+    public static bool IsFlapping(List recent)
+    {
+        if (recent == null || recent.Count < 4)
+        {
+            return false;
+        }
+
+        var effectiveStatuses = recent
+            .OrderBy(c => c.Timestamp)
+            .Select(c => c.GetEffectiveStatus())
+            .ToList();
+
+        int stateChanges = 0;
+        for (int i = 1; i < effectiveStatuses.Count; i++)
+        {
+            if (effectiveStatuses[i] != effectiveStatuses[i - 1])
+            {
+                stateChanges++;
+            }
+        }
+
+        return stateChanges > 3;
+    }
+
+    /// 
+    /// ๐น Map endpoint entity to EndpointDto (call anywhere you need)
+    /// 
+    public static EndpointDto MapToEndpointDto(Data.Endpoint endpoint)
+    {
+        return new EndpointDto
+        {
+            Id = endpoint.Id,
+            Name = endpoint.Name,
+            Group = new GroupDto
+            {
+                Id = endpoint.Group.Id,
+                Name = endpoint.Group.Name,
+                ParentId = endpoint.Group.ParentId,
+                Color = endpoint.Group.Color
+            },
+            Type = endpoint.Type.ToString().ToLower(),
+            Host = endpoint.Host,
+            Port = endpoint.Port,
+            HttpPath = endpoint.HttpPath,
+            HttpMatch = endpoint.HttpMatch,
+            IntervalSeconds = endpoint.IntervalSeconds,
+            TimeoutMs = endpoint.TimeoutMs,
+            Retries = endpoint.Retries,
+            Enabled = endpoint.Enabled
         };
     }
 }
diff --git a/ThingConnect.Pulse.Server/Models/EndpointDetailDto.cs b/ThingConnect.Pulse.Server/Models/EndpointDetailDto.cs
index 6309039..300cd56 100644
--- a/ThingConnect.Pulse.Server/Models/EndpointDetailDto.cs
+++ b/ThingConnect.Pulse.Server/Models/EndpointDetailDto.cs
@@ -1,3 +1,4 @@
+using ThingConnect.Pulse.Server.Data;
 using ThingConnect.Pulse.Server.Models;
 
 public sealed class EndpointDetailDto
@@ -6,3 +7,31 @@ public sealed class EndpointDetailDto
     public List Recent { get; set; } = [];
     public List Outages { get; set; } = [];
 }
+
+public sealed class RawCheckDto
+{
+    public DateTimeOffset Ts { get; set; }
+    public Classification Classification { get; set; }
+    public PrimaryResultDto Primary { get; set; } = default!;
+    public FallbackResultDto Fallback { get; set; } = default!;
+    public CurrentStateDto CurrentState { get; set; } = default!;
+}
+
+public sealed class PrimaryResultDto
+{
+    public string Type { get; set; } = default!;
+    public string Target { get; set; } = default!;
+    public string Status { get; set; } = default!;
+    public double? RttMs { get; set; }
+    public string? Error { get; set; }
+}
+
+public sealed class FallbackResultDto
+{
+    public bool Attempted { get; set; }
+    public string? Type { get; set; }
+    public string? Target { get; set; }
+    public string? Status { get; set; }
+    public double? RttMs { get; set; }
+    public string? Error { get; set; }
+}
diff --git a/ThingConnect.Pulse.Server/Models/HistoryDtos.cs b/ThingConnect.Pulse.Server/Models/HistoryDtos.cs
index 55109c7..364dc07 100644
--- a/ThingConnect.Pulse.Server/Models/HistoryDtos.cs
+++ b/ThingConnect.Pulse.Server/Models/HistoryDtos.cs
@@ -1,12 +1,7 @@
+using ThingConnect.Pulse.Server.Data;
+
 namespace ThingConnect.Pulse.Server.Models;
 
-public sealed class RawCheckDto
-{
-    public DateTimeOffset Ts { get; set; }
-    public string Status { get; set; } = default!;
-    public double? RttMs { get; set; }
-    public string? Error { get; set; }
-}
 
 public sealed class RollupBucketDto
 {
@@ -30,6 +25,7 @@ public sealed class OutageDto
     public DateTimeOffset? EndedTs { get; set; }
     public int? DurationS { get; set; }
     public string? LastError { get; set; }
+    public Classification? Classification { get; set; }
 }
 
 public sealed class HistoryResponseDto
diff --git a/ThingConnect.Pulse.Server/Models/StatusDtos.cs b/ThingConnect.Pulse.Server/Models/StatusDtos.cs
index 11df3f8..72fce72 100644
--- a/ThingConnect.Pulse.Server/Models/StatusDtos.cs
+++ b/ThingConnect.Pulse.Server/Models/StatusDtos.cs
@@ -3,8 +3,7 @@ namespace ThingConnect.Pulse.Server.Models;
 public sealed class LiveStatusItemDto
 {
     public EndpointDto Endpoint { get; set; } = default!;
-    public string Status { get; set; } = default!;
-    public double? RttMs { get; set; }
+    public CurrentStateDto CurrentState { get; set; } = default!;
     public DateTimeOffset LastChangeTs { get; set; }
     public List Sparkline { get; set; } = new();
 }
@@ -51,3 +50,12 @@ public sealed class PagedLiveDto
     public PageMetaDto Meta { get; set; } = default!;
     public List Items { get; set; } = new();
 }
+
+public sealed class CurrentStateDto
+{
+    public string Type { get; set; } = default!;
+    public string Target { get; set; } = default!;
+    public string Status { get; set; } = default!; // "up" or "down"
+    public double? RttMs { get; set; } // Priority-based RTT
+    public int Classification { get; set; } // Classification enum value
+}
diff --git a/ThingConnect.Pulse.Server/Services/EndpointService.cs b/ThingConnect.Pulse.Server/Services/EndpointService.cs
index 48bdd71..9f7783d 100644
--- a/ThingConnect.Pulse.Server/Services/EndpointService.cs
+++ b/ThingConnect.Pulse.Server/Services/EndpointService.cs
@@ -38,16 +38,61 @@ public EndpointService(PulseDbContext context)
             .Take(RecentFetchLimit)
             .ToListAsync();
 
-        var recent = rawChecks
-            .Select(c => new RawCheckDto
+        // Map to CheckResult objects for easier processing
+        var checks = rawChecks
+            .Select(c => new CheckResult
             {
-                Ts = ConvertToDateTimeOffset(c.Ts),
-                Status = c.Status.ToString().ToLower(),
+                EndpointId = c.EndpointId,
+                Timestamp = ConvertToDateTimeOffset(c.Ts),
+                Status = c.Status,
                 RttMs = c.RttMs,
-                Error = c.Error
+                Error = c.Error,
+                FallbackAttempted = (bool)c.FallbackAttempted,
+                FallbackStatus = c.FallbackStatus,
+                FallbackRttMs = c.FallbackRttMs,
+                FallbackError = c.FallbackError,
+                Classification = c.Classification
+            }).ToList();
+
+        var recentForEndpoint = checks
+            .Where(x => x.Timestamp >= windowStart)
+            .OrderBy(x => x.Timestamp)
+            .ToList();
+
+        // --- Map RawCheckDto including EffectiveState ---
+        var recent = checks
+            .Where(c => c.Timestamp >= windowStart)
+            .OrderByDescending(c => c.Timestamp)
+            .Select(c => new RawCheckDto
+            {
+                Ts = c.Timestamp,
+                Classification = c.DetermineClassification(),
+                Primary = new PrimaryResultDto
+                {
+                    Type = endpoint.Type.ToString().ToLower(),
+                    Target = endpoint.Host,
+                    Status = c.Status.ToString().ToLower(),
+                    RttMs = c.RttMs,
+                    Error = c.Error
+                },
+                Fallback = new FallbackResultDto
+                {
+                    Attempted = c.FallbackAttempted,
+                    Type = "icmp",
+                    Target = endpoint.Host,
+                    Status = c.FallbackStatus?.ToString().ToLower(),
+                    RttMs = c.FallbackRttMs,
+                    Error = c.FallbackError
+                },
+                CurrentState = new CurrentStateDto
+                {
+                    Type = c.FallbackAttempted && c.FallbackStatus != null ? "icmp" : endpoint.Type.ToString().ToLower(),
+                    Target = endpoint.Host,
+                    Status = c.DetermineStatusType(recentForEndpoint, TimeSpan.FromSeconds(endpoint.IntervalSeconds * 2)).ToString().ToLower(),
+                    RttMs = c.GetEffectiveRtt(),
+                    Classification = (int)c.DetermineClassification(),
+                }
             })
-            .Where(r => r.Ts >= windowStart)
-            .OrderByDescending(r => r.Ts)
             .ToList();
 
         // --- Fetch outages within window ---
@@ -73,7 +118,7 @@ public EndpointService(PulseDbContext context)
             .ToList();
 
         // --- Map endpoint DTO ---
-        var endpointDto = MapToEndpointDto(endpoint);
+        var endpointDto = CheckResult.MapToEndpointDto(endpoint);
 
         return new EndpointDetailDto
         {
@@ -83,31 +128,6 @@ public EndpointService(PulseDbContext context)
         };
     }
 
-    private EndpointDto MapToEndpointDto(Data.Endpoint endpoint)
-    {
-        return new EndpointDto
-        {
-            Id = endpoint.Id,
-            Name = endpoint.Name,
-            Group = new GroupDto
-            {
-                Id = endpoint.Group.Id,
-                Name = endpoint.Group.Name,
-                ParentId = endpoint.Group.ParentId,
-                Color = endpoint.Group.Color
-            },
-            Type = endpoint.Type.ToString().ToLower(),
-            Host = endpoint.Host,
-            Port = endpoint.Port,
-            HttpPath = endpoint.HttpPath,
-            HttpMatch = endpoint.HttpMatch,
-            IntervalSeconds = endpoint.IntervalSeconds,
-            TimeoutMs = endpoint.TimeoutMs,
-            Retries = endpoint.Retries,
-            Enabled = endpoint.Enabled
-        };
-    }
-
     // --- Helper to convert timestamp to DateTimeOffset ---
     private static DateTimeOffset ConvertToDateTimeOffset(T value)
     {
diff --git a/ThingConnect.Pulse.Server/Services/HistoryService.cs b/ThingConnect.Pulse.Server/Services/HistoryService.cs
index 6c1c224..365cc4a 100644
--- a/ThingConnect.Pulse.Server/Services/HistoryService.cs
+++ b/ThingConnect.Pulse.Server/Services/HistoryService.cs
@@ -51,14 +51,14 @@ public HistoryService(PulseDbContext context, ILogger logger)
 
         var response = new HistoryResponseDto
         {
-            Endpoint = MapToEndpointDto(endpoint)
+            Endpoint = CheckResult.MapToEndpointDto(endpoint)
         };
 
         // Fetch data based on bucket type
         switch (bucket.ToLower())
         {
             case "raw":
-                response.Raw = await GetRawDataAsync(endpointId, from, to);
+                response.Raw = await GetRawDataAsync(endpoint, from, to);
                 break;
 
             case "15m":
@@ -73,32 +73,69 @@ public HistoryService(PulseDbContext context, ILogger logger)
                 throw new ArgumentException($"Invalid bucket type: {bucket}. Valid values: raw, 15m, daily");
         }
 
-        // Always include outages for the time range
+        // Always include outages
         response.Outages = await GetOutagesAsync(endpointId, from, to);
 
         return response;
     }
 
-    private async Task> GetRawDataAsync(Guid endpointId, DateTimeOffset from, DateTimeOffset to)
+    private async Task> GetRawDataAsync(Data.Endpoint endpoint, DateTimeOffset from, DateTimeOffset to)
     {
         long fromUnix = UnixTimestamp.ToUnixSeconds(from);
         long toUnix = UnixTimestamp.ToUnixSeconds(to);
 
-        // SQLite limitation: fetch all data and filter in memory
         var rawData = await _context.CheckResultsRaw
-            .Where(c => c.EndpointId == endpointId)
-            .Select(c => new { c.Ts, c.Status, c.RttMs, c.Error })
+            .Where(c => c.EndpointId == endpoint.Id && c.Ts >= fromUnix && c.Ts <= toUnix)
+            .OrderBy(c => c.Ts)
             .ToListAsync();
 
-        return rawData
-            .Where(c => c.Ts >= fromUnix && c.Ts <= toUnix)
-            .OrderBy(c => c.Ts)
-            .Select(c => new RawCheckDto
+        // Convert DB rows -> CheckResult -> RawCheckDto
+        var checks = rawData
+            .Select(c => new CheckResult
             {
-                Ts = UnixTimestamp.FromUnixSeconds(c.Ts),
-                Status = c.Status == UpDown.up ? "up" : "down",
+                EndpointId = c.EndpointId,
+                Timestamp = UnixTimestamp.FromUnixSeconds(c.Ts),
+                Status = c.Status,
                 RttMs = c.RttMs,
-                Error = c.Error
+                Error = c.Error,
+                FallbackAttempted = (bool)c.FallbackAttempted,
+                FallbackStatus = c.FallbackStatus,
+                FallbackRttMs = c.FallbackRttMs,
+                FallbackError = c.FallbackError,
+                Classification = c.Classification
+            })
+            .ToList();
+
+        return checks
+            .Select(c => new RawCheckDto
+            {
+                Ts = c.Timestamp,
+                Classification = c.DetermineClassification(),
+                Primary = new PrimaryResultDto
+                {
+                    Type = endpoint.Type.ToString().ToLower(),
+                    Target = endpoint.Host,
+                    Status = c.Status.ToString().ToLower(),
+                    RttMs = c.RttMs,
+                    Error = c.Error
+                },
+                Fallback = new FallbackResultDto
+                {
+                    Attempted = c.FallbackAttempted,
+                    Type = "icmp",
+                    Target = endpoint.Host,
+                    Status = c.FallbackStatus?.ToString().ToLower(),
+                    RttMs = c.FallbackRttMs,
+                    Error = c.FallbackError
+                },
+                CurrentState = new CurrentStateDto
+                {
+                    Type = c.FallbackAttempted && c.FallbackStatus != null ? "icmp" : endpoint.Type.ToString().ToLower(),
+                    Target = endpoint.Host,
+                    Status = c.GetEffectiveStatus().ToString().ToLower(),
+                    RttMs = c.GetEffectiveRtt(),
+                    Classification = (int)c.DetermineClassification(),
+                }
             })
             .ToList();
     }
@@ -175,29 +212,4 @@ private async Task> GetOutagesAsync(Guid endpointId, DateTimeOff
             })
             .ToList();
     }
-
-    private EndpointDto MapToEndpointDto(Data.Endpoint endpoint)
-    {
-        return new EndpointDto
-        {
-            Id = endpoint.Id,
-            Name = endpoint.Name,
-            Group = new GroupDto
-            {
-                Id = endpoint.Group.Id,
-                Name = endpoint.Group.Name,
-                ParentId = endpoint.Group.ParentId,
-                Color = endpoint.Group.Color
-            },
-            Type = endpoint.Type.ToString().ToLower(),
-            Host = endpoint.Host,
-            Port = endpoint.Port,
-            HttpPath = endpoint.HttpPath,
-            HttpMatch = endpoint.HttpMatch,
-            IntervalSeconds = endpoint.IntervalSeconds,
-            TimeoutMs = endpoint.TimeoutMs,
-            Retries = endpoint.Retries,
-            Enabled = endpoint.Enabled
-        };
-    }
 }
diff --git a/ThingConnect.Pulse.Server/Services/Monitoring/OutageDetectionService.cs b/ThingConnect.Pulse.Server/Services/Monitoring/OutageDetectionService.cs
index d2d6a67..4471a30 100644
--- a/ThingConnect.Pulse.Server/Services/Monitoring/OutageDetectionService.cs
+++ b/ThingConnect.Pulse.Server/Services/Monitoring/OutageDetectionService.cs
@@ -22,6 +22,10 @@ public OutageDetectionService(IServiceProvider serviceProvider, ILogger
+    /// Processes a single check result: updates streaks, transitions UP/DOWN with flap damping,
+    /// and persists the raw result including fallback details and classification.
+    /// 
public async Task ProcessCheckResultAsync(CheckResult result, CancellationToken cancellationToken = default)
     {
         MonitorState state = _states.GetOrAdd(result.EndpointId, _ => new MonitorState());
@@ -33,30 +37,36 @@ public async Task ProcessCheckResultAsync(CheckResult result, Cancellation
 
         try
         {
-            // Update streak counters based on result
-            if (result.Status == UpDown.up)
+            var effectiveStatus = result.GetEffectiveStatus();
+            if (effectiveStatus == UpDown.up)
             {
                 state.RecordSuccess();
                 _logger.LogDebug(
-                    "RecordSuccess called for endpoint {EndpointId}. SuccessStreak={SuccessStreak}, FailStreak={FailStreak}",
-                    result.EndpointId, state.SuccessStreak, state.FailStreak
+                    "RecordSuccess called for endpoint {EndpointId}. EffectiveStatus={EffectiveStatus}, SuccessStreak={SuccessStreak}, FailStreak={FailStreak}",
+                    result.EndpointId, effectiveStatus, state.SuccessStreak, state.FailStreak
                 );
             }
             else
             {
                 state.RecordFailure();
                 _logger.LogDebug(
-                    "RecordFailure called for endpoint {EndpointId}. SuccessStreak={SuccessStreak}, FailStreak={FailStreak}, Error={Error}",
-                    result.EndpointId, state.SuccessStreak, state.FailStreak, result.Error
+                    "RecordFailure called for endpoint {EndpointId}. EffectiveStatus={EffectiveStatus}, SuccessStreak={SuccessStreak}, FailStreak={FailStreak}, Error={Error}",
+                    result.EndpointId, effectiveStatus, state.SuccessStreak, state.FailStreak, result.Error
                 );
             }
 
             // Check for DOWN transition
             if (state.ShouldTransitionToDown())
             {
-                await TransitionToDownAsync(result.EndpointId, state, UnixTimestamp.ToUnixSeconds(result.Timestamp), result.Error, cancellationToken);
+                await TransitionToDownAsync(
+                    result.EndpointId,
+                    state,
+                    UnixTimestamp.ToUnixSeconds(result.Timestamp),
+                    result.Error,
+                    result.Classification,
+                    cancellationToken);
                 stateChanged = true;
-                _logger.LogWarning("Endpoint {EndpointId} transitioned to DOWN after {FailStreak} consecutive failures",
+                _logger.LogWarning("Endpoint {EndpointId} transitioned to DOWN after {FailStreak} consecutive effective failures",
                     result.EndpointId, state.FailStreak);
             }
 
@@ -65,7 +75,7 @@ public async Task ProcessCheckResultAsync(CheckResult result, Cancellation
             {
                 await TransitionToUpAsync(result.EndpointId, state, UnixTimestamp.ToUnixSeconds(result.Timestamp), cancellationToken);
                 stateChanged = true;
-                _logger.LogInformation("Endpoint {EndpointId} transitioned to UP after {SuccessStreak} consecutive successes",
+                _logger.LogInformation("Endpoint {EndpointId} transitioned to UP after {SuccessStreak} consecutive effective successes",
                     result.EndpointId, state.SuccessStreak);
             }
 
@@ -207,10 +217,6 @@ public async Task InitializeStatesFromDatabaseAsync(CancellationToken cancellati
             if (inconsistenciesFixed > 0)
             {
                 await context.SaveChangesAsync(cancellationToken);
-            }
-
-            if (inconsistenciesFixed > 0)
-            {
                 _logger.LogInformation("Started monitoring session {SessionId}, initialized {Count} states, fixed {InconsistencyCount} state inconsistencies",
                     newSession.Id, initializedCount, inconsistenciesFixed);
             }
@@ -248,7 +254,6 @@ public async Task InitializeStatesFromDatabaseAsync(CancellationToken cancellati
                     "{GapDuration}s gap > {Threshold}s threshold ({IntervalSeconds}s interval), " +
                     "missed ~{MissedChecks} checks",
                     endpoint.Id, endpoint.Name, gapDuration, gapThreshold, endpoint.IntervalSeconds, missedChecks);
-
                 affectedEndpoints.Add(endpoint);
             }
         }
@@ -265,8 +270,8 @@ private async Task HandleMonitoringGapAsync(PulseDbContext context, long lastMon
         // Handle open outages only for affected endpoints
         List outagesForAffectedEndpoints = await context.Outages
             .Where(o => o.EndedTs == null &&
-                       o.StartedTs < lastMonitoringTime &&
-                       affectedEndpointIds.Contains(o.EndpointId))
+                        o.StartedTs < lastMonitoringTime &&
+                        affectedEndpointIds.Contains(o.EndpointId))
             .ToListAsync(cancellationToken);
 
         foreach (Outage? outage in outagesForAffectedEndpoints)
@@ -349,8 +354,7 @@ public async Task HandleGracefulShutdownAsync(string? shutdownReason = null, Can
         }
     }
 
-    private async Task TransitionToDownAsync(Guid endpointId, MonitorState state, long timestamp,
-        string? error, CancellationToken cancellationToken)
+    private async Task TransitionToDownAsync(Guid endpointId, MonitorState state, long timestamp, string? error, Classification? classification, CancellationToken cancellationToken)
     {
         using IServiceScope scope = _serviceProvider.CreateScope();
         PulseDbContext context = scope.ServiceProvider.GetRequiredService();
@@ -365,7 +369,8 @@ private async Task TransitionToDownAsync(Guid endpointId, MonitorState state, lo
             {
                 EndpointId = endpointId,
                 StartedTs = timestamp,
-                LastError = error
+                LastError = error,
+                Classification = classification
             };
 
             context.Outages.Add(outage);
@@ -515,18 +520,29 @@ private async Task UpdateEndpointStatusAsync(PulseDbContext context, Guid endpoi
         return (endpointStatus, openOutageId, false);
     }
 
+    /// 
+    /// Persists the raw check result including fallback probe fields and classification.
+    /// Also updates endpoint's LastRttMs for successful probes.
+    /// 
     private async Task SaveCheckResultAsync(CheckResult result, CancellationToken cancellationToken)
     {
         using IServiceScope scope = _serviceProvider.CreateScope();
         PulseDbContext context = scope.ServiceProvider.GetRequiredService();
 
-        CheckResultRaw rawResult = new CheckResultRaw
+        var rawResult = new CheckResultRaw
         {
             EndpointId = result.EndpointId,
             Ts = UnixTimestamp.ToUnixSeconds(result.Timestamp),
             Status = result.Status,
             RttMs = result.RttMs,
-            Error = result.Error
+            Error = result.Error,
+
+            // New fallback fields
+            FallbackAttempted = result.FallbackAttempted,
+            FallbackStatus = result.FallbackStatus,
+            FallbackRttMs = result.FallbackRttMs,
+            FallbackError = result.FallbackError,
+            Classification = result.Classification
         };
 
         context.CheckResultsRaw.Add(rawResult);
diff --git a/ThingConnect.Pulse.Server/Services/Monitoring/ProbeService.cs b/ThingConnect.Pulse.Server/Services/Monitoring/ProbeService.cs
index 33f3aab..dec82d2 100644
--- a/ThingConnect.Pulse.Server/Services/Monitoring/ProbeService.cs
+++ b/ThingConnect.Pulse.Server/Services/Monitoring/ProbeService.cs
@@ -1,6 +1,7 @@
 using System.Diagnostics;
 using System.Net.NetworkInformation;
 using System.Net.Sockets;
+using System.Threading;
 using ThingConnect.Pulse.Server.Data;
 using ThingConnect.Pulse.Server.Models;
 
@@ -24,25 +25,45 @@ public ProbeService(ILogger logger, IHttpClientFactory httpClientF
     public async Task ProbeAsync(Data.Endpoint endpoint, CancellationToken cancellationToken = default)
     {
         DateTimeOffset timestamp = DateTimeOffset.UtcNow;
+        CheckResult probeResult;
 
         try
         {
-            return endpoint.Type switch
+            probeResult = endpoint.Type switch
             {
                 ProbeType.icmp => await PingAsync(endpoint.Id, endpoint.Host, endpoint.TimeoutMs, cancellationToken),
-                ProbeType.tcp => await TcpConnectAsync(endpoint.Id, endpoint.Host,
-                    endpoint.Port ?? 80, endpoint.TimeoutMs, cancellationToken),
-                ProbeType.http => await HttpCheckAsync(endpoint.Id, endpoint.Host,
-                    endpoint.Port ?? (endpoint.Host.StartsWith("https://") ? 443 : 80),
+                ProbeType.tcp => await TcpConnectAsync(endpoint.Id, endpoint.Host, endpoint.Port ?? 80, endpoint.TimeoutMs, cancellationToken),
+                ProbeType.http => await HttpCheckAsync(endpoint.Id, endpoint.Host, endpoint.Port ?? (endpoint.Host.StartsWith("https://") ? 443 : 80),
                     endpoint.HttpPath, endpoint.HttpMatch, endpoint.TimeoutMs, cancellationToken),
                 _ => CheckResult.Failure(endpoint.Id, timestamp, $"Unknown probe type: {endpoint.Type}")
             };
         }
         catch (Exception ex)
         {
-            _logger.LogError(ex, "Probe failed for endpoint {EndpointId} ({Host})", endpoint.Id, endpoint.Host);
-            return CheckResult.Failure(endpoint.Id, timestamp, ex.Message);
+            _logger.LogError(ex, "Primary probe failed for endpoint {EndpointId} ({Host})", endpoint.Id, endpoint.Host);
+            probeResult = CheckResult.Failure(endpoint.Id, timestamp, ex.Message);
         }
+
+        // TCP/HTTP fallback to ICMP if primary failed
+        if (probeResult.Status == UpDown.down && endpoint.Type != ProbeType.icmp)
+        {
+            try
+            {
+                int fallbackTimeout = Math.Max(endpoint.TimeoutMs / 2, 1000);
+                CheckResult fallbackResult = await PingAsync(endpoint.Id, endpoint.Host, fallbackTimeout, cancellationToken);
+
+                // ApplyFallback automatically sets classification
+                probeResult.ApplyFallback(fallbackResult);
+            }
+            catch (Exception ex)
+            {
+                _logger.LogWarning(ex, "Fallback ICMP probe failed for endpoint {EndpointId} ({Host})", endpoint.Id, endpoint.Host);
+                CheckResult fallbackResult = CheckResult.Failure(endpoint.Id, DateTimeOffset.UtcNow, $"Fallback ping failed: {ex.Message}");
+                probeResult.ApplyFallback(fallbackResult);
+            }
+        }
+
+        return probeResult;
     }
 
     public async Task PingAsync(Guid endpointId, string host, int timeoutMs, CancellationToken cancellationToken = default)
diff --git a/ThingConnect.Pulse.Server/Services/Monitoring/StatusClassifier.cs b/ThingConnect.Pulse.Server/Services/Monitoring/StatusClassifier.cs
new file mode 100644
index 0000000..b50bbc3
--- /dev/null
+++ b/ThingConnect.Pulse.Server/Services/Monitoring/StatusClassifier.cs
@@ -0,0 +1,113 @@
+using System.Net;
+using ThingConnect.Pulse.Server.Data;
+using ThingConnect.Pulse.Server.Models;
+
+namespace ThingConnect.Pulse.Server.Services.Monitoring;
+
+/// 
+// Complex logic with history, performance, DNS, etc.
+// Save for Phase 2
+/// 
+public static class StatusClassifier
+{
+    public static Classification ClassifyStatus(
+        CheckResult primaryResult,
+        CheckResult fallbackResult,
+        Data.Endpoint endpoint,
+        IEnumerable recentHistory)
+    {
+        // 1. ICMP probes โ always Network on failure
+        if (endpoint.Type == ProbeType.icmp && primaryResult.Status == UpDown.down)
+        {
+            return Classification.Network;
+        }
+
+        // 2. Successful probes โ check performance
+        if (primaryResult.Status == UpDown.up)
+        {
+            if (IsPerformanceDegraded(primaryResult, endpoint))
+            {
+                return Classification.Performance;
+            }
+
+            return Classification.None; // explicitly healthy
+        }
+
+        // 3. Failed TCP/HTTP probes โ use fallback
+        if (fallbackResult != null)
+        {
+            var baseClassification = fallbackResult.Status == UpDown.up
+                ? Classification.Service
+                : Classification.Network;
+
+            // 4. Advanced patterns
+            if (IsIntermittent(recentHistory))
+            {
+                return Classification.Intermittent;
+            }
+
+            if (IsPartialService(primaryResult, fallbackResult, endpoint))
+            {
+                return Classification.PartialService;
+            }
+
+            if (IsDnsIssue(endpoint, fallbackResult))
+            {
+                return Classification.DnsResolution;
+            }
+
+            return baseClassification;
+        }
+
+        // 5. Fallback missing or failed โ default
+        return Classification.Unknown;
+    }
+
+    private static bool IsPerformanceDegraded(CheckResult result, Data.Endpoint endpoint)
+    {
+        if (!result.RttMs.HasValue) return false;
+
+        double threshold = endpoint.Type == ProbeType.icmp ? 2.0 : 3.0;
+        double baselineRtt = endpoint.ExpectedRttMs ?? 100; // default baseline
+
+        return result.RttMs > baselineRtt * threshold;
+    }
+
+    private static bool IsIntermittent(IEnumerable recentHistory)
+    {
+        var last15Min = recentHistory
+            .Where(r => r.Timestamp > DateTimeOffset.UtcNow.AddMinutes(-15))
+            .OrderBy(r => r.Timestamp)
+            .ToList();
+
+        if (last15Min.Count < 4) return false;
+
+        int transitions = 0;
+        for (int i = 1; i < last15Min.Count; i++)
+        {
+            if (last15Min[i].Status != last15Min[i - 1].Status)
+                transitions++;
+        }
+
+        return transitions >= 4;
+    }
+
+    private static bool IsPartialService(CheckResult primary, CheckResult fallback, Data.Endpoint endpoint)
+    {
+        return endpoint.Type == ProbeType.http &&
+               primary.Error?.Contains("50") == true && // HTTP 5xx
+               fallback.Status == UpDown.up;
+    }
+
+    private static bool IsDnsIssue(Data.Endpoint endpoint, CheckResult fallbackResult)
+    {
+        return IsHostname(endpoint.Host) &&
+               fallbackResult.Status == UpDown.down &&
+               (fallbackResult.Error?.Contains("resolve", StringComparison.OrdinalIgnoreCase) ?? false);
+    }
+
+    private static bool IsHostname(string host)
+    {
+        return !IPAddress.TryParse(host, out _);
+    }
+}
diff --git a/ThingConnect.Pulse.Server/Services/Rollup/RollupService.cs b/ThingConnect.Pulse.Server/Services/Rollup/RollupService.cs
index c49e9b1..ed2287c 100644
--- a/ThingConnect.Pulse.Server/Services/Rollup/RollupService.cs
+++ b/ThingConnect.Pulse.Server/Services/Rollup/RollupService.cs
@@ -26,48 +26,33 @@ public async Task ProcessRollup15mAsync(CancellationToken cancellationToken = de
 
         try
         {
-            // Get last watermark
             DateTimeOffset? lastWatermark = await _settingsService.GetLastRollup15mTimestampAsync();
-            long fromTs = lastWatermark.HasValue ? UnixTimestamp.ToUnixSeconds(lastWatermark.Value) : UnixTimestamp.Subtract(UnixTimestamp.Now(), TimeSpan.FromDays(7)); // Default: 7 days back
+            long fromTs = lastWatermark.HasValue 
+                ? UnixTimestamp.ToUnixSeconds(lastWatermark.Value) 
+                : UnixTimestamp.Subtract(UnixTimestamp.Now(), TimeSpan.FromDays(7));
             long toTs = UnixTimestamp.Now();
 
-            _logger.LogDebug("Processing 15m rollups from {FromTs} to {ToTs}", UnixTimestamp.FromUnixSeconds(fromTs), UnixTimestamp.FromUnixSeconds(toTs));
-
-            // Get all raw checks in the time window
-            // SQLite has issues with DateTimeOffset comparisons in LINQ, so fetch all and filter in memory
             List allChecks = await _context.CheckResultsRaw.ToListAsync(cancellationToken);
             var rawChecks = allChecks
                 .Where(c => c.Ts > fromTs && c.Ts <= toTs)
                 .OrderBy(c => c.EndpointId)
                 .ThenBy(c => c.Ts)
+                .Select(c => new WrappedCheck(c))
                 .ToList();
 
-            if (!rawChecks.Any())
-            {
-                _logger.LogDebug("No raw checks found for rollup processing");
-                return;
-            }
-
-            _logger.LogInformation("Processing {Count} raw checks", rawChecks.Count);
+            if (!rawChecks.Any()) return;
 
-            // Group by endpoint and calculate rollups
-            IEnumerable> endpointGroups = rawChecks.GroupBy(c => c.EndpointId);
+            var endpointGroups = rawChecks.GroupBy(c => c.EndpointId);
             List rollupsToUpsert = new();
 
-            foreach (IGrouping endpointGroup in endpointGroups)
+            foreach (var endpointGroup in endpointGroups)
             {
                 var checks = endpointGroup.OrderBy(c => c.Ts).ToList();
-                List endpointRollups = CalculateRollups15m(endpointGroup.Key, checks);
-                rollupsToUpsert.AddRange(endpointRollups);
+                rollupsToUpsert.AddRange(CalculateRollups15m(endpointGroup.Key, checks));
             }
 
-            // Upsert rollups in batches
             await UpsertRollups15mAsync(rollupsToUpsert, cancellationToken);
-
-            // Update watermark
             await _settingsService.SetLastRollup15mTimestampAsync(UnixTimestamp.FromUnixSeconds(toTs));
-
-            _logger.LogInformation("Completed 15m rollup processing. Generated {Count} rollup records", rollupsToUpsert.Count);
         }
         catch (Exception ex)
         {
@@ -82,57 +67,36 @@ public async Task ProcessRollupDailyAsync(CancellationToken cancellationToken =
 
         try
         {
-            // Get last watermark
             DateOnly? lastWatermark = await _settingsService.GetLastRollupDailyDateAsync();
             DateOnly fromDate = lastWatermark?.AddDays(1) ?? DateOnly.FromDateTime(DateTime.UtcNow.AddDays(-7));
-            var toDate = DateOnly.FromDateTime(DateTime.UtcNow.Date);
+            DateOnly toDate = DateOnly.FromDateTime(DateTime.UtcNow.Date);
 
-            if (fromDate >= toDate)
-            {
-                _logger.LogDebug("No new days to process for daily rollup");
-                return;
-            }
-
-            _logger.LogDebug("Processing daily rollups from {FromDate} to {ToDate}", fromDate, toDate);
+            if (fromDate >= toDate) return;
 
-            // Get all raw checks in the date range
             long fromTs = UnixTimestamp.ToUnixDate(fromDate);
             long toTs = UnixTimestamp.ToUnixDate(toDate);
 
-            // SQLite has issues with DateTimeOffset comparisons in LINQ, so fetch all and filter in memory
             List allChecks = await _context.CheckResultsRaw.ToListAsync(cancellationToken);
             var rawChecks = allChecks
                 .Where(c => c.Ts >= fromTs && c.Ts < toTs)
                 .OrderBy(c => c.EndpointId)
                 .ThenBy(c => c.Ts)
+                .Select(c => new WrappedCheck(c))
                 .ToList();
 
-            if (!rawChecks.Any())
-            {
-                _logger.LogDebug("No raw checks found for daily rollup processing");
-                return;
-            }
+            if (!rawChecks.Any()) return;
 
-            _logger.LogInformation("Processing {Count} raw checks for daily rollup", rawChecks.Count);
-
-            // Group by endpoint and calculate rollups
-            IEnumerable> endpointGroups = rawChecks.GroupBy(c => c.EndpointId);
+            var endpointGroups = rawChecks.GroupBy(c => c.EndpointId);
             List rollupsToUpsert = new();
 
-            foreach (IGrouping endpointGroup in endpointGroups)
+            foreach (var endpointGroup in endpointGroups)
             {
                 var checks = endpointGroup.OrderBy(c => c.Ts).ToList();
-                List endpointRollups = CalculateRollupsDaily(endpointGroup.Key, checks, fromDate, toDate);
-                rollupsToUpsert.AddRange(endpointRollups);
+                rollupsToUpsert.AddRange(CalculateRollupsDaily(endpointGroup.Key, checks, fromDate, toDate));
             }
 
-            // Upsert rollups in batches
             await UpsertRollupsDailyAsync(rollupsToUpsert, cancellationToken);
-
-            // Update watermark
             await _settingsService.SetLastRollupDailyDateAsync(toDate.AddDays(-1));
-
-            _logger.LogInformation("Completed daily rollup processing. Generated {Count} rollup records", rollupsToUpsert.Count);
         }
         catch (Exception ex)
         {
@@ -141,44 +105,35 @@ public async Task ProcessRollupDailyAsync(CancellationToken cancellationToken =
         }
     }
 
-    private List CalculateRollups15m(Guid endpointId, List checks)
+    // --- Private rollup calculation helpers ---
+    private List CalculateRollups15m(Guid endpointId, List checks)
     {
         var rollups = new List();
 
-        // Group by 15-minute bucket
         var bucketGroups = checks
-            .Select(c => new
-            {
-                Check = c,
-                Bucket = GetBucketTimestamp15m(c.Ts)
-            })
-            .GroupBy(x => x.Bucket);
+            .GroupBy(c => GetBucketTimestamp15m(c.Ts));
 
         foreach (var bucketGroup in bucketGroups)
         {
-            var bucketChecks = bucketGroup.Select(x => x.Check).OrderBy(c => c.Ts).ToList();
-
-            if (!bucketChecks.Any())
-            {
-                continue;
-            }
+            var bucketChecks = bucketGroup.OrderBy(c => c.Ts).ToList();
+            if (!bucketChecks.Any()) continue;
 
-            // Calculate metrics
             int totalChecks = bucketChecks.Count;
-            int upChecks = bucketChecks.Count(c => c.Status == UpDown.up);
+            int upChecks = bucketChecks.Count(c => c.GetEffectiveStatus() == UpDown.up);
             double upPct = totalChecks > 0 ? (double)upChecks / totalChecks * 100.0 : 0.0;
 
             var rttValues = bucketChecks
-                .Where(c => c.RttMs.HasValue && c.RttMs > 0)
-                .Select(c => c.RttMs!.Value)
+                .Select(c => c.GetEffectiveRtt())
+                .Where(rtt => rtt.HasValue && rtt > 0)
+                .Select(rtt => rtt!.Value)
                 .ToList();
             double? avgRttMs = rttValues.Any() ? rttValues.Average() : null;
 
-            // Count down events (upโdown transitions)
             int downEvents = 0;
             for (int i = 1; i < bucketChecks.Count; i++)
             {
-                if (bucketChecks[i - 1].Status == UpDown.up && bucketChecks[i].Status == UpDown.down)
+                if (bucketChecks[i - 1].GetEffectiveStatus() == UpDown.up &&
+                    bucketChecks[i].GetEffectiveStatus() == UpDown.down)
                 {
                     downEvents++;
                 }
@@ -187,7 +142,7 @@ public async Task ProcessRollupDailyAsync(CancellationToken cancellationToken =
             rollups.Add(new Data.Rollup15m
             {
                 EndpointId = endpointId,
-                BucketTs = bucketGroup.Key,
+                BucketTs = GetBucketTimestamp15m(bucketChecks.First().Ts),
                 UpPct = upPct,
                 AvgRttMs = avgRttMs,
                 DownEvents = downEvents
@@ -197,45 +152,35 @@ public async Task ProcessRollupDailyAsync(CancellationToken cancellationToken =
         return rollups;
     }
 
-    private List CalculateRollupsDaily(Guid endpointId, List checks, DateOnly fromDate, DateOnly toDate)
+    private List CalculateRollupsDaily(Guid endpointId, List checks, DateOnly fromDate, DateOnly toDate)
     {
         var rollups = new List();
 
-        // Group by date
         var dateGroups = checks
-            .Select(c => new
-            {
-                Check = c,
-                Date = DateOnly.FromDateTime(UnixTimestamp.FromUnixSeconds(c.Ts).Date)
-            })
-            .Where(x => x.Date >= fromDate && x.Date < toDate)
-            .GroupBy(x => x.Date);
+            .GroupBy(c => DateOnly.FromDateTime(UnixTimestamp.FromUnixSeconds(c.Ts).Date))
+            .Where(g => g.Key >= fromDate && g.Key < toDate);
 
         foreach (var dateGroup in dateGroups)
         {
-            var dayChecks = dateGroup.Select(x => x.Check).OrderBy(c => c.Ts).ToList();
-
-            if (!dayChecks.Any())
-            {
-                continue;
-            }
+            var dayChecks = dateGroup.OrderBy(c => c.Ts).ToList();
+            if (!dayChecks.Any()) continue;
 
-            // Calculate metrics
             int totalChecks = dayChecks.Count;
-            int upChecks = dayChecks.Count(c => c.Status == UpDown.up);
+            int upChecks = dayChecks.Count(c => c.GetEffectiveStatus() == UpDown.up);
             double upPct = totalChecks > 0 ? (double)upChecks / totalChecks * 100.0 : 0.0;
 
             var rttValues = dayChecks
-                .Where(c => c.RttMs.HasValue && c.RttMs > 0)
-                .Select(c => c.RttMs!.Value)
+                .Select(c => c.GetEffectiveRtt())
+                .Where(rtt => rtt.HasValue && rtt > 0)
+                .Select(rtt => rtt!.Value)
                 .ToList();
             double? avgRttMs = rttValues.Any() ? rttValues.Average() : null;
 
-            // Count down events (upโdown transitions)
             int downEvents = 0;
             for (int i = 1; i < dayChecks.Count; i++)
             {
-                if (dayChecks[i - 1].Status == UpDown.up && dayChecks[i].Status == UpDown.down)
+                if (dayChecks[i - 1].GetEffectiveStatus() == UpDown.up &&
+                    dayChecks[i].GetEffectiveStatus() == UpDown.down)
                 {
                     downEvents++;
                 }
@@ -256,74 +201,89 @@ public async Task ProcessRollupDailyAsync(CancellationToken cancellationToken =
 
     private static long GetBucketTimestamp15m(long unixTs)
     {
-        // Round down to nearest 15-minute boundary
         DateTimeOffset ts = UnixTimestamp.FromUnixSeconds(unixTs);
-        int minute = ts.Minute;
-        int bucketMinute = (minute / 15) * 15;
-
-        var bucketTime = new DateTimeOffset(ts.Year, ts.Month, ts.Day, ts.Hour, bucketMinute, 0, ts.Offset);
-        return UnixTimestamp.ToUnixSeconds(bucketTime);
+        int bucketMinute = (ts.Minute / 15) * 15;
+        return UnixTimestamp.ToUnixSeconds(new DateTimeOffset(ts.Year, ts.Month, ts.Day, ts.Hour, bucketMinute, 0, ts.Offset));
     }
 
     private async Task UpsertRollups15mAsync(List rollups, CancellationToken cancellationToken)
     {
-        if (!rollups.Any())
-        {
-            return;
-        }
+        if (!rollups.Any()) return;
 
-        // SQLite doesn't support MERGE/UPSERT in EF Core, so we'll do it manually
-        foreach (Data.Rollup15m rollup in rollups)
+        foreach (var rollup in rollups)
         {
-            Rollup15m? existing = await _context.Rollups15m
+            var existing = await _context.Rollups15m
                 .FirstOrDefaultAsync(r => r.EndpointId == rollup.EndpointId && r.BucketTs == rollup.BucketTs, cancellationToken);
 
             if (existing != null)
             {
-                // Update existing
                 existing.UpPct = rollup.UpPct;
                 existing.AvgRttMs = rollup.AvgRttMs;
                 existing.DownEvents = rollup.DownEvents;
             }
             else
             {
-                // Add new
                 _context.Rollups15m.Add(rollup);
             }
         }
 
         await _context.SaveChangesAsync(cancellationToken);
-        _logger.LogDebug("Upserted {Count} 15m rollup records", rollups.Count);
     }
 
     private async Task UpsertRollupsDailyAsync(List rollups, CancellationToken cancellationToken)
     {
-        if (!rollups.Any())
-        {
-            return;
-        }
+        if (!rollups.Any()) return;
 
-        // SQLite doesn't support MERGE/UPSERT in EF Core, so we'll do it manually
-        foreach (Data.RollupDaily rollup in rollups)
+        foreach (var rollup in rollups)
         {
-            RollupDaily? existing = await _context.RollupsDaily
+            var existing = await _context.RollupsDaily
                 .FirstOrDefaultAsync(r => r.EndpointId == rollup.EndpointId && r.BucketDate == rollup.BucketDate, cancellationToken);
 
             if (existing != null)
             {
-                // Update existing
                 existing.UpPct = rollup.UpPct;
                 existing.AvgRttMs = rollup.AvgRttMs;
                 existing.DownEvents = rollup.DownEvents;
             }
             else
             {
-                // Add new
                 _context.RollupsDaily.Add(rollup);
             }
         }
 
         await _context.SaveChangesAsync(cancellationToken);
-        _logger.LogDebug("Upserted {Count} daily rollup records", rollups.Count);
+    }
+
+    // --- Private wrapper class for effective status & RTT ---
+    private class WrappedCheck
+    {
+        private readonly CheckResultRaw _check;
+
+        public WrappedCheck(CheckResultRaw check)
+        {
+            _check = check;
+            Ts = check.Ts;
+            EndpointId = check.EndpointId;
+        }
+
+        public long Ts { get; }
+        public Guid EndpointId { get; }
+
+        public UpDown GetEffectiveStatus()
+        {
+            if (_check.Status == UpDown.down && _check.FallbackAttempted == true && _check.FallbackStatus == UpDown.up)
+            {
+                return UpDown.up;
+            }
+            return _check.Status;
+        }
+
+        public double? GetEffectiveRtt()
+        {
+            if (_check.Status == UpDown.up && _check.RttMs.HasValue) return _check.RttMs;
+            if (_check.Status == UpDown.down && _check.FallbackAttempted == true && _check.FallbackStatus == UpDown.up && _check.FallbackRttMs.HasValue)
+                return _check.FallbackRttMs;
+            return null;
+        }
     }
 }
diff --git a/ThingConnect.Pulse.Server/Services/StatusService.cs b/ThingConnect.Pulse.Server/Services/StatusService.cs
index 3fade8c..b417a57 100644
--- a/ThingConnect.Pulse.Server/Services/StatusService.cs
+++ b/ThingConnect.Pulse.Server/Services/StatusService.cs
@@ -1,3 +1,4 @@
+using System.Net.NetworkInformation;
 using Microsoft.EntityFrameworkCore;
 using Microsoft.Extensions.Caching.Memory;
 using ThingConnect.Pulse.Server.Data;
@@ -55,37 +56,44 @@ public async Task> GetLiveStatusAsync(string? group, str
 
         // Apply pagination
         List endpoints = await query
-        .OrderBy(e => e.GroupId)
-        .ThenBy(e => e.Name)
-        .ToListAsync();
-
-        // Get live status for each endpoint
+            .OrderBy(e => e.GroupId)
+            .ThenBy(e => e.Name)
+            .ToListAsync();
         var items = new List();
         var endpointIds = endpoints.Select(e => e.Id).ToList();
 
-        // Get latest checks for all endpoints - optimized query using window functions in SQLite
-        var latestChecks = await _context.CheckResultsRaw
-            .Where(c => endpointIds.Contains(c.EndpointId))
+        // Fetch recent checks for all endpoints (last 5 minutes)
+        long cutoffTime = UnixTimestamp.Subtract(UnixTimestamp.Now(), TimeSpan.FromMinutes(5));
+        var recentChecks = await _context.CheckResultsRaw
+            .Where(c => endpointIds.Contains(c.EndpointId) && c.Ts >= cutoffTime)
             .AsNoTracking()
-            .GroupBy(c => c.EndpointId)
-            .Select(g => new
+            .Select(c => new CheckResult
             {
-                EndpointId = g.Key,
-                LatestCheck = g.OrderByDescending(c => c.Ts).FirstOrDefault()
+                EndpointId = c.EndpointId,
+                Timestamp = UnixTimestamp.FromUnixSeconds(c.Ts),
+                Status = c.Status,
+                RttMs = c.RttMs,
+                FallbackAttempted = c.FallbackStatus.HasValue,
+                FallbackStatus = c.FallbackStatus,
+                FallbackRttMs = c.FallbackRttMs,
+                Classification = c.Classification,
             })
             .ToListAsync();
 
-        var latestCheckDict = latestChecks.ToDictionary(x => x.EndpointId, x => x.LatestCheck);
+        var checksGrouped = recentChecks.GroupBy(c => c.EndpointId).ToDictionary(g => g.Key, g => g.ToList());
 
-        // Get sparkline data (last 20 checks per endpoint for mini chart)
         Dictionary> sparklineData = await GetSparklineDataAsync(endpointIds);
 
         foreach (Data.Endpoint? endpoint in endpoints)
         {
-            StatusType status = DetermineStatus(endpoint, latestCheckDict);
+            var recent = checksGrouped.ContainsKey(endpoint.Id) ? checksGrouped[endpoint.Id] : new List();
+            StatusType status = recent.Any()
+                ? recent.Last().DetermineStatusType(recent, TimeSpan.FromSeconds(endpoint.IntervalSeconds * 2))
+                : StatusType.Down;
+
             List sparkline = sparklineData.ContainsKey(endpoint.Id)
-                ? sparklineData[endpoint.Id]
-                : new List();
+            ? sparklineData[endpoint.Id]
+            : new List();
 
             _logger.LogInformation(
                 "Endpoint {EndpointName}: Status = {Status}, LastRttMs = {RttMs}, LastChangeTs = {LastChangeTs}",
@@ -93,10 +101,18 @@ public async Task> GetLiveStatusAsync(string? group, str
 
             items.Add(new LiveStatusItemDto
             {
-                Endpoint = MapToEndpointDto(endpoint),
-                Status = status.ToString().ToLower(),
-                RttMs = endpoint.LastRttMs,
-                LastChangeTs = endpoint.LastChangeTs.HasValue ? UnixTimestamp.FromUnixSeconds(endpoint.LastChangeTs.Value) : DateTimeOffset.Now,
+                Endpoint = CheckResult.MapToEndpointDto(endpoint),
+                CurrentState = new CurrentStateDto
+                {
+                    Type = recent.Any() && recent.Last().FallbackAttempted ? "icmp" : endpoint.Type.ToString().ToLower(),
+                    Target = endpoint.Host,
+                    Status = status.ToString().ToLower(),
+                    RttMs = recent.Any() ? recent.Last().GetEffectiveRtt() : null,
+                    Classification = recent.Any() ? (int)recent.Last().DetermineClassification() : 0
+                },
+                LastChangeTs = endpoint.LastChangeTs.HasValue
+          ? UnixTimestamp.FromUnixSeconds(endpoint.LastChangeTs.Value)
+          : DateTimeOffset.Now,
                 Sparkline = sparkline
             });
         }
@@ -152,120 +168,40 @@ private async Task>> GetSparklineDataAsync
         var recentChecks = await _context.CheckResultsRaw
             .Where(c => endpointIds.Contains(c.EndpointId) && c.Ts >= cutoffTime)
             .AsNoTracking()
-            .Select(c => new { c.EndpointId, c.Ts, c.Status })
+            .Select(c => new CheckResult
+            {
+                EndpointId = c.EndpointId,
+                Timestamp = UnixTimestamp.FromUnixSeconds(c.Ts),
+                Status = c.Status,
+                FallbackAttempted = c.FallbackStatus.HasValue,
+                FallbackStatus = c.FallbackStatus,
+                Classification = c.Classification,
+            })
             .ToListAsync();
 
-        recentChecks = recentChecks
-            .OrderBy(c => c.EndpointId)
-            .ThenByDescending(c => c.Ts)
-            .ToList();
-
-        var groupedChecks = recentChecks.GroupBy(c => c.EndpointId);
-
-        foreach (var group in groupedChecks)
+        var groupedChecks = recentChecks
+            .GroupBy(c => c.EndpointId)
+            .ToDictionary(g => g.Key, g => g
+                .OrderByDescending(c => c.Timestamp)
+                .Take(20)
+                .OrderBy(c => c.Timestamp) // chronological order for sparkline
+                .ToList()
+            );
+
+        foreach (var kvp in groupedChecks)
         {
-            var points = group
-                .Take(20) // Maximum 20 points for sparkline
-                .OrderBy(c => c.Ts) // Order chronologically for display
+            var points = kvp.Value
                 .Select(c => new SparklinePoint
                 {
-                    Ts = UnixTimestamp.FromUnixSeconds(c.Ts),
-                    S = c.Status == UpDown.up ? "u" : "d"
+                    Ts = c.Timestamp,
+                    S = c.GetEffectiveStatus() == UpDown.up ? "u" : "d" // ๐น use effective status
                 })
                 .ToList();
 
-            sparklineData[group.Key] = points;
+            sparklineData[kvp.Key] = points;
         }
 
         return sparklineData;
     }
 
-    private StatusType DetermineStatus(Data.Endpoint endpoint, Dictionary latestChecks)
-    {
-        // Check if we have recent check data
-        if (!latestChecks.TryGetValue(endpoint.Id, out CheckResultRaw? latestCheck) || latestCheck == null)
-        {
-            return StatusType.Down; // No data means down
-        }
-
-        // Check if the latest check is recent enough (within 2x interval)
-        var expectedInterval = TimeSpan.FromSeconds(endpoint.IntervalSeconds * 2);
-        if (UnixTimestamp.Now() - latestCheck.Ts > (long)expectedInterval.TotalSeconds)
-        {
-            return StatusType.Down; // Stale data means down
-        }
-
-        // Check for flapping (multiple state changes in short period)
-        // This is simplified - in production you'd want more sophisticated flap detection
-        if (IsFlapping(endpoint.Id).Result)
-        {
-            return StatusType.Flapping;
-        }
-
-        return latestCheck.Status == UpDown.up ? StatusType.Up : StatusType.Down;
-    }
-
-    private async Task IsFlapping(Guid endpointId)
-    {
-        // Simple flap detection: check if there were > 3 state changes in last 5 minutes
-        long cutoffTime = UnixTimestamp.Subtract(UnixTimestamp.Now(), TimeSpan.FromMinutes(5));
-        var checks = await _context.CheckResultsRaw
-            .Where(c => c.EndpointId == endpointId && c.Ts >= cutoffTime)
-            .AsNoTracking()
-            .Select(c => new { c.Ts, c.Status })
-            .ToListAsync();
-
-        var recentChecks = checks
-            .OrderBy(c => c.Ts)
-            .Select(c => c.Status)
-            .ToList();
-
-        if (recentChecks.Count < 4)
-        {
-            return false;
-        }
-
-        int stateChanges = 0;
-        for (int i = 1; i < recentChecks.Count; i++)
-        {
-            if (recentChecks[i] != recentChecks[i - 1])
-            {
-                stateChanges++;
-            }
-        }
-
-        return stateChanges > 3;
-    }
-
-    private EndpointDto MapToEndpointDto(Data.Endpoint endpoint)
-    {
-        return new EndpointDto
-        {
-            Id = endpoint.Id,
-            Name = endpoint.Name,
-            Group = new GroupDto
-            {
-                Id = endpoint.Group.Id,
-                Name = endpoint.Group.Name,
-                ParentId = endpoint.Group.ParentId,
-                Color = endpoint.Group.Color
-            },
-            Type = endpoint.Type.ToString().ToLower(),
-            Host = endpoint.Host,
-            Port = endpoint.Port,
-            HttpPath = endpoint.HttpPath,
-            HttpMatch = endpoint.HttpMatch,
-            IntervalSeconds = endpoint.IntervalSeconds,
-            TimeoutMs = endpoint.TimeoutMs,
-            Retries = endpoint.Retries,
-            Enabled = endpoint.Enabled
-        };
-    }
-
-    private enum StatusType
-    {
-        Up,
-        Down,
-        Flapping
-    }
 }
diff --git a/docs/icmp-fallback-and-outage-classification.md b/docs/icmp-fallback-and-outage-classification.md
new file mode 100644
index 0000000..44d6b8c
--- /dev/null
+++ b/docs/icmp-fallback-and-outage-classification.md
@@ -0,0 +1,175 @@
+# ๐ก ICMP Fallback โ ThingConnect Pulse
+
+## ๐ Description
+
+ICMP fallback enhances outage classification accuracy by automatically performing ICMP ping tests when TCP or HTTP probes fail.  
+This enables **precise root cause analysis** by distinguishing between:
+
+- ๐  **Service Outage** โ Service down, host still reachable  
+- ๐ด **Network Outage** โ Host completely unreachable  
+- ๐ก **Mixed Outage** โ Flapping or unstable network  
+
+Without fallback, failed HTTP probes are ambiguous (could mean web service down *or* full network isolation).  
+With ICMP fallback, ThingConnect Pulse intelligently classifies failures for **better diagnostics and reduced false positives**.
+
+---
+
+## ๐ฏ Scope of Work
+
+### Core Implementation
+- Add automatic ICMP fallback logic on TCP/HTTP failures  
+- Extend `CheckResult` model with fallback probe results  
+- Implement outage classification system (Network, Service, Mixed, Unknown)  
+- Update outage detection to consider fallback outcomes  
+
+### Probe Enhancement
+- Modify `ProbeService` to run ICMP fallback after failed TCP/HTTP  
+- Configurable fallback timeout (default **500ms**)  
+- Respect concurrency limits & jitter scheduling  
+- Preserve original error messages + add fallback context  
+
+### Database Schema
+- Extend **`CheckResultRaw`** with fallback probe fields  
+- Add **`OutageClassification`** enum  
+- Update **`Outage`** table with classification results  
+- Maintain backwards compatibility  
+
+### UI Integration
+- Dashboard shows classification badges (๐ด, ๐ , ๐ก)  
+- Endpoint details display fallback probe results  
+- History view filter by outage type  
+- CSV exports include `OutageType` + `FallbackResult`  
+
+---
+
+## โ
 Acceptance Criteria
+
+### Functional
+- TCP/HTTP failures trigger ICMP fallback within **2s**  
+- ICMP fallback timeout = **500ms** (configurable)  
+- Correct outage classification applied:
+  - **Network Outage** โ Primary + ICMP fail  
+  - **Service Outage** โ Primary fail, ICMP succeed  
+  - **Mixed Outage** โ Inconsistent results over multiple checks  
+  - **Unknown** โ Errors/timeout in fallback  
+- Jitter & concurrency respected  
+- Original error messages preserved  
+
+### Performance
+- <1s overhead on failed probes  
+- No extra cost on successful probes  
+- +20B memory per endpoint for fallback tracking  
+- ~30% DB storage increase for failed probe records  
+
+### UX
+- Dashboard shows color-coded outage types  
+- Endpoint details: "Last seen via ICMP" timestamps  
+- History charts distinguish outage types  
+- CSV exports include new fields  
+
+---
+
+
+# ๐ฅ Outage Classification Decision Logic
+
+## ๐ 1. Primary Classification (Immediate Fallback)
+
+### For TCP/HTTP Probe Failures
+TCP/HTTP Probe Fails
+โโโ Execute ICMP Fallback
+โโโ ICMP Succeeds โ Service (2)
+โโโ ICMP Fails โ Network (1)
+โโโ ICMP Timeout/Error โ Unknown (0)
+
+### For ICMP Probe Failures
+ICMP Probe Fails
+โโโ No fallback needed โ Network (1)
+
+### For Successful Probes
+Probe Succeeds
+โโโ RTT > Performance Threshold โ Performance (4)
+โโโ RTT Normal โ No Classification
+
+---
+
+## โ๏ธ 2. Advanced Classification (Historical / Contextual Analysis)
+
+These rules are applied **after primary classification**, and may **override** the immediate result.
+
+### ๐ก Intermittent (3)
+- **Trigger**: โฅ4 UP/DOWN transitions in 15 minutes  
+- **Logic**: Analyze `CheckResultRaw` history  
+- **Override**: Reclassify `Network`/`Service` โ `Intermittent`  
+
+---
+
+### ๐  PartialService (5) *(HTTP only)*
+- **Trigger**: HTTP 5xx errors + TCP succeeds  
+- **Logic**: Parse HTTP error codes from primary failure  
+- **Classification**: `PartialService`  
+
+---
+
+### ๐ต DnsResolution (6)
+- **Trigger**: Hostname probe fails, IP probe succeeds  
+- **Logic**: Compare DNS resolution with fallback reachability  
+- **Classification**: `DnsResolution`  
+
+---
+
+### ๐ฃ Congestion (7)
+- **Trigger**: RTT increase >50% across multiple endpoints simultaneously  
+- **Logic**: Cross-endpoint RTT correlation  
+- **Classification**: `Congestion`  
+
+---
+
+### ๐ข Maintenance (8)
+- **Trigger**: Outage overlaps with maintenance window  
+- **Logic**: Check YAML `maintenance` schedule  
+- **Classification**: `Maintenance`  
+
+---
+
+## ๐ 3. Classification Precedence
+
+When multiple conditions apply, the following precedence is used:
+
+1. **Maintenance (8)** โ Highest priority  
+2. **DnsResolution (6)** / **PartialService (5)** โ Specific service-layer issues  
+3. **Intermittent (3)** โ Overrides unstable host classifications  
+4. **Congestion (7)** โ Correlated cross-host slowdown  
+5. **Primary Classification (0, 1, 2, 4)** โ Default result  
+
+---
+
+## ๐บ๏ธ 4. Mermaid Flowchart
+
+```mermaid
+flowchart TD
+    A[Probe Result] -->|TCP/HTTP Fail| B[Run ICMP Fallback]
+    A -->|ICMP Fail| N[Network (1)]
+    A -->|Success| S[Check RTT]
+
+    B -->|ICMP Success| Svc[Service (2)]
+    B -->|ICMP Fail| Net[Network (1)]
+    B -->|ICMP Timeout/Error| Unk[Unknown (0)]
+
+    S -->|RTT > Threshold| Perf[Performance (4)]
+    S -->|RTT Normal| NoClass[No Classification]
+
+    %% Advanced Overrides
+    subgraph Advanced
+        I[Intermittent (3)]
+        P[PartialService (5)]
+        D[DnsResolution (6)]
+        C[Congestion (7)]
+        M[Maintenance (8)]
+    end
+
+    Net --> I
+    Svc --> I
+    A --> P
+    A --> D
+    A --> C
+    A --> M
\ No newline at end of file
diff --git a/outage-probe-flow-analysis.md b/docs/outage-probe-flow-analysis.md
similarity index 100%
rename from outage-probe-flow-analysis.md
rename to docs/outage-probe-flow-analysis.md
diff --git a/thingconnect.pulse.client/obj/Debug/package.g.props b/thingconnect.pulse.client/obj/Debug/package.g.props
index 24d3f29..3bd503f 100644
--- a/thingconnect.pulse.client/obj/Debug/package.g.props
+++ b/thingconnect.pulse.client/obj/Debug/package.g.props
@@ -11,17 +11,13 @@
     vite preview
     prettier --write .
     cd .. && husky ./thingconnect.pulse.client/.husky
+    ^3.27.0
     ^3.24.2
     ^11.14.0
     ^5.2.1
     ^4.7.0
     ^10.11.0
     ^5.84.2
-    ^3.12.0
-    ^3.12.0
-    ^3.12.0
-    ^3.12.0
-    ^3.12.0
     ^1.11.0
     ^4.1.0
     ^12.23.14
@@ -37,6 +33,7 @@
     ^7.62.0
     ^5.5.0
     ^7.8.1
+    ^3.2.1
     ^4.1.9
     ^3.24.0
     ^9.33.0
diff --git a/thingconnect.pulse.client/package-lock.json b/thingconnect.pulse.client/package-lock.json
index 381f9b2..bb93024 100644
--- a/thingconnect.pulse.client/package-lock.json
+++ b/thingconnect.pulse.client/package-lock.json
@@ -8,17 +8,13 @@
       "name": "thingconnect.pulse.client",
       "version": "0.1.0",
       "dependencies": {
+        "@chakra-ui/charts": "^3.27.0",
         "@chakra-ui/react": "^3.24.2",
         "@emotion/react": "^11.14.0",
         "@hookform/resolvers": "^5.2.1",
         "@monaco-editor/react": "^4.7.0",
         "@sentry/react": "^10.11.0",
         "@tanstack/react-query": "^5.84.2",
-        "@visx/axis": "^3.12.0",
-        "@visx/responsive": "^3.12.0",
-        "@visx/scale": "^3.12.0",
-        "@visx/shape": "^3.12.0",
-        "@visx/tooltip": "^3.12.0",
         "axios": "^1.11.0",
         "date-fns": "^4.1.0",
         "framer-motion": "^12.23.14",
@@ -34,6 +30,7 @@
         "react-hook-form": "^7.62.0",
         "react-icons": "^5.5.0",
         "react-router-dom": "^7.8.1",
+        "recharts": "^3.2.1",
         "zod": "^4.1.9"
       },
       "devDependencies": {
@@ -263,6 +260,18 @@
         "node": ">=6.9.0"
       }
     },
+    "node_modules/@chakra-ui/charts": {
+      "version": "3.27.0",
+      "resolved": "https://registry.npmjs.org/@chakra-ui/charts/-/charts-3.27.0.tgz",
+      "integrity": "sha512-nCn4TbbQZIbnr89ynETD4rrW3Rh+it+w55q3QUc76GbqMTfcs4I148UsP/nb5YURQ9WAHwmMXhzlW9T62JqSvw==",
+      "license": "MIT",
+      "peerDependencies": {
+        "@chakra-ui/react": ">=3",
+        "react": ">=18",
+        "react-dom": ">=18",
+        "recharts": ">=2"
+      }
+    },
     "node_modules/@chakra-ui/cli": {
       "version": "3.25.0",
       "resolved": "https://registry.npmjs.org/@chakra-ui/cli/-/cli-3.25.0.tgz",
@@ -1467,6 +1476,32 @@
         "node": ">=14"
       }
     },
+    "node_modules/@reduxjs/toolkit": {
+      "version": "2.9.0",
+      "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.9.0.tgz",
+      "integrity": "sha512-fSfQlSRu9Z5yBkvsNhYF2rPS8cGXn/TZVrlwN1948QyZ8xMZ0JvP50S2acZNaf+o63u6aEeMjipFyksjIcWrog==",
+      "license": "MIT",
+      "dependencies": {
+        "@standard-schema/spec": "^1.0.0",
+        "@standard-schema/utils": "^0.3.0",
+        "immer": "^10.0.3",
+        "redux": "^5.0.1",
+        "redux-thunk": "^3.1.0",
+        "reselect": "^5.1.0"
+      },
+      "peerDependencies": {
+        "react": "^16.9.0 || ^17.0.0 || ^18 || ^19",
+        "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0"
+      },
+      "peerDependenciesMeta": {
+        "react": {
+          "optional": true
+        },
+        "react-redux": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/@rolldown/pluginutils": {
       "version": "1.0.0-beta.35",
       "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.35.tgz",
@@ -1830,6 +1865,12 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/@standard-schema/spec": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz",
+      "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==",
+      "license": "MIT"
+    },
     "node_modules/@standard-schema/utils": {
       "version": "0.3.0",
       "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz",
@@ -2135,81 +2176,66 @@
       "integrity": "sha512-nl09VhutdjINdWyXxHWN/w9zlNCfr60JUqJbd24YXUuCwgeL0TpFSdElCwb6cxfB6ybE19Gjj4g0jsgkXxKv1Q=="
     },
     "node_modules/@types/d3-array": {
-      "version": "3.0.3",
-      "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.0.3.tgz",
-      "integrity": "sha512-Reoy+pKnvsksN0lQUlcH6dOGjRZ/3WRwXR//m+/8lt1BXeI4xyaUZoqULNjyXXRuh0Mj4LNpkCvhUpQlY3X5xQ==",
+      "version": "3.2.2",
+      "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz",
+      "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==",
       "license": "MIT"
     },
     "node_modules/@types/d3-color": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.0.tgz",
-      "integrity": "sha512-HKuicPHJuvPgCD+np6Se9MQvS6OCbJmOjGvylzMJRlDwUXjKTTXs6Pwgk79O09Vj/ho3u1ofXnhFOaEWWPrlwA==",
-      "license": "MIT"
-    },
-    "node_modules/@types/d3-delaunay": {
-      "version": "6.0.1",
-      "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.1.tgz",
-      "integrity": "sha512-tLxQ2sfT0p6sxdG75c6f/ekqxjyYR0+LwPrsO1mbC9YDBzPJhs2HbJJRrn8Ez1DBoHRo2yx7YEATI+8V1nGMnQ==",
+      "version": "3.1.3",
+      "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
+      "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==",
       "license": "MIT"
     },
-    "node_modules/@types/d3-format": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.1.tgz",
-      "integrity": "sha512-5KY70ifCCzorkLuIkDe0Z9YTf9RR2CjBX1iaJG+rgM/cPP+sO+q9YdQ9WdhQcgPj1EQiJ2/0+yUkkziTG6Lubg==",
+    "node_modules/@types/d3-ease": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz",
+      "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==",
       "license": "MIT"
     },
-    "node_modules/@types/d3-geo": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz",
-      "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==",
-      "license": "MIT",
-      "dependencies": {
-        "@types/geojson": "*"
-      }
-    },
     "node_modules/@types/d3-interpolate": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
-      "integrity": "sha512-jx5leotSeac3jr0RePOH1KdR9rISG91QIE4Q2PYTu4OymLTZfA3SrnURSLzKH48HmXVUru50b8nje4E79oQSQw==",
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
+      "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
       "license": "MIT",
       "dependencies": {
         "@types/d3-color": "*"
       }
     },
     "node_modules/@types/d3-path": {
-      "version": "1.0.11",
-      "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-1.0.11.tgz",
-      "integrity": "sha512-4pQMp8ldf7UaB/gR8Fvvy69psNHkTpD/pVw3vmEi8iZAB9EPMBruB1JvHO4BIq9QkUUd2lV1F5YXpMNj7JPBpw==",
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz",
+      "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==",
       "license": "MIT"
     },
     "node_modules/@types/d3-scale": {
-      "version": "4.0.2",
-      "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.2.tgz",
-      "integrity": "sha512-Yk4htunhPAwN0XGlIwArRomOjdoBFXC3+kCxK2Ubg7I9shQlVSJy/pG/Ht5ASN+gdMIalpk8TJ5xV74jFsetLA==",
+      "version": "4.0.9",
+      "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz",
+      "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==",
       "license": "MIT",
       "dependencies": {
         "@types/d3-time": "*"
       }
     },
     "node_modules/@types/d3-shape": {
-      "version": "1.3.12",
-      "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-1.3.12.tgz",
-      "integrity": "sha512-8oMzcd4+poSLGgV0R1Q1rOlx/xdmozS4Xab7np0eamFFUYq71AU9pOCJEFnkXW2aI/oXdVYJzw6pssbSut7Z9Q==",
+      "version": "3.1.7",
+      "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz",
+      "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==",
       "license": "MIT",
       "dependencies": {
-        "@types/d3-path": "^1"
+        "@types/d3-path": "*"
       }
     },
     "node_modules/@types/d3-time": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.0.tgz",
-      "integrity": "sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg==",
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz",
+      "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==",
       "license": "MIT"
     },
-    "node_modules/@types/d3-time-format": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-2.1.0.tgz",
-      "integrity": "sha512-/myT3I7EwlukNOX2xVdMzb8FRgNzRMpsZddwst9Ld/VFe6LyJyRp0s32l/V9XoUzk+Gqu56F/oGk6507+8BxrA==",
+    "node_modules/@types/d3-timer": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz",
+      "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==",
       "license": "MIT"
     },
     "node_modules/@types/debug": {
@@ -2227,12 +2253,6 @@
       "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
       "dev": true
     },
-    "node_modules/@types/geojson": {
-      "version": "7946.0.16",
-      "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz",
-      "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==",
-      "license": "MIT"
-    },
     "node_modules/@types/json-schema": {
       "version": "7.0.15",
       "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
@@ -2242,7 +2262,8 @@
     "node_modules/@types/lodash": {
       "version": "4.17.20",
       "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz",
-      "integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA=="
+      "integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==",
+      "dev": true
     },
     "node_modules/@types/luxon": {
       "version": "3.7.1",
@@ -2282,6 +2303,7 @@
       "version": "19.1.13",
       "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.13.tgz",
       "integrity": "sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ==",
+      "devOptional": true,
       "dependencies": {
         "csstype": "^3.0.2"
       }
@@ -2290,10 +2312,17 @@
       "version": "19.1.9",
       "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.9.tgz",
       "integrity": "sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ==",
+      "dev": true,
       "peerDependencies": {
         "@types/react": "^19.0.0"
       }
     },
+    "node_modules/@types/use-sync-external-store": {
+      "version": "0.0.6",
+      "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
+      "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==",
+      "license": "MIT"
+    },
     "node_modules/@typescript-eslint/eslint-plugin": {
       "version": "8.44.0",
       "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.44.0.tgz",
@@ -2551,178 +2580,6 @@
         "node": ">=20.18 <=24.x"
       }
     },
-    "node_modules/@visx/axis": {
-      "version": "3.12.0",
-      "resolved": "https://registry.npmjs.org/@visx/axis/-/axis-3.12.0.tgz",
-      "integrity": "sha512-8MoWpfuaJkhm2Yg+HwyytK8nk+zDugCqTT/tRmQX7gW4LYrHYLXFUXOzbDyyBakCVaUbUaAhVFxpMASJiQKf7A==",
-      "license": "MIT",
-      "dependencies": {
-        "@types/react": "*",
-        "@visx/group": "3.12.0",
-        "@visx/point": "3.12.0",
-        "@visx/scale": "3.12.0",
-        "@visx/shape": "3.12.0",
-        "@visx/text": "3.12.0",
-        "classnames": "^2.3.1",
-        "prop-types": "^15.6.0"
-      },
-      "peerDependencies": {
-        "react": "^16.3.0-0 || ^17.0.0-0 || ^18.0.0-0"
-      }
-    },
-    "node_modules/@visx/bounds": {
-      "version": "3.12.0",
-      "resolved": "https://registry.npmjs.org/@visx/bounds/-/bounds-3.12.0.tgz",
-      "integrity": "sha512-peAlNCUbYaaZ0IO6c1lDdEAnZv2iGPDiLIM8a6gu7CaMhtXZJkqrTh+AjidNcIqITktrICpGxJE/Qo9D099dvQ==",
-      "license": "MIT",
-      "dependencies": {
-        "@types/react": "*",
-        "@types/react-dom": "*",
-        "prop-types": "^15.5.10"
-      },
-      "peerDependencies": {
-        "react": "^16.0.0-0 || ^17.0.0-0 || ^18.0.0-0",
-        "react-dom": "^16.0.0-0 || ^17.0.0-0 || ^18.0.0-0"
-      }
-    },
-    "node_modules/@visx/curve": {
-      "version": "3.12.0",
-      "resolved": "https://registry.npmjs.org/@visx/curve/-/curve-3.12.0.tgz",
-      "integrity": "sha512-Ng1mefXIzoIoAivw7dJ+ZZYYUbfuwXgZCgQynShr6ZIVw7P4q4HeQfJP3W24ON+1uCSrzoycHSXRelhR9SBPcw==",
-      "license": "MIT",
-      "dependencies": {
-        "@types/d3-shape": "^1.3.1",
-        "d3-shape": "^1.0.6"
-      }
-    },
-    "node_modules/@visx/group": {
-      "version": "3.12.0",
-      "resolved": "https://registry.npmjs.org/@visx/group/-/group-3.12.0.tgz",
-      "integrity": "sha512-Dye8iS1alVXPv7nj/7M37gJe6sSKqJLH7x6sEWAsRQ9clI0kFvjbKcKgF+U3aAVQr0NCohheFV+DtR8trfK/Ag==",
-      "license": "MIT",
-      "dependencies": {
-        "@types/react": "*",
-        "classnames": "^2.3.1",
-        "prop-types": "^15.6.2"
-      },
-      "peerDependencies": {
-        "react": "^16.0.0-0 || ^17.0.0-0 || ^18.0.0-0"
-      }
-    },
-    "node_modules/@visx/point": {
-      "version": "3.12.0",
-      "resolved": "https://registry.npmjs.org/@visx/point/-/point-3.12.0.tgz",
-      "integrity": "sha512-I6UrHoJAEVbx3RORQNupgTiX5EzjuZpiwLPxn8L2mI5nfERotPKi1Yus12Cq2WtXqEBR/WgqTnoImlqOXBykcA==",
-      "license": "MIT"
-    },
-    "node_modules/@visx/responsive": {
-      "version": "3.12.0",
-      "resolved": "https://registry.npmjs.org/@visx/responsive/-/responsive-3.12.0.tgz",
-      "integrity": "sha512-GV1BTYwAGlk/K5c9vH8lS2syPnTuIqEacI7L6LRPbsuaLscXMNi+i9fZyzo0BWvAdtRV8v6Urzglo++lvAXT1Q==",
-      "license": "MIT",
-      "dependencies": {
-        "@types/lodash": "^4.14.172",
-        "@types/react": "*",
-        "lodash": "^4.17.21",
-        "prop-types": "^15.6.1"
-      },
-      "peerDependencies": {
-        "react": "^16.0.0-0 || ^17.0.0-0 || ^18.0.0-0"
-      }
-    },
-    "node_modules/@visx/scale": {
-      "version": "3.12.0",
-      "resolved": "https://registry.npmjs.org/@visx/scale/-/scale-3.12.0.tgz",
-      "integrity": "sha512-+ubijrZ2AwWCsNey0HGLJ0YKNeC/XImEFsr9rM+Uef1CM3PNM43NDdNTrdBejSlzRq0lcfQPWYMYQFSlkLcPOg==",
-      "license": "MIT",
-      "dependencies": {
-        "@visx/vendor": "3.12.0"
-      }
-    },
-    "node_modules/@visx/shape": {
-      "version": "3.12.0",
-      "resolved": "https://registry.npmjs.org/@visx/shape/-/shape-3.12.0.tgz",
-      "integrity": "sha512-/1l0lrpX9tPic6SJEalryBKWjP/ilDRnQA+BGJTI1tj7i23mJ/J0t4nJHyA1GrL4QA/bM/qTJ35eyz5dEhJc4g==",
-      "license": "MIT",
-      "dependencies": {
-        "@types/d3-path": "^1.0.8",
-        "@types/d3-shape": "^1.3.1",
-        "@types/lodash": "^4.14.172",
-        "@types/react": "*",
-        "@visx/curve": "3.12.0",
-        "@visx/group": "3.12.0",
-        "@visx/scale": "3.12.0",
-        "classnames": "^2.3.1",
-        "d3-path": "^1.0.5",
-        "d3-shape": "^1.2.0",
-        "lodash": "^4.17.21",
-        "prop-types": "^15.5.10"
-      },
-      "peerDependencies": {
-        "react": "^16.3.0-0 || ^17.0.0-0 || ^18.0.0-0"
-      }
-    },
-    "node_modules/@visx/text": {
-      "version": "3.12.0",
-      "resolved": "https://registry.npmjs.org/@visx/text/-/text-3.12.0.tgz",
-      "integrity": "sha512-0rbDYQlbuKPhBqXkkGYKFec1gQo05YxV45DORzr6hCyaizdJk1G+n9VkuKSHKBy1vVQhBA0W3u/WXd7tiODQPA==",
-      "license": "MIT",
-      "dependencies": {
-        "@types/lodash": "^4.14.172",
-        "@types/react": "*",
-        "classnames": "^2.3.1",
-        "lodash": "^4.17.21",
-        "prop-types": "^15.7.2",
-        "reduce-css-calc": "^1.3.0"
-      },
-      "peerDependencies": {
-        "react": "^16.3.0-0 || ^17.0.0-0 || ^18.0.0-0"
-      }
-    },
-    "node_modules/@visx/tooltip": {
-      "version": "3.12.0",
-      "resolved": "https://registry.npmjs.org/@visx/tooltip/-/tooltip-3.12.0.tgz",
-      "integrity": "sha512-pWhsYhgl0Shbeqf80qy4QCB6zpq6tQtMQQxKlh3UiKxzkkfl+Metaf9p0/S0HexNi4vewOPOo89xWx93hBeh3A==",
-      "license": "MIT",
-      "dependencies": {
-        "@types/react": "*",
-        "@visx/bounds": "3.12.0",
-        "classnames": "^2.3.1",
-        "prop-types": "^15.5.10",
-        "react-use-measure": "^2.0.4"
-      },
-      "peerDependencies": {
-        "react": "^16.8.0-0 || ^17.0.0-0 || ^18.0.0-0",
-        "react-dom": "^16.8.0-0 || ^17.0.0-0 || ^18.0.0-0"
-      }
-    },
-    "node_modules/@visx/vendor": {
-      "version": "3.12.0",
-      "resolved": "https://registry.npmjs.org/@visx/vendor/-/vendor-3.12.0.tgz",
-      "integrity": "sha512-SVO+G0xtnL9dsNpGDcjCgoiCnlB3iLSM9KLz1sLbSrV7RaVXwY3/BTm2X9OWN1jH2a9M+eHt6DJ6sE6CXm4cUg==",
-      "license": "MIT and ISC",
-      "dependencies": {
-        "@types/d3-array": "3.0.3",
-        "@types/d3-color": "3.1.0",
-        "@types/d3-delaunay": "6.0.1",
-        "@types/d3-format": "3.0.1",
-        "@types/d3-geo": "3.1.0",
-        "@types/d3-interpolate": "3.0.1",
-        "@types/d3-scale": "4.0.2",
-        "@types/d3-time": "3.0.0",
-        "@types/d3-time-format": "2.1.0",
-        "d3-array": "3.2.1",
-        "d3-color": "3.1.0",
-        "d3-delaunay": "6.0.2",
-        "d3-format": "3.1.0",
-        "d3-geo": "3.1.0",
-        "d3-interpolate": "3.0.1",
-        "d3-scale": "4.0.2",
-        "d3-time": "3.1.0",
-        "d3-time-format": "4.1.0",
-        "internmap": "2.0.3"
-      }
-    },
     "node_modules/@vitejs/plugin-react-swc": {
       "version": "4.1.0",
       "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-4.1.0.tgz",
@@ -3674,7 +3531,8 @@
     "node_modules/balanced-match": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
-      "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
+      "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+      "dev": true
     },
     "node_modules/base64-arraybuffer": {
       "version": "1.0.2",
@@ -3793,12 +3651,6 @@
         "fsevents": "~2.3.2"
       }
     },
-    "node_modules/classnames": {
-      "version": "2.5.1",
-      "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
-      "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==",
-      "license": "MIT"
-    },
     "node_modules/cli-cursor": {
       "version": "5.0.0",
       "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz",
@@ -3842,6 +3694,15 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/clsx": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+      "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      }
+    },
     "node_modules/color-convert": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -3963,9 +3824,9 @@
       "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
     },
     "node_modules/d3-array": {
-      "version": "3.2.1",
-      "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.1.tgz",
-      "integrity": "sha512-gUY/qeHq/yNqqoCKNq4vtpFLdoCdvyNpWoC/KNjhGbhDuQpAM9sIQQKkXSNpXa9h5KySs/gzm7R88WkUutgwWQ==",
+      "version": "3.2.4",
+      "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
+      "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
       "license": "ISC",
       "dependencies": {
         "internmap": "1 - 2"
@@ -3983,14 +3844,11 @@
         "node": ">=12"
       }
     },
-    "node_modules/d3-delaunay": {
-      "version": "6.0.2",
-      "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.2.tgz",
-      "integrity": "sha512-IMLNldruDQScrcfT+MWnazhHbDJhcRJyOEBAJfwQnHle1RPh6WDuLvxNArUju2VSMSUuKlY5BGHRJ2cYyoFLQQ==",
-      "license": "ISC",
-      "dependencies": {
-        "delaunator": "5"
-      },
+    "node_modules/d3-ease": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
+      "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
+      "license": "BSD-3-Clause",
       "engines": {
         "node": ">=12"
       }
@@ -4004,18 +3862,6 @@
         "node": ">=12"
       }
     },
-    "node_modules/d3-geo": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.0.tgz",
-      "integrity": "sha512-JEo5HxXDdDYXCaWdwLRt79y7giK8SbhZJbFWXqbRTolCHFI5jRqteLzCsq51NKbUoX0PjBVSohxrx+NoOUujYA==",
-      "license": "ISC",
-      "dependencies": {
-        "d3-array": "2.5.0 - 3"
-      },
-      "engines": {
-        "node": ">=12"
-      }
-    },
     "node_modules/d3-interpolate": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
@@ -4029,10 +3875,13 @@
       }
     },
     "node_modules/d3-path": {
-      "version": "1.0.9",
-      "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz",
-      "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==",
-      "license": "BSD-3-Clause"
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz",
+      "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
+      "license": "ISC",
+      "engines": {
+        "node": ">=12"
+      }
     },
     "node_modules/d3-scale": {
       "version": "4.0.2",
@@ -4051,12 +3900,15 @@
       }
     },
     "node_modules/d3-shape": {
-      "version": "1.3.7",
-      "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz",
-      "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==",
-      "license": "BSD-3-Clause",
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
+      "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
+      "license": "ISC",
       "dependencies": {
-        "d3-path": "1"
+        "d3-path": "^3.1.0"
+      },
+      "engines": {
+        "node": ">=12"
       }
     },
     "node_modules/d3-time": {
@@ -4083,6 +3935,15 @@
         "node": ">=12"
       }
     },
+    "node_modules/d3-timer": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
+      "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
+      "license": "ISC",
+      "engines": {
+        "node": ">=12"
+      }
+    },
     "node_modules/data-uri-to-buffer": {
       "version": "4.0.1",
       "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
@@ -4117,21 +3978,18 @@
         }
       }
     },
+    "node_modules/decimal.js-light": {
+      "version": "2.5.1",
+      "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
+      "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==",
+      "license": "MIT"
+    },
     "node_modules/deep-is": {
       "version": "0.1.4",
       "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
       "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
       "dev": true
     },
-    "node_modules/delaunator": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz",
-      "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==",
-      "license": "ISC",
-      "dependencies": {
-        "robust-predicates": "^3.0.2"
-      }
-    },
     "node_modules/delayed-stream": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@@ -4226,6 +4084,16 @@
         "node": ">= 0.4"
       }
     },
+    "node_modules/es-toolkit": {
+      "version": "1.39.10",
+      "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.39.10.tgz",
+      "integrity": "sha512-E0iGnTtbDhkeczB0T+mxmoVlT4YNweEKBLq7oaU4p11mecdsZpNWOglI4895Vh4usbQ+LsJiuLuI2L0Vdmfm2w==",
+      "license": "MIT",
+      "workspaces": [
+        "docs",
+        "benchmarks"
+      ]
+    },
     "node_modules/esbuild": {
       "version": "0.25.9",
       "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz",
@@ -4598,8 +4466,7 @@
     "node_modules/eventemitter3": {
       "version": "5.0.1",
       "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
-      "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==",
-      "dev": true
+      "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="
     },
     "node_modules/fast-deep-equal": {
       "version": "3.1.3",
@@ -5092,6 +4959,16 @@
         "node": ">= 4"
       }
     },
+    "node_modules/immer": {
+      "version": "10.1.3",
+      "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.3.tgz",
+      "integrity": "sha512-tmjF/k8QDKydUlm3mZU+tjM6zeq9/fFpPqH9SzWmBnVVKsPBg/V66qsMwb3/Bo90cgUN+ghdVBess+hPsxUyRw==",
+      "license": "MIT",
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/immer"
+      }
+    },
     "node_modules/import-fresh": {
       "version": "3.3.1",
       "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
@@ -5483,18 +5360,6 @@
       "integrity": "sha512-nMoGWW2HurtuJf6XAL56FWTDCWLOTSsanrgwOyaR5Y4e3zfG5N/0cU5xWZSEU3tBxhQugRbV1xL9jb+ug7yZww==",
       "dev": true
     },
-    "node_modules/loose-envify": {
-      "version": "1.4.0",
-      "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
-      "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
-      "license": "MIT",
-      "dependencies": {
-        "js-tokens": "^3.0.0 || ^4.0.0"
-      },
-      "bin": {
-        "loose-envify": "cli.js"
-      }
-    },
     "node_modules/lru-cache": {
       "version": "10.4.3",
       "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
@@ -5518,12 +5383,6 @@
         "node": ">=12"
       }
     },
-    "node_modules/math-expression-evaluator": {
-      "version": "1.4.0",
-      "resolved": "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-1.4.0.tgz",
-      "integrity": "sha512-4vRUvPyxdO8cWULGTh9dZWL2tZK6LDBvj+OGHBER7poH9Qdt7kXEoj20wiz4lQUbUXQZFjPbe5mVDo9nutizCw==",
-      "license": "MIT"
-    },
     "node_modules/math-intrinsics": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -5774,6 +5633,7 @@
       "version": "4.1.1",
       "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
       "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+      "dev": true,
       "engines": {
         "node": ">=0.10.0"
       }
@@ -6030,17 +5890,6 @@
         "url": "https://github.com/prettier/prettier?sponsor=1"
       }
     },
-    "node_modules/prop-types": {
-      "version": "15.8.1",
-      "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
-      "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
-      "license": "MIT",
-      "dependencies": {
-        "loose-envify": "^1.4.0",
-        "object-assign": "^4.1.1",
-        "react-is": "^16.13.1"
-      }
-    },
     "node_modules/proxy-compare": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/proxy-compare/-/proxy-compare-3.0.1.tgz",
@@ -6136,6 +5985,29 @@
       "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
       "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
     },
+    "node_modules/react-redux": {
+      "version": "9.2.0",
+      "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
+      "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/use-sync-external-store": "^0.0.6",
+        "use-sync-external-store": "^1.4.0"
+      },
+      "peerDependencies": {
+        "@types/react": "^18.2.25 || ^19",
+        "react": "^18.0 || ^19",
+        "redux": "^5.0.0"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        },
+        "redux": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/react-router": {
       "version": "7.8.2",
       "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.8.2.tgz",
@@ -6172,21 +6044,6 @@
         "react-dom": ">=18"
       }
     },
-    "node_modules/react-use-measure": {
-      "version": "2.1.7",
-      "resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.7.tgz",
-      "integrity": "sha512-KrvcAo13I/60HpwGO5jpW7E9DfusKyLPLvuHlUyP5zqnmAPhNc6qTRjUQrdTADl0lpPpDVU2/Gg51UlOGHXbdg==",
-      "license": "MIT",
-      "peerDependencies": {
-        "react": ">=16.13",
-        "react-dom": ">=16.13"
-      },
-      "peerDependenciesMeta": {
-        "react-dom": {
-          "optional": true
-        }
-      }
-    },
     "node_modules/readdirp": {
       "version": "3.6.0",
       "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
@@ -6199,32 +6056,54 @@
         "node": ">=8.10.0"
       }
     },
-    "node_modules/reduce-css-calc": {
-      "version": "1.3.0",
-      "resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz",
-      "integrity": "sha512-0dVfwYVOlf/LBA2ec4OwQ6p3X9mYxn/wOl2xTcLwjnPYrkgEfPx3VI4eGCH3rQLlPISG5v9I9bkZosKsNRTRKA==",
+    "node_modules/recharts": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.2.1.tgz",
+      "integrity": "sha512-0JKwHRiFZdmLq/6nmilxEZl3pqb4T+aKkOkOi/ZISRZwfBhVMgInxzlYU9D4KnCH3KINScLy68m/OvMXoYGZUw==",
       "license": "MIT",
       "dependencies": {
-        "balanced-match": "^0.4.2",
-        "math-expression-evaluator": "^1.2.14",
-        "reduce-function-call": "^1.0.1"
+        "@reduxjs/toolkit": "1.x.x || 2.x.x",
+        "clsx": "^2.1.1",
+        "decimal.js-light": "^2.5.1",
+        "es-toolkit": "^1.39.3",
+        "eventemitter3": "^5.0.1",
+        "immer": "^10.1.1",
+        "react-redux": "8.x.x || 9.x.x",
+        "reselect": "5.1.1",
+        "tiny-invariant": "^1.3.3",
+        "use-sync-external-store": "^1.2.2",
+        "victory-vendor": "^37.0.2"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "peerDependencies": {
+        "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+        "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+        "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
       }
     },
-    "node_modules/reduce-css-calc/node_modules/balanced-match": {
-      "version": "0.4.2",
-      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz",
-      "integrity": "sha512-STw03mQKnGUYtoNjmowo4F2cRmIIxYEGiMsjjwla/u5P1lxadj/05WkNaFjNiKTgJkj8KiXbgAiRTmcQRwQNtg==",
+    "node_modules/redux": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
+      "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
       "license": "MIT"
     },
-    "node_modules/reduce-function-call": {
-      "version": "1.0.3",
-      "resolved": "https://registry.npmjs.org/reduce-function-call/-/reduce-function-call-1.0.3.tgz",
-      "integrity": "sha512-Hl/tuV2VDgWgCSEeWMLwxLZqX7OK59eU1guxXsRKTAyeYimivsKdtcV4fu3r710tpG5GmDKDhQ0HSZLExnNmyQ==",
+    "node_modules/redux-thunk": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz",
+      "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==",
       "license": "MIT",
-      "dependencies": {
-        "balanced-match": "^1.0.0"
+      "peerDependencies": {
+        "redux": "^5.0.0"
       }
     },
+    "node_modules/reselect": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
+      "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==",
+      "license": "MIT"
+    },
     "node_modules/resolve": {
       "version": "1.22.10",
       "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
@@ -6284,12 +6163,6 @@
       "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
       "dev": true
     },
-    "node_modules/robust-predicates": {
-      "version": "3.0.2",
-      "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz",
-      "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==",
-      "license": "Unlicense"
-    },
     "node_modules/rollup": {
       "version": "4.48.0",
       "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.48.0.tgz",
@@ -6692,6 +6565,12 @@
         "node": ">=0.8"
       }
     },
+    "node_modules/tiny-invariant": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
+      "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==",
+      "license": "MIT"
+    },
     "node_modules/tinyglobby": {
       "version": "0.2.15",
       "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
@@ -6915,6 +6794,37 @@
         "punycode": "^2.1.0"
       }
     },
+    "node_modules/use-sync-external-store": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz",
+      "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==",
+      "license": "MIT",
+      "peerDependencies": {
+        "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+      }
+    },
+    "node_modules/victory-vendor": {
+      "version": "37.3.6",
+      "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz",
+      "integrity": "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==",
+      "license": "MIT AND ISC",
+      "dependencies": {
+        "@types/d3-array": "^3.0.3",
+        "@types/d3-ease": "^3.0.0",
+        "@types/d3-interpolate": "^3.0.1",
+        "@types/d3-scale": "^4.0.2",
+        "@types/d3-shape": "^3.1.0",
+        "@types/d3-time": "^3.0.0",
+        "@types/d3-timer": "^3.0.0",
+        "d3-array": "^3.1.6",
+        "d3-ease": "^3.0.1",
+        "d3-interpolate": "^3.0.1",
+        "d3-scale": "^4.0.2",
+        "d3-shape": "^3.1.0",
+        "d3-time": "^3.0.0",
+        "d3-timer": "^3.0.1"
+      }
+    },
     "node_modules/vite": {
       "version": "7.1.6",
       "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.6.tgz",
@@ -7360,6 +7270,12 @@
         "@babel/helper-validator-identifier": "^7.27.1"
       }
     },
+    "@chakra-ui/charts": {
+      "version": "3.27.0",
+      "resolved": "https://registry.npmjs.org/@chakra-ui/charts/-/charts-3.27.0.tgz",
+      "integrity": "sha512-nCn4TbbQZIbnr89ynETD4rrW3Rh+it+w55q3QUc76GbqMTfcs4I148UsP/nb5YURQ9WAHwmMXhzlW9T62JqSvw==",
+      "requires": {}
+    },
     "@chakra-ui/cli": {
       "version": "3.25.0",
       "resolved": "https://registry.npmjs.org/@chakra-ui/cli/-/cli-3.25.0.tgz",
@@ -8176,6 +8092,19 @@
       "dev": true,
       "optional": true
     },
+    "@reduxjs/toolkit": {
+      "version": "2.9.0",
+      "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.9.0.tgz",
+      "integrity": "sha512-fSfQlSRu9Z5yBkvsNhYF2rPS8cGXn/TZVrlwN1948QyZ8xMZ0JvP50S2acZNaf+o63u6aEeMjipFyksjIcWrog==",
+      "requires": {
+        "@standard-schema/spec": "^1.0.0",
+        "@standard-schema/utils": "^0.3.0",
+        "immer": "^10.0.3",
+        "redux": "^5.0.1",
+        "redux-thunk": "^3.1.0",
+        "reselect": "^5.1.0"
+      }
+    },
     "@rolldown/pluginutils": {
       "version": "1.0.0-beta.35",
       "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.35.tgz",
@@ -8389,6 +8318,11 @@
       "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==",
       "dev": true
     },
+    "@standard-schema/spec": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz",
+      "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="
+    },
     "@standard-schema/utils": {
       "version": "0.3.0",
       "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz",
@@ -8556,71 +8490,58 @@
       "integrity": "sha512-nl09VhutdjINdWyXxHWN/w9zlNCfr60JUqJbd24YXUuCwgeL0TpFSdElCwb6cxfB6ybE19Gjj4g0jsgkXxKv1Q=="
     },
     "@types/d3-array": {
-      "version": "3.0.3",
-      "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.0.3.tgz",
-      "integrity": "sha512-Reoy+pKnvsksN0lQUlcH6dOGjRZ/3WRwXR//m+/8lt1BXeI4xyaUZoqULNjyXXRuh0Mj4LNpkCvhUpQlY3X5xQ=="
+      "version": "3.2.2",
+      "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz",
+      "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw=="
     },
     "@types/d3-color": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.0.tgz",
-      "integrity": "sha512-HKuicPHJuvPgCD+np6Se9MQvS6OCbJmOjGvylzMJRlDwUXjKTTXs6Pwgk79O09Vj/ho3u1ofXnhFOaEWWPrlwA=="
-    },
-    "@types/d3-delaunay": {
-      "version": "6.0.1",
-      "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.1.tgz",
-      "integrity": "sha512-tLxQ2sfT0p6sxdG75c6f/ekqxjyYR0+LwPrsO1mbC9YDBzPJhs2HbJJRrn8Ez1DBoHRo2yx7YEATI+8V1nGMnQ=="
-    },
-    "@types/d3-format": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.1.tgz",
-      "integrity": "sha512-5KY70ifCCzorkLuIkDe0Z9YTf9RR2CjBX1iaJG+rgM/cPP+sO+q9YdQ9WdhQcgPj1EQiJ2/0+yUkkziTG6Lubg=="
+      "version": "3.1.3",
+      "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
+      "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="
     },
-    "@types/d3-geo": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz",
-      "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==",
-      "requires": {
-        "@types/geojson": "*"
-      }
+    "@types/d3-ease": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz",
+      "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA=="
     },
     "@types/d3-interpolate": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
-      "integrity": "sha512-jx5leotSeac3jr0RePOH1KdR9rISG91QIE4Q2PYTu4OymLTZfA3SrnURSLzKH48HmXVUru50b8nje4E79oQSQw==",
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
+      "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
       "requires": {
         "@types/d3-color": "*"
       }
     },
     "@types/d3-path": {
-      "version": "1.0.11",
-      "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-1.0.11.tgz",
-      "integrity": "sha512-4pQMp8ldf7UaB/gR8Fvvy69psNHkTpD/pVw3vmEi8iZAB9EPMBruB1JvHO4BIq9QkUUd2lV1F5YXpMNj7JPBpw=="
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz",
+      "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg=="
     },
     "@types/d3-scale": {
-      "version": "4.0.2",
-      "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.2.tgz",
-      "integrity": "sha512-Yk4htunhPAwN0XGlIwArRomOjdoBFXC3+kCxK2Ubg7I9shQlVSJy/pG/Ht5ASN+gdMIalpk8TJ5xV74jFsetLA==",
+      "version": "4.0.9",
+      "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz",
+      "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==",
       "requires": {
         "@types/d3-time": "*"
       }
     },
     "@types/d3-shape": {
-      "version": "1.3.12",
-      "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-1.3.12.tgz",
-      "integrity": "sha512-8oMzcd4+poSLGgV0R1Q1rOlx/xdmozS4Xab7np0eamFFUYq71AU9pOCJEFnkXW2aI/oXdVYJzw6pssbSut7Z9Q==",
+      "version": "3.1.7",
+      "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz",
+      "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==",
       "requires": {
-        "@types/d3-path": "^1"
+        "@types/d3-path": "*"
       }
     },
     "@types/d3-time": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.0.tgz",
-      "integrity": "sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg=="
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz",
+      "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g=="
     },
-    "@types/d3-time-format": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-2.1.0.tgz",
-      "integrity": "sha512-/myT3I7EwlukNOX2xVdMzb8FRgNzRMpsZddwst9Ld/VFe6LyJyRp0s32l/V9XoUzk+Gqu56F/oGk6507+8BxrA=="
+    "@types/d3-timer": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz",
+      "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw=="
     },
     "@types/debug": {
       "version": "4.1.12",
@@ -8637,11 +8558,6 @@
       "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
       "dev": true
     },
-    "@types/geojson": {
-      "version": "7946.0.16",
-      "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz",
-      "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg=="
-    },
     "@types/json-schema": {
       "version": "7.0.15",
       "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
@@ -8651,7 +8567,8 @@
     "@types/lodash": {
       "version": "4.17.20",
       "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz",
-      "integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA=="
+      "integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==",
+      "dev": true
     },
     "@types/luxon": {
       "version": "3.7.1",
@@ -8689,6 +8606,7 @@
       "version": "19.1.13",
       "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.13.tgz",
       "integrity": "sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ==",
+      "devOptional": true,
       "requires": {
         "csstype": "^3.0.2"
       }
@@ -8697,8 +8615,14 @@
       "version": "19.1.9",
       "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.9.tgz",
       "integrity": "sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ==",
+      "dev": true,
       "requires": {}
     },
+    "@types/use-sync-external-store": {
+      "version": "0.0.6",
+      "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
+      "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg=="
+    },
     "@typescript-eslint/eslint-plugin": {
       "version": "8.44.0",
       "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.44.0.tgz",
@@ -8838,144 +8762,6 @@
       "integrity": "sha512-eqm/OzwETl1Zd5ehW5CUXhYf8tqb+seBCkHBKXh1rEMS94n+OhyCY0KAlZv/17qPoN73WT2nGDN9SdYlvoWbTQ==",
       "dev": true
     },
-    "@visx/axis": {
-      "version": "3.12.0",
-      "resolved": "https://registry.npmjs.org/@visx/axis/-/axis-3.12.0.tgz",
-      "integrity": "sha512-8MoWpfuaJkhm2Yg+HwyytK8nk+zDugCqTT/tRmQX7gW4LYrHYLXFUXOzbDyyBakCVaUbUaAhVFxpMASJiQKf7A==",
-      "requires": {
-        "@types/react": "*",
-        "@visx/group": "3.12.0",
-        "@visx/point": "3.12.0",
-        "@visx/scale": "3.12.0",
-        "@visx/shape": "3.12.0",
-        "@visx/text": "3.12.0",
-        "classnames": "^2.3.1",
-        "prop-types": "^15.6.0"
-      }
-    },
-    "@visx/bounds": {
-      "version": "3.12.0",
-      "resolved": "https://registry.npmjs.org/@visx/bounds/-/bounds-3.12.0.tgz",
-      "integrity": "sha512-peAlNCUbYaaZ0IO6c1lDdEAnZv2iGPDiLIM8a6gu7CaMhtXZJkqrTh+AjidNcIqITktrICpGxJE/Qo9D099dvQ==",
-      "requires": {
-        "@types/react": "*",
-        "@types/react-dom": "*",
-        "prop-types": "^15.5.10"
-      }
-    },
-    "@visx/curve": {
-      "version": "3.12.0",
-      "resolved": "https://registry.npmjs.org/@visx/curve/-/curve-3.12.0.tgz",
-      "integrity": "sha512-Ng1mefXIzoIoAivw7dJ+ZZYYUbfuwXgZCgQynShr6ZIVw7P4q4HeQfJP3W24ON+1uCSrzoycHSXRelhR9SBPcw==",
-      "requires": {
-        "@types/d3-shape": "^1.3.1",
-        "d3-shape": "^1.0.6"
-      }
-    },
-    "@visx/group": {
-      "version": "3.12.0",
-      "resolved": "https://registry.npmjs.org/@visx/group/-/group-3.12.0.tgz",
-      "integrity": "sha512-Dye8iS1alVXPv7nj/7M37gJe6sSKqJLH7x6sEWAsRQ9clI0kFvjbKcKgF+U3aAVQr0NCohheFV+DtR8trfK/Ag==",
-      "requires": {
-        "@types/react": "*",
-        "classnames": "^2.3.1",
-        "prop-types": "^15.6.2"
-      }
-    },
-    "@visx/point": {
-      "version": "3.12.0",
-      "resolved": "https://registry.npmjs.org/@visx/point/-/point-3.12.0.tgz",
-      "integrity": "sha512-I6UrHoJAEVbx3RORQNupgTiX5EzjuZpiwLPxn8L2mI5nfERotPKi1Yus12Cq2WtXqEBR/WgqTnoImlqOXBykcA=="
-    },
-    "@visx/responsive": {
-      "version": "3.12.0",
-      "resolved": "https://registry.npmjs.org/@visx/responsive/-/responsive-3.12.0.tgz",
-      "integrity": "sha512-GV1BTYwAGlk/K5c9vH8lS2syPnTuIqEacI7L6LRPbsuaLscXMNi+i9fZyzo0BWvAdtRV8v6Urzglo++lvAXT1Q==",
-      "requires": {
-        "@types/lodash": "^4.14.172",
-        "@types/react": "*",
-        "lodash": "^4.17.21",
-        "prop-types": "^15.6.1"
-      }
-    },
-    "@visx/scale": {
-      "version": "3.12.0",
-      "resolved": "https://registry.npmjs.org/@visx/scale/-/scale-3.12.0.tgz",
-      "integrity": "sha512-+ubijrZ2AwWCsNey0HGLJ0YKNeC/XImEFsr9rM+Uef1CM3PNM43NDdNTrdBejSlzRq0lcfQPWYMYQFSlkLcPOg==",
-      "requires": {
-        "@visx/vendor": "3.12.0"
-      }
-    },
-    "@visx/shape": {
-      "version": "3.12.0",
-      "resolved": "https://registry.npmjs.org/@visx/shape/-/shape-3.12.0.tgz",
-      "integrity": "sha512-/1l0lrpX9tPic6SJEalryBKWjP/ilDRnQA+BGJTI1tj7i23mJ/J0t4nJHyA1GrL4QA/bM/qTJ35eyz5dEhJc4g==",
-      "requires": {
-        "@types/d3-path": "^1.0.8",
-        "@types/d3-shape": "^1.3.1",
-        "@types/lodash": "^4.14.172",
-        "@types/react": "*",
-        "@visx/curve": "3.12.0",
-        "@visx/group": "3.12.0",
-        "@visx/scale": "3.12.0",
-        "classnames": "^2.3.1",
-        "d3-path": "^1.0.5",
-        "d3-shape": "^1.2.0",
-        "lodash": "^4.17.21",
-        "prop-types": "^15.5.10"
-      }
-    },
-    "@visx/text": {
-      "version": "3.12.0",
-      "resolved": "https://registry.npmjs.org/@visx/text/-/text-3.12.0.tgz",
-      "integrity": "sha512-0rbDYQlbuKPhBqXkkGYKFec1gQo05YxV45DORzr6hCyaizdJk1G+n9VkuKSHKBy1vVQhBA0W3u/WXd7tiODQPA==",
-      "requires": {
-        "@types/lodash": "^4.14.172",
-        "@types/react": "*",
-        "classnames": "^2.3.1",
-        "lodash": "^4.17.21",
-        "prop-types": "^15.7.2",
-        "reduce-css-calc": "^1.3.0"
-      }
-    },
-    "@visx/tooltip": {
-      "version": "3.12.0",
-      "resolved": "https://registry.npmjs.org/@visx/tooltip/-/tooltip-3.12.0.tgz",
-      "integrity": "sha512-pWhsYhgl0Shbeqf80qy4QCB6zpq6tQtMQQxKlh3UiKxzkkfl+Metaf9p0/S0HexNi4vewOPOo89xWx93hBeh3A==",
-      "requires": {
-        "@types/react": "*",
-        "@visx/bounds": "3.12.0",
-        "classnames": "^2.3.1",
-        "prop-types": "^15.5.10",
-        "react-use-measure": "^2.0.4"
-      }
-    },
-    "@visx/vendor": {
-      "version": "3.12.0",
-      "resolved": "https://registry.npmjs.org/@visx/vendor/-/vendor-3.12.0.tgz",
-      "integrity": "sha512-SVO+G0xtnL9dsNpGDcjCgoiCnlB3iLSM9KLz1sLbSrV7RaVXwY3/BTm2X9OWN1jH2a9M+eHt6DJ6sE6CXm4cUg==",
-      "requires": {
-        "@types/d3-array": "3.0.3",
-        "@types/d3-color": "3.1.0",
-        "@types/d3-delaunay": "6.0.1",
-        "@types/d3-format": "3.0.1",
-        "@types/d3-geo": "3.1.0",
-        "@types/d3-interpolate": "3.0.1",
-        "@types/d3-scale": "4.0.2",
-        "@types/d3-time": "3.0.0",
-        "@types/d3-time-format": "2.1.0",
-        "d3-array": "3.2.1",
-        "d3-color": "3.1.0",
-        "d3-delaunay": "6.0.2",
-        "d3-format": "3.1.0",
-        "d3-geo": "3.1.0",
-        "d3-interpolate": "3.0.1",
-        "d3-scale": "4.0.2",
-        "d3-time": "3.1.0",
-        "d3-time-format": "4.1.0",
-        "internmap": "2.0.3"
-      }
-    },
     "@vitejs/plugin-react-swc": {
       "version": "4.1.0",
       "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-4.1.0.tgz",
@@ -9868,7 +9654,8 @@
     "balanced-match": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
-      "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
+      "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+      "dev": true
     },
     "base64-arraybuffer": {
       "version": "1.0.2",
@@ -9955,11 +9742,6 @@
         "readdirp": "~3.6.0"
       }
     },
-    "classnames": {
-      "version": "2.5.1",
-      "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
-      "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow=="
-    },
     "cli-cursor": {
       "version": "5.0.0",
       "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz",
@@ -9988,6 +9770,11 @@
         "string-width": "^7.0.0"
       }
     },
+    "clsx": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+      "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="
+    },
     "color-convert": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -10087,9 +9874,9 @@
       "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
     },
     "d3-array": {
-      "version": "3.2.1",
-      "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.1.tgz",
-      "integrity": "sha512-gUY/qeHq/yNqqoCKNq4vtpFLdoCdvyNpWoC/KNjhGbhDuQpAM9sIQQKkXSNpXa9h5KySs/gzm7R88WkUutgwWQ==",
+      "version": "3.2.4",
+      "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
+      "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
       "requires": {
         "internmap": "1 - 2"
       }
@@ -10099,27 +9886,16 @@
       "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
       "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA=="
     },
-    "d3-delaunay": {
-      "version": "6.0.2",
-      "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.2.tgz",
-      "integrity": "sha512-IMLNldruDQScrcfT+MWnazhHbDJhcRJyOEBAJfwQnHle1RPh6WDuLvxNArUju2VSMSUuKlY5BGHRJ2cYyoFLQQ==",
-      "requires": {
-        "delaunator": "5"
-      }
+    "d3-ease": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
+      "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w=="
     },
     "d3-format": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz",
       "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA=="
     },
-    "d3-geo": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.0.tgz",
-      "integrity": "sha512-JEo5HxXDdDYXCaWdwLRt79y7giK8SbhZJbFWXqbRTolCHFI5jRqteLzCsq51NKbUoX0PjBVSohxrx+NoOUujYA==",
-      "requires": {
-        "d3-array": "2.5.0 - 3"
-      }
-    },
     "d3-interpolate": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
@@ -10129,9 +9905,9 @@
       }
     },
     "d3-path": {
-      "version": "1.0.9",
-      "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz",
-      "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg=="
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz",
+      "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ=="
     },
     "d3-scale": {
       "version": "4.0.2",
@@ -10146,11 +9922,11 @@
       }
     },
     "d3-shape": {
-      "version": "1.3.7",
-      "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz",
-      "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==",
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
+      "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
       "requires": {
-        "d3-path": "1"
+        "d3-path": "^3.1.0"
       }
     },
     "d3-time": {
@@ -10169,6 +9945,11 @@
         "d3-time": "1 - 3"
       }
     },
+    "d3-timer": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
+      "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA=="
+    },
     "data-uri-to-buffer": {
       "version": "4.0.1",
       "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
@@ -10188,20 +9969,17 @@
         "ms": "^2.1.3"
       }
     },
+    "decimal.js-light": {
+      "version": "2.5.1",
+      "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
+      "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg=="
+    },
     "deep-is": {
       "version": "0.1.4",
       "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
       "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
       "dev": true
     },
-    "delaunator": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz",
-      "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==",
-      "requires": {
-        "robust-predicates": "^3.0.2"
-      }
-    },
     "delayed-stream": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@@ -10272,6 +10050,11 @@
         "hasown": "^2.0.2"
       }
     },
+    "es-toolkit": {
+      "version": "1.39.10",
+      "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.39.10.tgz",
+      "integrity": "sha512-E0iGnTtbDhkeczB0T+mxmoVlT4YNweEKBLq7oaU4p11mecdsZpNWOglI4895Vh4usbQ+LsJiuLuI2L0Vdmfm2w=="
+    },
     "esbuild": {
       "version": "0.25.9",
       "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz",
@@ -10521,8 +10304,7 @@
     "eventemitter3": {
       "version": "5.0.1",
       "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
-      "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==",
-      "dev": true
+      "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="
     },
     "fast-deep-equal": {
       "version": "3.1.3",
@@ -10849,6 +10631,11 @@
       "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
       "dev": true
     },
+    "immer": {
+      "version": "10.1.3",
+      "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.3.tgz",
+      "integrity": "sha512-tmjF/k8QDKydUlm3mZU+tjM6zeq9/fFpPqH9SzWmBnVVKsPBg/V66qsMwb3/Bo90cgUN+ghdVBess+hPsxUyRw=="
+    },
     "import-fresh": {
       "version": "3.3.1",
       "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
@@ -11129,14 +10916,6 @@
       "integrity": "sha512-nMoGWW2HurtuJf6XAL56FWTDCWLOTSsanrgwOyaR5Y4e3zfG5N/0cU5xWZSEU3tBxhQugRbV1xL9jb+ug7yZww==",
       "dev": true
     },
-    "loose-envify": {
-      "version": "1.4.0",
-      "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
-      "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
-      "requires": {
-        "js-tokens": "^3.0.0 || ^4.0.0"
-      }
-    },
     "lru-cache": {
       "version": "10.4.3",
       "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
@@ -11154,11 +10933,6 @@
       "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz",
       "integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew=="
     },
-    "math-expression-evaluator": {
-      "version": "1.4.0",
-      "resolved": "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-1.4.0.tgz",
-      "integrity": "sha512-4vRUvPyxdO8cWULGTh9dZWL2tZK6LDBvj+OGHBER7poH9Qdt7kXEoj20wiz4lQUbUXQZFjPbe5mVDo9nutizCw=="
-    },
     "math-intrinsics": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -11324,7 +11098,8 @@
     "object-assign": {
       "version": "4.1.1",
       "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
-      "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="
+      "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+      "dev": true
     },
     "onetime": {
       "version": "7.0.0",
@@ -11486,16 +11261,6 @@
       "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
       "dev": true
     },
-    "prop-types": {
-      "version": "15.8.1",
-      "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
-      "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
-      "requires": {
-        "loose-envify": "^1.4.0",
-        "object-assign": "^4.1.1",
-        "react-is": "^16.13.1"
-      }
-    },
     "proxy-compare": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/proxy-compare/-/proxy-compare-3.0.1.tgz",
@@ -11556,6 +11321,15 @@
       "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
       "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
     },
+    "react-redux": {
+      "version": "9.2.0",
+      "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
+      "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
+      "requires": {
+        "@types/use-sync-external-store": "^0.0.6",
+        "use-sync-external-store": "^1.4.0"
+      }
+    },
     "react-router": {
       "version": "7.8.2",
       "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.8.2.tgz",
@@ -11573,12 +11347,6 @@
         "react-router": "7.8.2"
       }
     },
-    "react-use-measure": {
-      "version": "2.1.7",
-      "resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.7.tgz",
-      "integrity": "sha512-KrvcAo13I/60HpwGO5jpW7E9DfusKyLPLvuHlUyP5zqnmAPhNc6qTRjUQrdTADl0lpPpDVU2/Gg51UlOGHXbdg==",
-      "requires": {}
-    },
     "readdirp": {
       "version": "3.6.0",
       "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
@@ -11588,30 +11356,39 @@
         "picomatch": "^2.2.1"
       }
     },
-    "reduce-css-calc": {
-      "version": "1.3.0",
-      "resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz",
-      "integrity": "sha512-0dVfwYVOlf/LBA2ec4OwQ6p3X9mYxn/wOl2xTcLwjnPYrkgEfPx3VI4eGCH3rQLlPISG5v9I9bkZosKsNRTRKA==",
+    "recharts": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.2.1.tgz",
+      "integrity": "sha512-0JKwHRiFZdmLq/6nmilxEZl3pqb4T+aKkOkOi/ZISRZwfBhVMgInxzlYU9D4KnCH3KINScLy68m/OvMXoYGZUw==",
       "requires": {
-        "balanced-match": "^0.4.2",
-        "math-expression-evaluator": "^1.2.14",
-        "reduce-function-call": "^1.0.1"
-      },
-      "dependencies": {
-        "balanced-match": {
-          "version": "0.4.2",
-          "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz",
-          "integrity": "sha512-STw03mQKnGUYtoNjmowo4F2cRmIIxYEGiMsjjwla/u5P1lxadj/05WkNaFjNiKTgJkj8KiXbgAiRTmcQRwQNtg=="
-        }
+        "@reduxjs/toolkit": "1.x.x || 2.x.x",
+        "clsx": "^2.1.1",
+        "decimal.js-light": "^2.5.1",
+        "es-toolkit": "^1.39.3",
+        "eventemitter3": "^5.0.1",
+        "immer": "^10.1.1",
+        "react-redux": "8.x.x || 9.x.x",
+        "reselect": "5.1.1",
+        "tiny-invariant": "^1.3.3",
+        "use-sync-external-store": "^1.2.2",
+        "victory-vendor": "^37.0.2"
       }
     },
-    "reduce-function-call": {
-      "version": "1.0.3",
-      "resolved": "https://registry.npmjs.org/reduce-function-call/-/reduce-function-call-1.0.3.tgz",
-      "integrity": "sha512-Hl/tuV2VDgWgCSEeWMLwxLZqX7OK59eU1guxXsRKTAyeYimivsKdtcV4fu3r710tpG5GmDKDhQ0HSZLExnNmyQ==",
-      "requires": {
-        "balanced-match": "^1.0.0"
-      }
+    "redux": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
+      "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w=="
+    },
+    "redux-thunk": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz",
+      "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==",
+      "requires": {}
+    },
+    "reselect": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
+      "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w=="
     },
     "resolve": {
       "version": "1.22.10",
@@ -11650,11 +11427,6 @@
       "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
       "dev": true
     },
-    "robust-predicates": {
-      "version": "3.0.2",
-      "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz",
-      "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg=="
-    },
     "rollup": {
       "version": "4.48.0",
       "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.48.0.tgz",
@@ -11937,6 +11709,11 @@
         "thenify": ">= 3.1.0 < 4"
       }
     },
+    "tiny-invariant": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
+      "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="
+    },
     "tinyglobby": {
       "version": "0.2.15",
       "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
@@ -12072,6 +11849,33 @@
         "punycode": "^2.1.0"
       }
     },
+    "use-sync-external-store": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz",
+      "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==",
+      "requires": {}
+    },
+    "victory-vendor": {
+      "version": "37.3.6",
+      "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz",
+      "integrity": "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==",
+      "requires": {
+        "@types/d3-array": "^3.0.3",
+        "@types/d3-ease": "^3.0.0",
+        "@types/d3-interpolate": "^3.0.1",
+        "@types/d3-scale": "^4.0.2",
+        "@types/d3-shape": "^3.1.0",
+        "@types/d3-time": "^3.0.0",
+        "@types/d3-timer": "^3.0.0",
+        "d3-array": "^3.1.6",
+        "d3-ease": "^3.0.1",
+        "d3-interpolate": "^3.0.1",
+        "d3-scale": "^4.0.2",
+        "d3-shape": "^3.1.0",
+        "d3-time": "^3.0.0",
+        "d3-timer": "^3.0.1"
+      }
+    },
     "vite": {
       "version": "7.1.6",
       "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.6.tgz",
diff --git a/thingconnect.pulse.client/package.json b/thingconnect.pulse.client/package.json
index 5ae7b42..250acbe 100644
--- a/thingconnect.pulse.client/package.json
+++ b/thingconnect.pulse.client/package.json
@@ -12,17 +12,13 @@
     "prepare": "cd .. && husky ./thingconnect.pulse.client/.husky"
   },
   "dependencies": {
+    "@chakra-ui/charts": "^3.27.0",
     "@chakra-ui/react": "^3.24.2",
     "@emotion/react": "^11.14.0",
     "@hookform/resolvers": "^5.2.1",
     "@monaco-editor/react": "^4.7.0",
     "@sentry/react": "^10.11.0",
     "@tanstack/react-query": "^5.84.2",
-    "@visx/axis": "^3.12.0",
-    "@visx/responsive": "^3.12.0",
-    "@visx/scale": "^3.12.0",
-    "@visx/shape": "^3.12.0",
-    "@visx/tooltip": "^3.12.0",
     "axios": "^1.11.0",
     "date-fns": "^4.1.0",
     "framer-motion": "^12.23.14",
@@ -38,6 +34,7 @@
     "react-hook-form": "^7.62.0",
     "react-icons": "^5.5.0",
     "react-router-dom": "^7.8.1",
+    "recharts": "^3.2.1",
     "zod": "^4.1.9"
   },
   "devDependencies": {
diff --git a/thingconnect.pulse.client/src/api/services/endpoint.service.ts b/thingconnect.pulse.client/src/api/services/endpoint.service.ts
index 77f921f..3d749ba 100644
--- a/thingconnect.pulse.client/src/api/services/endpoint.service.ts
+++ b/thingconnect.pulse.client/src/api/services/endpoint.service.ts
@@ -17,18 +17,10 @@ export class EndpointService {
       if (!endpointItem) {
         throw new Error(`Endpoint ${id} not found`);
       }
-
       // Convert live status to endpoint detail format
       return {
         endpoint: endpointItem.endpoint,
-        recent: [
-          {
-            ts: endpointItem.lastChangeTs,
-            status: endpointItem.status === 'flapping' ? 'down' : endpointItem.status,
-            rttMs: endpointItem.rttMs,
-            error: null,
-          },
-        ],
+        recent: [],
         outages: [],
       };
     }
diff --git a/thingconnect.pulse.client/src/api/services/history.service.ts b/thingconnect.pulse.client/src/api/services/history.service.ts
index db50fa7..795b3c8 100644
--- a/thingconnect.pulse.client/src/api/services/history.service.ts
+++ b/thingconnect.pulse.client/src/api/services/history.service.ts
@@ -73,14 +73,19 @@ export class HistoryService {
 
     // Determine which data to export based on bucket
     if (bucket === 'raw' && data.raw.length > 0) {
-      lines.push('Timestamp,Status,Response Time (ms),Error');
+      lines.push(
+        'Timestamp,Primary Status,Primary RTT (ms),Primary Error,Fallback Status,Fallback RTT (ms),Fallback Error'
+      );
       data.raw.forEach(check => {
         lines.push(
           [
             check.ts,
-            check.status,
-            check.rttMs || '',
-            check.error ? `"${check.error.replace(/"/g, '""')}"` : '',
+            check.primary.status,
+            check.primary.rttMs || '',
+            check.primary.error ? `"${check.primary.error.replace(/"/g, '""')}"` : '',
+            check.fallback.status,
+            check.fallback.rttMs || '',
+            check.fallback.error ? `"${check.fallback.error.replace(/"/g, '""')}"` : '',
           ].join(',')
         );
       });
diff --git a/thingconnect.pulse.client/src/api/types.ts b/thingconnect.pulse.client/src/api/types.ts
index 67b2dc7..4309b3e 100644
--- a/thingconnect.pulse.client/src/api/types.ts
+++ b/thingconnect.pulse.client/src/api/types.ts
@@ -30,8 +30,7 @@ export interface SparklinePoint {
 
 export interface LiveStatusItem {
   endpoint: Endpoint;
-  status: 'up' | 'down' | 'flapping';
-  rttMs?: number | null;
+  currentState: CurrentState;
   lastChangeTs: string;
   sparkline: SparklinePoint[];
 }
@@ -82,18 +81,57 @@ export interface StateChange {
   error?: string;
 }
 
-export interface RawCheck {
-  ts: string;
+export type Classification =
+  | -1 // None
+  | 0 // Unknown
+  | 1 // Network
+  | 2 // Service
+  | 3 // Intermittent
+  | 4 // Performance
+  | 5 // PartialService
+  | 6 // DnsResolution
+  | 7 // Congestion
+  | 8; // Maintenance
+
+export interface PrimaryResult {
+  type: string;        // "icmp" | "tcp" | "http"
+  target: string;      // hostname or IP
   status: 'up' | 'down';
   rttMs?: number | null;
   error?: string | null;
 }
 
+export interface FallbackResult {
+  attempted: boolean;
+  type?: 'icmp'| null;
+  target?: string | null;
+  status?: 'up' | 'down' | null;
+  rttMs?: number | null;
+  error?: string | null;
+}
+
+export interface CurrentState {
+  type: 'icmp' | 'tcp' | 'http';
+  target: string;
+  status: 'up' | 'down' | 'flapping' | 'service';
+  rttMs?: number | null;
+  classification?: Classification | null;
+}
+
+export interface RawCheck {
+  ts: string;                 
+  classification: Classification;
+  primary: PrimaryResult;
+  fallback: FallbackResult;
+  currentState: CurrentState;
+}
+
 export interface Outage {
   startedTs: string;
   endedTs?: string | null;
   durationS?: number | null;
   lastError?: string | null;
+  classification: Classification;
 }
 
 export interface EndpointDetail {
diff --git a/thingconnect.pulse.client/src/components/AvailabilityChart.tsx b/thingconnect.pulse.client/src/components/AvailabilityChart.tsx
index f57cdb0..2a1d75c 100644
--- a/thingconnect.pulse.client/src/components/AvailabilityChart.tsx
+++ b/thingconnect.pulse.client/src/components/AvailabilityChart.tsx
@@ -1,9 +1,7 @@
 import { useMemo } from 'react';
 import { Box, Text, VStack, Skeleton } from '@chakra-ui/react';
-import { ParentSize } from '@visx/responsive';
-import { Group } from '@visx/group';
-import { AxisLeft, AxisBottom } from '@visx/axis';
-import { scaleBand, scaleLinear } from '@visx/scale';
+import { Chart, useChart } from '@chakra-ui/charts';
+import { Bar, BarChart, CartesianGrid, Tooltip, XAxis, YAxis } from 'recharts';
 import type { HistoryResponse } from '@/api/types';
 import type { BucketType } from '@/types/bucket';
 import { CloudOff } from 'lucide-react';
@@ -18,37 +16,42 @@ export interface AvailabilityChartProps {
 
 export function AvailabilityChart({ data, bucket, isLoading }: AvailabilityChartProps) {
   const chartData = useMemo(() => {
-    if (!data) return null;
+    if (!data) return [];
     switch (bucket) {
       case 'raw':
         return data.raw.map(check => ({
-          xaxis: new Date(check.ts).toLocaleTimeString('en-US', {
+          label: new Date(check.ts).toLocaleTimeString('en-US', {
             hour: '2-digit',
             minute: '2-digit',
           }),
-          yaxis: check.status === 'up' ? 100 : 0,
+          uptime: check.currentState.status === 'up' ? 100 : 0,
         }));
       case '15m':
         return data.rollup15m.map(bucket => ({
-          xaxis: new Date(bucket.bucketTs).toLocaleTimeString('en-US', {
+          label: new Date(bucket.bucketTs).toLocaleTimeString('en-US', {
             hour: '2-digit',
             minute: '2-digit',
           }),
-          yaxis: bucket.upPct,
+          uptime: bucket.upPct,
         }));
       case 'daily':
         return data.rollupDaily.map(bucket => ({
-          xaxis: new Date(bucket.bucketDate).toLocaleDateString('en-US', {
+          label: new Date(bucket.bucketDate).toLocaleDateString('en-US', {
             month: 'short',
             day: 'numeric',
           }),
-          yaxis: bucket.upPct,
+          uptime: bucket.upPct,
         }));
       default:
         return [];
     }
   }, [data, bucket]);
 
+  const chart = useChart({
+    data: chartData,
+    series: [{ name: 'uptime', color: 'blue.500' }],
+  });
+
   if (chartData?.length === 0) {
     return (
       
-      
-        {({ width, height }) => {
-          const xMax = width - margin.left - margin.right;
-          const yMax = height - margin.top - margin.bottom;
-
-          const xScale = scaleBand({
-            range: [0, xMax],
-            domain: chartData?.map(d => d.xaxis) ?? [],
-            padding: 0.2,
-          });
-
-          const yScale = scaleLinear({
-            range: [yMax, 0],
-            domain: [0, 100],
-          });
-
-          return (
-            
-          );
-        }}
-      
-    
+    
+      
+        
+        
+         `${v}%`}
+        />
+         {
+            if (active && payload && payload.length) {
+              const uptime = payload[0].payload.uptime;
+              return (
+                
+                  {`Time: ${label}`}
+                  {`Uptime: ${uptime.toFixed(3)}%`}
+                
+              );
+            }
+            return null;
+          }}
+        />
+        {chart.series.map(s => (
+          
+        ))}
+        
+      
+    
   );
 }
diff --git a/thingconnect.pulse.client/src/components/OutageList.tsx b/thingconnect.pulse.client/src/components/OutageList.tsx
index cbdd921..cccd760 100644
--- a/thingconnect.pulse.client/src/components/OutageList.tsx
+++ b/thingconnect.pulse.client/src/components/OutageList.tsx
@@ -30,6 +30,9 @@ export function OutagesList({ outages, isLoading }: OutagesListProps) {
         gap={1}
         py={5}
         h='100%'
+        bg='gray.50'
+        _dark={{ bg: 'gray.800' }}
+        borderRadius='md'
       >
         
         
diff --git a/thingconnect.pulse.client/src/components/RecentChecksTable.tsx b/thingconnect.pulse.client/src/components/RecentChecksTable.tsx
index 4e78efa..13d9397 100644
--- a/thingconnect.pulse.client/src/components/RecentChecksTable.tsx
+++ b/thingconnect.pulse.client/src/components/RecentChecksTable.tsx
@@ -36,6 +36,9 @@ export function RecentChecksTable({ checks, pageSize = 10 }: RecentChecksTablePr
         gap={1}
         py={5}
         h='100%'
+        bg='gray.50'
+        _dark={{ bg: 'gray.800' }}
+        borderRadius='md'
       >
         
         
@@ -51,35 +54,85 @@ export function RecentChecksTable({ checks, pageSize = 10 }: RecentChecksTablePr
         
           
             
-              Time
-              Status
-              RTT
-              Error
+              Time
+              Primary Status
+              Primary RTT (ms)
+              Primary Error
+              Fallback Status
+              Fallback RTT (ms)
+              Fallback Error
             
           
+
           
-            {pagedChecks.map((check, index) => (
-              
+            {pagedChecks.map((check, idx) => (
+              
                 
                   
                     {formatDistanceToNow(new Date(check.ts), { addSuffix: true })}
                   
                 
                 
-                  
-                    {check.status.toUpperCase()}
+                  
+                    {check.primary.status.toUpperCase()}
                   
                 
                 
-                  {check.rttMs ? `${check.rttMs}ms` : '-'}
+                  
+                    {check.primary.rttMs ? `${check.primary.rttMs}ms` : '-'}
+                  
                 
-                
-                  
-                    
-                      {check.error || '-'}
+                
+                  
+                    
+                      {check.primary.error || '-'}
                     
                   
                 
+                
+                  {check.fallback.attempted ? (
+                    
+                      {check.fallback.status?.toUpperCase() ?? '-'}
+                    
+                  ) : (
+                    
+                      Not attempted
+                    
+                  )}
+                
+                
+                  
+                    {check.fallback.attempted && check.fallback.rttMs != null
+                      ? `${check.fallback.rttMs}ms`
+                      : '-'}
+                  
+                
+                
+                  {check.fallback.attempted ? (
+                    
+                      
+                        {check.fallback.error || '-'}
+                      
+                    
+                  ) : (
+                    
+                      Not attempted
+                    
+                  )}
+                
               
             ))}
           
diff --git a/thingconnect.pulse.client/src/components/history/AvailabilityStats.tsx b/thingconnect.pulse.client/src/components/history/AvailabilityStats.tsx
index 5e27c3a..916dbb7 100644
--- a/thingconnect.pulse.client/src/components/history/AvailabilityStats.tsx
+++ b/thingconnect.pulse.client/src/components/history/AvailabilityStats.tsx
@@ -35,9 +35,12 @@ export function AvailabilityStats({
     switch (bucket) {
       case 'raw': {
         totalPoints = data.raw.length;
-        upPoints = data.raw.filter(check => check.status === 'up').length;
-        const validRttChecks = data.raw.filter(check => check.rttMs != null);
-        totalResponseTime = validRttChecks.reduce((sum, check) => sum + (check.rttMs || 0), 0);
+        upPoints = data.raw.filter(check => check.currentState.status === 'up').length;
+        const validRttChecks = data.raw.filter(check => check.currentState.rttMs != null);
+        totalResponseTime = validRttChecks.reduce(
+          (sum, check) => sum + (check.currentState.rttMs || 0),
+          0
+        );
         responseTimeCount = validRttChecks.length;
         break;
       }
diff --git a/thingconnect.pulse.client/src/components/history/HistoryTable.tsx b/thingconnect.pulse.client/src/components/history/HistoryTable.tsx
index 2371b67..1789729 100644
--- a/thingconnect.pulse.client/src/components/history/HistoryTable.tsx
+++ b/thingconnect.pulse.client/src/components/history/HistoryTable.tsx
@@ -41,9 +41,8 @@ export function HistoryTable({ data, bucket, pageSize = 20, isLoading }: History
           .map(check => ({
             timestamp: check.ts,
             displayTime: new Date(check.ts).toLocaleString(),
-            status: check.status,
-            responseTime: check.rttMs,
-            error: check.error,
+            primary: check.primary,
+            fallback: check.fallback,
             type: 'raw' as const,
           }))
           .sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
@@ -88,7 +87,7 @@ export function HistoryTable({ data, bucket, pageSize = 20, isLoading }: History
     return tableData.slice(startIndex, startIndex + pageSize);
   }, [tableData, currentPage, pageSize]);
 
-  const getStatusBadge = (status?: string) => {
+  const getStatusBadge = (status?: string | null) => {
     if (!status) return null;
     const config = {
       up: { color: 'green', icon: CheckCircle },
@@ -150,9 +149,12 @@ export function HistoryTable({ data, bucket, pageSize = 20, isLoading }: History
               Timestamp
               {bucket === 'raw' ? (
                 <>
-                  Status
-                  Response Time
-                  Error
+                  Primary Status
+                  Primary RTT
+                  Primary Error
+                  Fallback Status
+                  Fallback RTT
+                  Fallback Error
                 >
               ) : (
                 <>
@@ -168,7 +170,7 @@ export function HistoryTable({ data, bucket, pageSize = 20, isLoading }: History
             {isLoading
               ? Array.from({ length: 8 }).map((_, i) => (
                   
-                    {Array.from({ length: bucket === 'raw' ? 4 : 4 }).map((_, j) => (
+                    {Array.from({ length: bucket === 'raw' ? 7 : 4 }).map((_, j) => (
                       
                         
                       
@@ -185,23 +187,43 @@ export function HistoryTable({ data, bucket, pageSize = 20, isLoading }: History
 
                     {row.type === 'raw' ? (
                       <>
-                        {getStatusBadge(row.status)}
+                        {getStatusBadge(row.primary?.status)}
                         
                           
-                            {formatResponseTime(row.responseTime)}
+                            {formatResponseTime(row.primary?.rttMs)}
+                          
+                        
+                        
+                          
+                            
+                              {row.primary?.error || '-'}
+                            
+                          
+                        
+                        {getStatusBadge(row.fallback?.status)}
+                        
+                          
+                            {formatResponseTime(row.fallback?.rttMs)}
                           
                         
                         
-                          
+                          
                             
-                              {row.error || '-'}
+                              {row.fallback?.error || '-'}
                             
                           
                         
diff --git a/thingconnect.pulse.client/src/components/status/StatusAccordion.tsx b/thingconnect.pulse.client/src/components/status/StatusAccordion.tsx
index a2036d1..3cdb9c5 100644
--- a/thingconnect.pulse.client/src/components/status/StatusAccordion.tsx
+++ b/thingconnect.pulse.client/src/components/status/StatusAccordion.tsx
@@ -3,11 +3,18 @@ import type { LiveStatusItem } from '@/api/types';
 import { StatusTable } from './StatusTable';
 
 type Props = {
-  groupedEndpoints: Record<'up' | 'down' | 'flapping', LiveStatusItem[]>;
+  groupedEndpoints: Record<'up' | 'down' | 'flapping' | 'service', LiveStatusItem[]>;
   isLoading: boolean;
 };
 
 export function StatusAccordion({ groupedEndpoints, isLoading }: Props) {
+  const statusColorMap: Record<'up' | 'down' | 'flapping' | 'service', string> = {
+    up: 'green',
+    down: 'red',
+    flapping: 'yellow',
+    service: 'orange',
+  } as const;
+
   return (
     
       {Object.entries(groupedEndpoints).map(([status, items]) => {
@@ -16,21 +23,15 @@ export function StatusAccordion({ groupedEndpoints, isLoading }: Props) {
           ? typedItems
           : Object.values(typedItems).flat();
 
-        const statusColorMap: Record<'up' | 'down' | 'flapping', string> = {
-          up: 'green',
-          down: 'red',
-          flapping: 'yellow',
-        } as const;
-
         return (
           
             
               
@@ -38,11 +39,11 @@ export function StatusAccordion({ groupedEndpoints, isLoading }: Props) {
                   
                   
                     {status}
@@ -62,11 +63,11 @@ export function StatusAccordion({ groupedEndpoints, isLoading }: Props) {
                   
                     {itemsArray?.length
-                      ? itemsArray?.length > 1
-                        ? `${itemsArray?.length} Endpoints`
+                      ? itemsArray.length > 1
+                        ? `${itemsArray.length} Endpoints`
                         : '1 Endpoint'
                       : 'No Endpoints'}
                   
diff --git a/thingconnect.pulse.client/src/components/status/StatusCard.tsx b/thingconnect.pulse.client/src/components/status/StatusCard.tsx
index 210df71..53eb350 100644
--- a/thingconnect.pulse.client/src/components/status/StatusCard.tsx
+++ b/thingconnect.pulse.client/src/components/status/StatusCard.tsx
@@ -19,6 +19,8 @@ export function StatusCard({ item }: StatusCardProps) {
         return 'red';
       case 'flapping':
         return 'yellow';
+      case 'service':
+        return 'yellow';
       default:
         return 'gray';
     }
@@ -77,15 +79,15 @@ export function StatusCard({ item }: StatusCardProps) {
             
 
             
-              {item.status}
+              {item.currentState.status}
             
           
 
@@ -105,10 +107,10 @@ export function StatusCard({ item }: StatusCardProps) {
             
-              {formatRTT(item.rttMs)}
+              {formatRTT(item.currentState.rttMs)}
             
           
 
diff --git a/thingconnect.pulse.client/src/components/status/StatusGroupAccordion.tsx b/thingconnect.pulse.client/src/components/status/StatusGroupAccordion.tsx
index dac70d2..ab220f3 100644
--- a/thingconnect.pulse.client/src/components/status/StatusGroupAccordion.tsx
+++ b/thingconnect.pulse.client/src/components/status/StatusGroupAccordion.tsx
@@ -3,11 +3,21 @@ import type { LiveStatusItem } from '@/api/types';
 import { StatusTable } from './StatusTable';
 
 type Props = {
-  groupedEndpoints: Record<'up' | 'down' | 'flapping', Record>;
+  groupedEndpoints: Record<
+    'up' | 'down' | 'flapping' | 'service',
+    Record
+  >;
   isLoading: boolean;
 };
 
 export function StatusGroupAccordion({ groupedEndpoints, isLoading }: Props) {
+  const statusColorMap: Record<'up' | 'down' | 'flapping' | 'service', string> = {
+    up: 'green',
+    down: 'red',
+    flapping: 'yellow',
+    service: 'orange',
+  } as const;
+
   return (
     
       {Object.entries(groupedEndpoints).map(([status, groupItems]) => {
@@ -15,11 +25,6 @@ export function StatusGroupAccordion({ groupedEndpoints, isLoading }: Props) {
           (sum, group) => sum + (group?.length || 0),
           0
         );
-        const statusColorMap: Record<'up' | 'down' | 'flapping', string> = {
-          up: 'green',
-          down: 'red',
-          flapping: 'yellow',
-        } as const;
 
         // Narrow the type for TypeScript
         const typedGroupItems = groupItems || {};
@@ -27,12 +32,12 @@ export function StatusGroupAccordion({ groupedEndpoints, isLoading }: Props) {
         return (
           
             
               
@@ -40,15 +45,15 @@ export function StatusGroupAccordion({ groupedEndpoints, isLoading }: Props) {
                   
                   
                     {totalEndpoints ? `${totalEndpoints} Endpoints` : 'No Endpoints'}
                   
diff --git a/thingconnect.pulse.client/src/components/status/StatusTable.tsx b/thingconnect.pulse.client/src/components/status/StatusTable.tsx
index a1e9303..777977e 100644
--- a/thingconnect.pulse.client/src/components/status/StatusTable.tsx
+++ b/thingconnect.pulse.client/src/components/status/StatusTable.tsx
@@ -5,6 +5,7 @@ import { useNavigate } from 'react-router-dom';
 import { useAnalytics } from '@/hooks/useAnalytics';
 import type { LiveStatusItem } from '@/api/types';
 import TrendBlocks from './TrendBlocks';
+import { Tooltip } from '../ui/tooltip';
 
 interface StatusTableProps {
   items: LiveStatusItem[] | null | undefined;
@@ -22,6 +23,8 @@ export function StatusTable({ items, isLoading }: StatusTableProps) {
       case 'down':
         return 'red';
       case 'flapping':
+        return 'orange';
+      case 'service':
         return 'yellow';
       default:
         return 'gray';
@@ -116,16 +119,35 @@ export function StatusTable({ items, isLoading }: StatusTableProps) {
                   onClick={() => handleRowClick(item.endpoint.id)}
                 >
                   
-                    
-                      {item.status.toUpperCase()}
-                    
+                    {item.currentState.status === 'service' ? (
+                      
+                        
+                          {item.currentState.status.toUpperCase()}
+                        
+                      
+                    ) : (
+                      
+                        {item.currentState.status.toUpperCase()}
+                      
+                    )}
                   
                   
                     {item.endpoint.name}
@@ -145,10 +167,10 @@ export function StatusTable({ items, isLoading }: StatusTableProps) {
                     
-                      {formatRTT(item.rttMs)}
+                      {formatRTT(item.currentState.rttMs)}
                     
                   
                   
diff --git a/thingconnect.pulse.client/src/components/status/SystemOverviewStats.tsx b/thingconnect.pulse.client/src/components/status/SystemOverviewStats.tsx
index 4f67781..530f389 100644
--- a/thingconnect.pulse.client/src/components/status/SystemOverviewStats.tsx
+++ b/thingconnect.pulse.client/src/components/status/SystemOverviewStats.tsx
@@ -19,6 +19,7 @@ type SystemOverviewStatsProps = {
     up: number;
     down: number;
     flapping: number;
+    service: number;
   };
 };
 
@@ -68,10 +69,21 @@ export function SystemOverviewStats({ statusCounts }: SystemOverviewStatsProps)
       darkColor: 'yellow.200',
       darkBg: 'yellow.800',
     },
+    {
+      icon: Activity,
+      title: 'SERVICE',
+      subtitle: 'TCP/HTTP down, ICMP reachable',
+      value: statusCounts.service,
+      textColor: 'purple.500',
+      color: 'purple.600',
+      bg: 'purple.100',
+      darkColor: 'purple.200',
+      darkBg: 'purple.800',
+    },
   ];
 
   return (
-    
+    
       {stats.map(stat => (
         
           
diff --git a/thingconnect.pulse.client/src/icons/Discord.tsx b/thingconnect.pulse.client/src/icons/Discord.tsx
new file mode 100644
index 0000000..abe2bc6
--- /dev/null
+++ b/thingconnect.pulse.client/src/icons/Discord.tsx
@@ -0,0 +1,16 @@
+import React from 'react';
+
+export function Discord(props: React.SVGProps) {
+  return (
+    
+  );
+}
diff --git a/thingconnect.pulse.client/src/pages/About.tsx b/thingconnect.pulse.client/src/pages/About.tsx
index 9ad68fc..af09136 100644
--- a/thingconnect.pulse.client/src/pages/About.tsx
+++ b/thingconnect.pulse.client/src/pages/About.tsx
@@ -30,6 +30,7 @@ import {
 import { PageHeader } from '@/components/layout/PageHeader';
 import { useForceRefreshNotifications, useNotificationStats } from '@/hooks/useNotifications';
 import thingConnectLogo from '@/assets/thingconnect-pulse-logo.svg';
+import { Discord } from '@/icons/Discord';
 
 export default function About() {
   const { data: stats } = useNotificationStats();
@@ -122,7 +123,7 @@ export default function About() {
                 
                   {[
                     {
-                      icon: MessageCircle,
+                      icon: Discord,
                       title: 'Discord',
                       desc: 'Community support and real-time help',
                       tags: ['Community Support', 'Q&A', 'General Chat', 'Networking'],
@@ -133,7 +134,7 @@ export default function About() {
                       title: 'Reddit',
                       desc: 'Share questions and experiences',
                       tags: ['Discussions', 'Tips', 'Troubleshooting'],
-                      link: 'https://reddit.com',
+                      link: 'https://www.reddit.com/r/thingconnectio/',
                     },
                     {
                       icon: Linkedin,
@@ -340,7 +341,10 @@ export default function About() {
                         
                           Unread Count:
                         
-                        
+                        
                           {stats?.unreadNotifications || 0}
                         
                       
@@ -350,7 +354,9 @@ export default function About() {
                           Last Sync:
                         
                         
-                          {stats?.lastFetch ? new Date(stats.lastFetch).toLocaleDateString() : 'Never'}
+                          {stats?.lastFetch
+                            ? new Date(stats.lastFetch).toLocaleDateString()
+                            : 'Never'}
                         
                       
 
@@ -389,7 +395,8 @@ export default function About() {
                     
 
                     
-                      Notifications are automatically synced every 6 hours. Use the button below to trigger an immediate refresh.
+                      Notifications are automatically synced every 6 hours. Use the button below to
+                      trigger an immediate refresh.
                     
 
                     
@@ -405,18 +412,33 @@ export default function About() {
                       
 
                       {refreshMutation.isSuccess && (
-                        
+                        
                           Notifications refreshed successfully!
                         
                       )}
 
                       {refreshMutation.isError && (
-                        
+                        
                           Failed to refresh notifications. Please try again.
                         
                       )}
 
-                      
+                      
                         Syncs from: thingconnect-pulse.s3.ap-south-1.amazonaws.com
                       
                     
diff --git a/thingconnect.pulse.client/src/pages/Dashboard.tsx b/thingconnect.pulse.client/src/pages/Dashboard.tsx
index c1d3950..dcb16e3 100644
--- a/thingconnect.pulse.client/src/pages/Dashboard.tsx
+++ b/thingconnect.pulse.client/src/pages/Dashboard.tsx
@@ -53,10 +53,10 @@ export default function Dashboard() {
       const statusCounts = data.items.reduce(
         (acc, item) => {
           acc.total++;
-          acc[item.status]++;
+          acc[item.currentState.status]++;
           return acc;
         },
-        { total: 0, up: 0, down: 0, flapping: 0 }
+        { total: 0, up: 0, down: 0, flapping: 0, service: 0 }
       );
 
       analytics.trackSystemMetrics({
@@ -99,17 +99,26 @@ export default function Dashboard() {
 
     if (isGroupByStatus && isGroupByGroup) {
       // Status โ Group โ Endpoints
-      const statusBuckets: Record<'up' | 'down' | 'flapping', Record> = {
+      const statusBuckets: Record<
+        'up' | 'down' | 'flapping' | 'service',
+        Record
+      > = {
         up: {},
         down: {},
         flapping: {},
+        service: {},
       };
 
       // Get unique groups from all endpoints
       const uniqueGroups = new Set(filteredItems.map(item => item.endpoint.group.name));
 
       // Prepare status buckets with all groups, even if empty
-      const defaultStatuses: Array<'up' | 'down' | 'flapping'> = ['up', 'down', 'flapping'];
+      const defaultStatuses: Array<'up' | 'down' | 'flapping' | 'service'> = [
+        'up',
+        'down',
+        'flapping',
+        'service',
+      ];
       defaultStatuses.forEach(status => {
         statusBuckets[status] = {};
         uniqueGroups.forEach(group => {
@@ -119,7 +128,7 @@ export default function Dashboard() {
 
       // Populate the status buckets
       filteredItems.forEach(item => {
-        const status = item.status;
+        const status = item.currentState.status;
         const group = item.endpoint.group.name;
 
         statusBuckets[status][group].push(item);
@@ -145,18 +154,24 @@ export default function Dashboard() {
       finalResult = groupBuckets;
     } else if (isGroupByStatus) {
       // Status โ Endpoints
-      const statusBuckets: Record<'up' | 'down' | 'flapping', LiveStatusItem[]> = {
+      const statusBuckets: Record<'up' | 'down' | 'flapping' | 'service', LiveStatusItem[]> = {
         up: [],
         down: [],
         flapping: [],
+        service: [],
       };
 
       filteredItems.forEach(item => {
-        statusBuckets[item.status].push(item);
+        statusBuckets[item.currentState.status].push(item);
       });
 
       // Always include all statuses, even if empty
-      const defaultStatuses: Array<'up' | 'down' | 'flapping'> = ['up', 'down', 'flapping'];
+      const defaultStatuses: Array<'up' | 'down' | 'flapping' | 'service'> = [
+        'up',
+        'down',
+        'flapping',
+        'service',
+      ];
       defaultStatuses.forEach(status => {
         finalResult[status] = statusBuckets[status];
       });
@@ -183,15 +198,15 @@ export default function Dashboard() {
 
   // Count status totals
   const statusCounts = useMemo(() => {
-    if (!data?.items) return { total: 0, up: 0, down: 0, flapping: 0 };
+    if (!data?.items) return { total: 0, up: 0, down: 0, flapping: 0, service: 0 };
 
     const counts = data.items.reduce(
       (acc, item) => {
         acc.total++;
-        acc[item.status]++;
+        acc[item.currentState.status]++;
         return acc;
       },
-      { total: 0, up: 0, down: 0, flapping: 0 }
+      { total: 0, up: 0, down: 0, flapping: 0, service: 0 }
     );
 
     return counts;
diff --git a/thingconnect.pulse.client/src/pages/EndpointDetail.tsx b/thingconnect.pulse.client/src/pages/EndpointDetail.tsx
index ab76fbf..199d8fa 100644
--- a/thingconnect.pulse.client/src/pages/EndpointDetail.tsx
+++ b/thingconnect.pulse.client/src/pages/EndpointDetail.tsx
@@ -38,6 +38,7 @@ import { Skeleton } from '@/components/ui/skeleton';
 import { EmptyState } from '@/components/ui/empty-state';
 import { RecentChecksTable } from '@/components/RecentChecksTable';
 import { OutagesList } from '@/components/OutageList';
+import { PageSection } from '@/components/layout/PageSection';
 
 function getStatusColor(status: string) {
   switch (status.toLowerCase()) {
@@ -46,6 +47,8 @@ function getStatusColor(status: string) {
     case 'down':
       return 'red';
     case 'flapping':
+      return 'yellow';
+    case 'service':
       return 'orange';
     default:
       return 'gray';
@@ -60,6 +63,8 @@ function getStatusIcon(status: string) {
       return ArrowDown;
     case 'flapping':
       return AlertTriangle;
+    case 'service':
+      return Activity;
     default:
       return Circle;
   }
@@ -107,7 +112,7 @@ export default function EndpointDetail() {
 
   const backButton = (
     
-