Skip to content

Commit 195f8bc

Browse files
committed
Apply Metrics::isLowerValueBetter to forecast values
1 parent d95c450 commit 195f8bc

4 files changed

Lines changed: 320 additions & 61 deletions

File tree

plugins/CoreVisualizations/JqplotDataGenerator/Evolution.php

Lines changed: 103 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ protected function initChartObjectData($dataTable, $visualization)
8181
// collect series data to show. each row-to-display/column-to-display permutation creates a series.
8282
$allSeriesData = [];
8383
$allSeriesDataAvailability = [];
84+
$allSeriesAllowsDownwardForecast = [];
8485
foreach ($rowsToDisplay as $rowIdentifier) {
8586
$rowLabel = $rowIdentifier;
8687

@@ -93,10 +94,32 @@ protected function initChartObjectData($dataTable, $visualization)
9394
}
9495

9596
foreach ($columnsToDisplay as $columnName) {
97+
$columnAllowsDownwardForecast = $this->columnAllowsDownwardForecast(
98+
$columnName,
99+
$units[$columnName] ?? false
100+
);
101+
96102
if (!$this->isComparing) {
97-
$this->setNonComparisonSeriesData($allSeriesData, $allSeriesDataAvailability, $rowLabel, $columnName, $dataTable);
103+
$this->setNonComparisonSeriesData(
104+
$allSeriesData,
105+
$allSeriesDataAvailability,
106+
$allSeriesAllowsDownwardForecast,
107+
$rowLabel,
108+
$columnName,
109+
$columnAllowsDownwardForecast,
110+
$dataTable
111+
);
98112
} else {
99-
$this->setComparisonSeriesData($allSeriesData, $allSeriesDataAvailability, $seriesLabels, $rowLabel, $columnName, $dataTable);
113+
$this->setComparisonSeriesData(
114+
$allSeriesData,
115+
$allSeriesDataAvailability,
116+
$allSeriesAllowsDownwardForecast,
117+
$seriesLabels,
118+
$rowLabel,
119+
$columnName,
120+
$columnAllowsDownwardForecast,
121+
$dataTable
122+
);
100123
}
101124
}
102125
}
@@ -144,7 +167,14 @@ protected function initChartObjectData($dataTable, $visualization)
144167

145168
$dataStates = $this->setDataStates($visualization, $dataTables);
146169
$visualization->setForecastData(
147-
$this->buildForecastData($allSeriesData, $dataTables, $dataStates, $seriesUnits, $allSeriesDataAvailability)
170+
$this->buildForecastData(
171+
$allSeriesData,
172+
$dataTables,
173+
$dataStates,
174+
$seriesUnits,
175+
$allSeriesDataAvailability,
176+
$allSeriesAllowsDownwardForecast
177+
)
148178
);
149179
}
150180

@@ -154,14 +184,16 @@ protected function initChartObjectData($dataTable, $visualization)
154184
* @param array<int, string> $dataStates
155185
* @param array<string, string|false> $seriesUnits
156186
* @param array<string, array<int, bool>> $allSeriesDataAvailability
187+
* @param array<string, bool> $allSeriesAllowsDownwardForecast
157188
* @return array<int, array<int, float|null>>
158189
*/
159190
protected function buildForecastData(
160191
array $allSeriesData,
161192
array $dataTables,
162193
array $dataStates,
163194
array $seriesUnits,
164-
array $allSeriesDataAvailability
195+
array $allSeriesDataAvailability,
196+
array $allSeriesAllowsDownwardForecast
165197
): array {
166198
if (empty($this->properties['show_forecast']) || $this->isComparing) {
167199
return [];
@@ -176,7 +208,33 @@ protected function buildForecastData(
176208
}
177209
}
178210

179-
return (new ForecastBuilder())->build($allSeriesData, $dataTables, $dataStates, $seriesUnits, $allSeriesDataAvailability);
211+
return (new ForecastBuilder())->build(
212+
$allSeriesData,
213+
$dataTables,
214+
$dataStates,
215+
$seriesUnits,
216+
$allSeriesDataAvailability,
217+
$allSeriesAllowsDownwardForecast
218+
);
219+
}
220+
221+
/**
222+
* Whether forecasts for a given column may legitimately fall below the current partial value.
223+
*
224+
* Counts (visits, conversions, etc.) only grow within an incomplete period, so their forecast
225+
* is gated by the "forecast >= current" rule. Ratios and lower-is-better metrics (bounce rate,
226+
* exit rate, average page generation time, position) can move either way during the period
227+
* and need the gate lifted, otherwise valid downward trends are silently suppressed.
228+
*
229+
* @param string|false $columnUnit
230+
*/
231+
private function columnAllowsDownwardForecast(string $columnName, $columnUnit): bool
232+
{
233+
if ($columnUnit === '%') {
234+
return true;
235+
}
236+
237+
return Metrics::isLowerValueBetter($columnName);
180238
}
181239

182240
private function getSeriesData($rowLabel, $columnName, DataTable\Map $dataTable, &$seriesDataAvailability)
@@ -295,17 +353,33 @@ protected function addSelectedSeriesXLabels(array &$xLabels, array $dataTables)
295353
}
296354
}
297355

