@@ -289,6 +289,37 @@ public function testBuildReusesForecastAsSyntheticDataForLaterNoDataDaysAndRecal
289289 self ::assertSame ([[null , null , null , null , 47.9996 , 58.3997 , 74.7198 , 59.9994 ]], $ forecastData );
290290 }
291291
292+ public function testBuildMonotonicReturnsFullPriorWhenIncompleteTickHasNoData (): void
293+ {
294+ $ site = $ this ->createSiteMock ();
295+
296+ // The "today" tick exists in the date range but has no archived data yet — currentValue
297+ // is 0 and there is no earlier incomplete tick to carry a forecast forward from. The
298+ // blend would otherwise dilute the historical prior with a meaningless zero; the builder
299+ // must return the full prior mean instead. Apr 3/10/17/24 are all Fridays so the daily
300+ // weekday filter keeps every prior tick in the sample.
301+ $ dataTables = [
302+ $ this ->createDataTableForDay ('2026-04-03 ' , $ site ),
303+ $ this ->createDataTableForDay ('2026-04-10 ' , $ site ),
304+ $ this ->createDataTableForDay ('2026-04-17 ' , $ site ),
305+ $ this ->createDataTableForDay ('2026-04-24 ' , $ site , '2026-04-24 00:00:00 ' ),
306+ ];
307+
308+ $ forecastData = (new ForecastBuilder ())->build (
309+ ['Visits ' => [80.0 , 100.0 , 60.0 , 0.0 ]],
310+ $ dataTables ,
311+ [
312+ ArchiveState::COMPLETE ,
313+ ArchiveState::COMPLETE ,
314+ ArchiveState::COMPLETE ,
315+ ArchiveState::INCOMPLETE ,
316+ ],
317+ ['Visits ' => false ]
318+ );
319+
320+ self ::assertSame ([[null , null , null , 80.0 ]], $ forecastData );
321+ }
322+
292323 public function testBuildDoesNotCarryForwardAcrossSuppressedForecastGap (): void
293324 {
294325 $ site = $ this ->createSiteMock ();
0 commit comments