diff --git a/src/Agent/AutopilotMonitor.Agent.V2.Core.Tests/Monitoring/Office/OfficeInstallDetectorTests.cs b/src/Agent/AutopilotMonitor.Agent.V2.Core.Tests/Monitoring/Office/OfficeInstallDetectorTests.cs
index 6193c5db..85fc2ca0 100644
--- a/src/Agent/AutopilotMonitor.Agent.V2.Core.Tests/Monitoring/Office/OfficeInstallDetectorTests.cs
+++ b/src/Agent/AutopilotMonitor.Agent.V2.Core.Tests/Monitoring/Office/OfficeInstallDetectorTests.cs
@@ -292,6 +292,8 @@ namespace AutopilotMonitor.Agent.V2.Core.Tests.Monitoring.Office
Assert.Equal(0L, summary["bytesFromPeers"]);
Assert.Equal(80, summary["percentFromCacheServer"]);
Assert.Equal(20, summary["percentFromCdn"]);
+ Assert.Equal(83, summary["downloadPercent"]); // 1000 of 1200 declared
+ Assert.Equal(true, summary["streamingFinished"]);
}
[Fact]
@@ -544,6 +546,8 @@ namespace AutopilotMonitor.Agent.V2.Core.Tests.Monitoring.Office
Assert.Equal(1000L, summary["totalBytesDownloaded"]);
Assert.Equal(800L, summary["bytesFromCacheServer"]);
Assert.Equal(200L, summary["bytesFromCdn"]);
+ Assert.Equal(83, summary["downloadPercent"]);
+ Assert.Equal(true, summary["streamingFinished"]);
Assert.Equal(OfficeInstallStateData.StateCompleted, rig.Persistence!.Load()!.State);
}
diff --git a/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/Office/OfficeInstallDetector.cs b/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/Office/OfficeInstallDetector.cs
index f9147253..e9a5c52f 100644
--- a/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/Office/OfficeInstallDetector.cs
+++ b/src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/Office/OfficeInstallDetector.cs
@@ -411,7 +411,7 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.Office
data["coreBinariesPresent"] = coreBinariesPresent;
// One-time Delivery-Optimization summary (how Office was delivered) — far more useful than
// a streaming download-% (which is noise under Connected-Cache + multi-job churn).
- if (_peakDo != null) data["doSummary"] = BuildDoSummary(_peakDo);
+ if (_peakDo != null) data["doSummary"] = BuildDoSummary(_peakDo, snap.StreamingFinished);
}
var message = BuildMessage(eventType, snap);
@@ -467,8 +467,14 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.Office
/// highest-bytes sample seen. Splits the bytes into Connected-Cache-Server / peer / pure-CDN: on
/// MCC-enabled networks BytesFromHttp includes the cache-server bytes, so pure CDN is
/// BytesFromHttp - BytesFromCacheServer (see project DO-telemetry notes).
+ ///
+ /// aggregateFileSize is the sum of the DO jobs' declared target sizes at the peak
+ /// instant — NOT a stable "total Office payload". It commonly exceeds totalBytesDownloaded
+ /// because the completed event fires at core-binary completion while background streaming of the
+ /// remaining declared content is still in flight (streamingFinished == false). The mirrored
+ /// downloadPercent + streamingFinished make that gap self-explanatory.
///
- private static Dictionary BuildDoSummary(OfficeDoSample d)
+ private static Dictionary BuildDoSummary(OfficeDoSample d, bool streamingFinished)
{
long total = d.TotalBytesDownloaded;
long cacheServer = d.BytesFromCacheServer;
@@ -480,6 +486,8 @@ namespace AutopilotMonitor.Agent.V2.Core.Monitoring.Telemetry.Office
{
{ "totalBytesDownloaded", total },
{ "aggregateFileSize", d.FileSize },
+ { "downloadPercent", (object?)d.DownloadPercent ?? 0 },
+ { "streamingFinished", streamingFinished },
{ "jobCount", d.JobCount },
{ "bytesFromCacheServer", cacheServer },
{ "bytesFromPeers", peers },