Skip to content

Commit

Permalink
Fix virtual interpreter average
Browse files Browse the repository at this point in the history
  • Loading branch information
andig committed Mar 13, 2019
1 parent 9653b54 commit ca0a2e5
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 5 deletions.
2 changes: 1 addition & 1 deletion lib/Interpreter/DataIterator.php
Expand Up @@ -70,7 +70,7 @@ public function __construct(\Traversable $stmt, $rowCount, $tupleCount) {
}

// skipping first reading, just for getting first timestamp, value is remembered
list($this->from, $this->firstValue, $foo) = $this->stmt->fetch();
list($this->from, $this->firstValue, $foo) = $this->stmt->fetch();

// ensure valid data range if we have 1 row only (not using iterator then)
if ($this->from) $this->to = $this->from;
Expand Down
2 changes: 1 addition & 1 deletion lib/Interpreter/VirtualInterpreter.php
Expand Up @@ -283,7 +283,7 @@ public function getAverage() {
}

if ($this->output == self::ACTUAL_VALUES) {
$delta = $this->getTo() - $this->getFrom();
$delta = ($this->getTo() - $this->getFrom()) / 3.6e6;
return $this->consumption / $delta;
}
else {
Expand Down
93 changes: 90 additions & 3 deletions test/VirtualTest.php
Expand Up @@ -200,7 +200,7 @@ function testVirtualChannel($styles, $inputs) {

// expected timestamps
$timestamps = $this->extractUniqueTimestamps($data);
$from = array_shift($timestamps);
$from = array_shift($timestamps); // first timestamp

// expected values
$values = array();
Expand All @@ -213,6 +213,7 @@ function testVirtualChannel($styles, $inputs) {

$values[] = array($ts, $in1v - $in2v, 1);
}
$to = $values[count($values)-1][0]; // last timestamp

// add input values
$this->addData($data);
Expand All @@ -225,6 +226,87 @@ function testVirtualChannel($styles, $inputs) {
// has no access to very first database row consumed by DataIterator
$this->assertEquals(array_splice($values, 2), array_splice($tuples, 2));
$this->assertEquals($from, $vi->getFrom());
$this->assertEquals($to, $vi->getTo());

// delete
foreach ([$in1, $in2, $out] as $uuid) {
// $url = '/channel/' . $uuid . '.json?operation=delete';
// $this->getJson($url);
$this->deleteChannel($uuid);
}
}

/**
* Identical test as before but checks for consumption and average values
*
* @dataProvider virtualInputProvider
* @depends testVirtualChannel
*/
function testVirtualChannelConsumption($styles, $inputs) {
// create channel, style=lines forces STRATEGY_TS_AFTER
$in1 = $this->createChannel('Sensor', 'powersensor', ['style' => $styles[0]]);
$in2 = $this->createChannel('Sensor', 'powersensor', ['style' => $styles[1]]);
$out = $this->createChannel('Virtual', 'virtualconsumption', [
'unit' => 'foo',
'in1' => $in1,
'in2' => $in2,
'rule' => 'in1()-in2()'
]);

$this->assertTrue(isset($this->json->entity), "Expected <entity> got none.");
$this->assertTrue(isset($this->json->entity->uuid));

$data = [
$in1 => $this->getSeriesData($inputs[0]),
$in2 => $this->getSeriesData($inputs[1])
];

// expected timestamps
$timestamps = $this->extractUniqueTimestamps($data);
$from = array_shift($timestamps);

// expected values
$values = array();
foreach ($timestamps as $ts) {
$func = array($this, $styles[0] == 'states' ? 'getValueBefore' : 'getValueAfter');
$in1v = $func($data[$in1], $ts);

$func = array($this, $styles[1] == 'states' ? 'getValueBefore' : 'getValueAfter');
$in2v = $func($data[$in2], $ts);

$values[] = array($ts, $in1v - $in2v, 1);
}
$to = $values[count($values)-1][0]; // last timestamp

// add input values
$this->addData($data);

// get result
$vi = $this->createInterpreter($out, 1, 'now', null, null);
$tuples = $this->getInterpreterResult($vi);

// weighed average
$last_ts = $from;
$consumption = (array_reduce($values, function ($carry, $tuple) use (&$last_ts) {
if ($tuple[0] > $last_ts) {
$carry += $tuple[1] * ($tuple[0] - $last_ts) / 3.6e6;
$last_ts = $tuple[0];
}
return $carry;
}));

// weighed average
$average = $consumption / ($to - $from) * 3.6e6;

// omit first 2 timestamps from assertion since VirtualInterpreter
// has no access to very first database row consumed by DataIterator
$this->assertEquals(array_splice($values, 2), array_splice($tuples, 2));

// @todo verify consumption calculation for 'states'
if ($styles[0] != 'states') {
$this->assertEquals($consumption, $vi->getConsumption(), "Consumption mismatch");
$this->assertEquals($average, $vi->getAverage(), "Average mismatch");
}

// delete
foreach ([$in1, $in2, $out] as $uuid) {
Expand All @@ -234,7 +316,7 @@ function testVirtualChannel($styles, $inputs) {
}
}

function testVirtualChannelConsumption() {
function testVirtualChannelGroupedConsumption() {
// create
$in1 = $this->createChannel('Sensor', 'powersensor');
$out = $this->createChannel('Virtual', 'virtualconsumption', [
Expand Down Expand Up @@ -271,12 +353,17 @@ function testVirtualChannelConsumption() {
}, array_keys($data[$in1]));
array_shift($values);

// in consumption mode the total consumption is the sum of period consumptions
$consumption = (array_reduce($values, function($carry, $tuple) {
return $carry + $tuple[1];
}));

// in consumption mode the average is the total divided by number of periods
$average = $consumption / (count($series) - 1);

$this->assertEquals(array_splice($values, 0), array_splice($tuples, 0));
$this->assertEquals($consumption, $vi->getConsumption());
$this->assertEquals($consumption, $vi->getConsumption(), "Consumption mismatch");
$this->assertEquals($average, $vi->getAverage(), "Average mismatch");

// partial consumption
$vi = $this->createInterpreter($out, 0, 2 * 3600 * 1000, null, 'hour', ['consumption']);
Expand Down

0 comments on commit ca0a2e5

Please sign in to comment.