Skip to content

Commit e27b4c9

Browse files
committed
Allow forecast if no data is flagged as incomplete
1 parent 00ab34e commit e27b4c9

2 files changed

Lines changed: 37 additions & 1 deletion

File tree

plugins/CoreVisualizations/JqplotDataGenerator/ForecastBuilder.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,10 @@ public function build(
153153
/**
154154
* Forecast for monotonic count series: linear elapsed-ratio extrapolation blended with the
155155
* historical same-period prior. Carries the previous forecast forward when the current tick
156-
* has no positive value so a synthetic 0 does not collapse the linear seed to zero.
156+
* has no positive value so a synthetic 0 does not collapse the linear seed to zero. When the
157+
* current tick is the first incomplete tick (no previous forecast) and historical priors
158+
* exist, the blend would otherwise dilute the prior with a meaningless zero, so it falls back
159+
* to the prior mean directly.
157160
*
158161
* @param array<int, float> $pastValues
159162
*/
@@ -179,6 +182,8 @@ private function buildMonotonicForecastValue(
179182

180183
if ($currentValue <= 0 && $previousForecastValue !== null) {
181184
$baseForecast = $previousForecastValue;
185+
} elseif ($currentValue <= 0 && [] !== $pastValues) {
186+
$baseForecast = $priorForecast;
182187
}
183188

184189
$forecastValue = $this->blendForecastValue($baseForecast, $priorForecast, $weight);

plugins/CoreVisualizations/tests/Unit/JqplotDataGenerator/ForecastBuilderTest.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)