298-
private function setNonComparisonSeriesData(array &$allSeriesData, array &$allSeriesDataAvailability, $rowLabel, $columnName, DataTable\Map $dataTable)
299-
{
356+
private function setNonComparisonSeriesData(
357+
array &$allSeriesData,
358+
array &$allSeriesDataAvailability,
359+
array &$allSeriesAllowsDownwardForecast,
360+
$rowLabel,
361+
$columnName,
362+
bool $columnAllowsDownwardForecast,
363+
DataTable\Map $dataTable
364+
) {
300365
$seriesLabel = $this->getSeriesLabel($rowLabel, $columnName);
301366

302367
$seriesData = $this->getSeriesData($rowLabel, $columnName, $dataTable, $seriesDataAvailability);
303368
$allSeriesData[$seriesLabel] = $seriesData;
304369
$allSeriesDataAvailability[$seriesLabel] = $seriesDataAvailability;
370+
$allSeriesAllowsDownwardForecast[$seriesLabel] = $columnAllowsDownwardForecast;
305371
}
306372

307-
private function setComparisonSeriesData(array &$allSeriesData, array &$allSeriesDataAvailability, array $seriesLabels, $rowLabel, $columnName, DataTable\Map $dataTable)
308-
{
373+
private function setComparisonSeriesData(
374+
array &$allSeriesData,
375+
array &$allSeriesDataAvailability,
376+
array &$allSeriesAllowsDownwardForecast,
377+
array $seriesLabels,
378+
$rowLabel,
379+
$columnName,
380+
bool $columnAllowsDownwardForecast,
381+
DataTable\Map $dataTable
382+
) {
309383
foreach ($dataTable->getDataTables() as $label => $childTable) {
310384
// get the row for this label (use the first if $rowLabel is false)
311385
if ($rowLabel === false) {
@@ -322,6 +396,7 @@ private function setComparisonSeriesData(array &$allSeriesData, array &$allSerie
322396
$wholeSeriesLabel = $this->getComparisonSeriesLabelFromCompareSeries($seriesLabelPrefix, $columnName, $rowLabel);
323397
$allSeriesData[$wholeSeriesLabel][] = 0;
324398
$allSeriesDataAvailability[$wholeSeriesLabel][] = false;
399+
$allSeriesAllowsDownwardForecast[$wholeSeriesLabel] = $columnAllowsDownwardForecast;
325400
}
326401

327402
continue;
@@ -334,6 +409,7 @@ private function setComparisonSeriesData(array &$allSeriesData, array &$allSerie
334409
$value = $compareRow->getColumn($columnName);
335410
$allSeriesData[$seriesLabel][] = $value;
336411
$allSeriesDataAvailability[$seriesLabel][] = $this->hasColumnValue($value);
412+
$allSeriesAllowsDownwardForecast[$seriesLabel] = $columnAllowsDownwardForecast;
337413
}
338414

339415
$totalsRow = $comparisonTable->getTotalsRow();
@@ -342,6 +418,7 @@ private function setComparisonSeriesData(array &$allSeriesData, array &$allSerie
342418
$value = $totalsRow->getColumn($columnName);
343419
$allSeriesData[$seriesLabel][] = $value;
344420
$allSeriesDataAvailability[$seriesLabel][] = $this->hasColumnValue($value);
421+
$allSeriesAllowsDownwardForecast[$seriesLabel] = $columnAllowsDownwardForecast;
345422
}
346423
}
347424
}
@@ -487,6 +564,7 @@ public function precomputeForecast($dataTable): array
487564

488565
$allSeriesData = [];
489566
$allSeriesDataAvailability = [];
567+
$allSeriesAllowsDownwardForecast = [];
490568
foreach ($rowsToDisplay as $rowIdentifier) {
491569
$rowLabel = $rowIdentifier;
492570

@@ -499,7 +577,20 @@ public function precomputeForecast($dataTable): array
499577
}
500578

501579
foreach ($columnsToDisplay as $columnName) {
502-
$this->setNonComparisonSeriesData($allSeriesData, $allSeriesDataAvailability, $rowLabel, $columnName, $dataTable);
580+
$columnAllowsDownwardForecast = $this->columnAllowsDownwardForecast(
581+
$columnName,
582+
$units[$columnName] ?? false
583+
);
584+
585+
$this->setNonComparisonSeriesData(
586+
$allSeriesData,
587+
$allSeriesDataAvailability,
588+
$allSeriesAllowsDownwardForecast,
589+
$rowLabel,
590+
$columnName,
591+
$columnAllowsDownwardForecast,
592+
$dataTable
593+
);
503594
}
504595
}
505596

@@ -508,7 +599,8 @@ public function precomputeForecast($dataTable): array
508599
$dataTables,
509600
$dataStates,
510601
$seriesUnits,
511-
$allSeriesDataAvailability
602+
$allSeriesDataAvailability,
603+
$allSeriesAllowsDownwardForecast
512604
);
513605
}
514606
}

0 commit comments

Comments
 (0)