1
0
mirror of https://github.com/beestat/app.git synced 2025-05-24 02:14:03 -04:00

Updated profile generation; added some metrics.

This commit is contained in:
Jon Ziebell 2020-02-27 20:09:11 -05:00
parent a210487ca6
commit 154af5d89f
3 changed files with 241 additions and 11 deletions

View File

@ -50,7 +50,7 @@ function array_median($array) {
}
// Useful function
function array_average($array) {
function array_mean($array) {
if (count($array) === 0) {
return null;
}

View File

@ -177,6 +177,16 @@ class profile extends cora\api {
'heat' => [],
'cool' => []
];
$runtime_seconds = [
'heat_1' => 0,
'heat_2' => 0,
'auxiliary_heat_1' => 0,
'auxiliary_heat_2' => 0,
'cool_1' => 0,
'cool_2' => 0
];
$degree_days_baseline = 65;
$degree_days = [];
$begin_runtime = [];
while($current_timestamp <= $end_timestamp) {
@ -213,22 +223,26 @@ class profile extends cora\api {
// consistently represented instead of having to do this logic
// throughout the generator.
$runtime = [];
$degree_days_date = date('Y-m-d', $current_timestamp);
$degree_days_temperatures = [];
while($row = $result->fetch_assoc()) {
$timestamp = strtotime($row['timestamp']);
$hour = date('G', $timestamp);
$date = date('Y-m-d', $timestamp);
if (
$ignore_solar_heating === true &&
$hour > 6 &&
$hour < 22
) {
continue;
// Degree days
if($date !== $degree_days_date) {
$degree_days[] = (array_mean($degree_days_temperatures) / 10) - $degree_days_baseline;
$degree_days_date = $date;
$degree_days_temperatures = [];
}
$degree_days_temperatures[] = $row['outdoor_temperature'];
if($first_timestamp === null) {
$first_timestamp = $row['timestamp'];
}
// Normalizing heating and cooling a bit.
if(
$thermostat['system_type']['detected']['heat'] === 'compressor' ||
$thermostat['system_type']['detected']['heat'] === 'geothermal'
@ -255,6 +269,21 @@ class profile extends cora\api {
$row['cool_2'] = 0;
}
$runtime_seconds['heat_1'] += $row['heat_1'];
$runtime_seconds['heat_2'] += $row['heat_2'];
$runtime_seconds['auxiliary_heat_1'] += $row['auxiliary_heat_1'];
$runtime_seconds['auxiliary_heat_2'] += $row['auxiliary_heat_2'];
$runtime_seconds['cool_1'] += $row['cool_1'];
$runtime_seconds['cool_2'] += $row['cool_2'];
if (
$ignore_solar_heating === true &&
$hour > 6 &&
$hour < 22
) {
continue;
}
if (isset($runtime[$timestamp]) === false) {
$runtime[$timestamp] = [];
}
@ -630,8 +659,6 @@ class profile extends cora\api {
$current_timestamp += $five_minutes;
}
// print_r($samples);
// Process the samples
$deltas_raw = [];
foreach($samples as $sample) {
@ -670,6 +697,18 @@ class profile extends cora\api {
'heat' => null,
'cool' => null
],
'degree_days' => [
'heat' => null,
'cool' => null
],
'runtime' => [
'heat_1' => round($runtime_seconds['heat_1'] / 3600),
'heat_2' => round($runtime_seconds['heat_2'] / 3600),
'auxiliary_heat_1' => round($runtime_seconds['auxiliary_heat_1'] / 3600),
'auxiliary_heat_2' => round($runtime_seconds['auxiliary_heat_2'] / 3600),
'cool_1' => round($runtime_seconds['cool_1'] / 3600),
'cool_2' => round($runtime_seconds['cool_2'] / 3600),
],
'metadata' => [
'generated_at' => date('c'),
'duration' => round((time() - strtotime($first_timestamp)) / 86400),
@ -730,11 +769,27 @@ class profile extends cora\api {
foreach(['heat', 'cool'] as $type) {
if(count($setpoints[$type]) > 0) {
$profile['setpoint'][$type] = round(array_average($setpoints[$type])) / 10;
$profile['setpoint'][$type] = round(array_mean($setpoints[$type])) / 10;
$profile['metadata']['setpoint'][$type]['samples'] = count($setpoints[$type]);
}
}
// Heating and cooling degree days.
foreach($degree_days as $degree_day) {
if($degree_day < 0) {
$profile['degree_days']['cool'] += ($degree_day * -1);
} else {
$profile['degree_days']['heat'] += ($degree_day);
}
}
if ($profile['degree_days']['cool'] !== null) {
$profile['degree_days']['cool'] = round($profile['degree_days']['cool']);
}
if ($profile['degree_days']['heat'] !== null) {
$profile['degree_days']['heat'] = round($profile['degree_days']['heat']);
}
return $profile;
}

View File

@ -17,6 +17,7 @@ class thermostat_group extends cora\crud {
'generate_profiles',
'generate_profile',
'get_scores',
'get_metrics',
'update_system_types'
],
'public' => []
@ -27,7 +28,8 @@ class thermostat_group extends cora\crud {
'generate_temperature_profiles' => 604800, // 7 Days
'generate_profile' => 604800, // 7 Days
'generate_profiles' => 604800, // 7 Days
'get_scores' => 604800 // 7 Days
'get_scores' => 604800, // 7 Days
'get_metrics' => 604800 // 7 Days
];
/**
@ -75,6 +77,18 @@ class thermostat_group extends cora\crud {
'heat' => null,
'cool' => null
],
'degree_days' => [
'heat' => null,
'cool' => null
],
'runtime' => [
'heat_1' => 0,
'heat_2' => 0,
'auxiliary_heat_1' => 0,
'auxiliary_heat_2' => 0,
'cool_1' => 0,
'cool_2' => 0
],
'metadata' => [
'generated_at' => date('c'),
'duration' => null,
@ -151,6 +165,22 @@ class thermostat_group extends cora\crud {
}
}
}
// Degree days.
if($profile['degree_days']['heat'] !== null) {
$group_profile['degree_days']['heat'] += $profile['degree_days']['heat'];
}
if($profile['degree_days']['cool'] !== null) {
$group_profile['degree_days']['cool'] += $profile['degree_days']['cool'];
}
// Runtime
$group_profile['runtime']['heat_1'] += $profile['runtime']['heat_1'];
$group_profile['runtime']['heat_2'] += $profile['runtime']['heat_2'];
$group_profile['runtime']['auxiliary_heat_1'] += $profile['runtime']['auxiliary_heat_1'];
$group_profile['runtime']['auxiliary_heat_2'] += $profile['runtime']['auxiliary_heat_2'];
$group_profile['runtime']['cool_1'] += $profile['runtime']['cool_1'];
$group_profile['runtime']['cool_2'] += $profile['runtime']['cool_2'];
}
// echo '<pre>';
@ -497,6 +527,151 @@ class thermostat_group extends cora\crud {
return $scores;
}
/**
* Compare this thermostat_group to all other matching ones.
*
* @param array $attributes The attributes to compare to.
*
* @return array
*/
public function get_metrics($type, $attributes) {
// All or none are required.
if(
(
isset($attributes['address_latitude']) === true ||
isset($attributes['address_longitude']) === true ||
isset($attributes['address_radius']) === true
) &&
(
isset($attributes['address_latitude']) === false ||
isset($attributes['address_longitude']) === false ||
isset($attributes['address_radius']) === false
)
) {
throw new Exception('If one of address_latitude, address_longitude, or address_radius are set, then all are required.');
}
// Pull these values out so they don't get queried; this comparison is done
// in PHP.
if(isset($attributes['address_radius']) === true) {
$address_latitude = $attributes['address_latitude'];
$address_longitude = $attributes['address_longitude'];
$address_radius = $attributes['address_radius'];
unset($attributes['address_latitude']);
unset($attributes['address_longitude']);
unset($attributes['address_radius']);
}
$metric_codes = [
'setpoint_heat',
'setpoint_cool'
];
$metrics = [];
foreach($metric_codes as $metric_code) {
$metrics[$metric_code] = [
'values' => [],
'histogram' => [],
'standard_deviation' => null,
'median' => null
];
}
$limit_start = 0;
$limit_count = 1000;
/**
* Selecting lots of rows can eventually run PHP out of memory, so chunk
* this up into several queries to avoid that.
*/
do {
// Get all matching thermostat groups.
$other_thermostat_groups = $this->database->read(
'thermostat_group',
$attributes,
[], // columns
[], // order_by
[$limit_start, $limit_count] // limit
);
// Get all the scores from the other thermostat groups
foreach($other_thermostat_groups as $other_thermostat_group) {
// Only use profiles with at least a year of data
// Only use profiles generated in the past year
//
if(
$other_thermostat_group['profile']['metadata']['duration'] >= 365 &&
strtotime($other_thermostat_group['profile']['metadata']['generated_at']) > strtotime('-1 year')
) {
// Skip thermostat_groups that are too far away.
if(
isset($address_radius) === true &&
$this->haversine_great_circle_distance(
$address_latitude,
$address_longitude,
$other_thermostat_group['address_latitude'],
$other_thermostat_group['address_longitude']
) > $address_radius
) {
continue;
}
// setpoint_heat
if($other_thermostat_group['profile']['setpoint']['heat'] !== null) {
$setpoint_heat = round($other_thermostat_group['profile']['setpoint']['heat']);
if(isset($metrics['setpoint_heat']['histogram'][$setpoint_heat]) === false) {
$metrics['setpoint_heat']['histogram'][$setpoint_heat] = 0;
}
$metrics['setpoint_heat']['histogram'][$setpoint_heat]++;
$metrics['setpoint_heat']['values'][] = $setpoint_heat;
}
// setpoint_cool
if($other_thermostat_group['profile']['setpoint']['cool'] !== null) {
$setpoint_cool = round($other_thermostat_group['profile']['setpoint']['cool']);
if(isset($metrics['setpoint_cool']['histogram'][$setpoint_cool]) === false) {
$metrics['setpoint_cool']['histogram'][$setpoint_cool] = 0;
}
$metrics['setpoint_cool']['histogram'][$setpoint_cool]++;
$metrics['setpoint_cool']['values'][] = $setpoint_cool;
}
}
}
$limit_start += $limit_count;
} while (count($other_thermostat_groups) === $limit_count);
// setpoint_heat
$metrics['setpoint_heat']['standard_deviation'] = round($this->standard_deviation(
$metrics['setpoint_heat']['values']
), 2);
$metrics['setpoint_heat']['median'] = array_median($metrics['setpoint_heat']['values']);
unset($metrics['setpoint_heat']['values']);
// setpoint_cool
$metrics['setpoint_cool']['standard_deviation'] = round($this->standard_deviation(
$metrics['setpoint_cool']['values']
), 2);
$metrics['setpoint_cool']['median'] = array_median($metrics['setpoint_cool']['values']);
unset($metrics['setpoint_cool']['values']);
return $metrics;
}
private function standard_deviation($array) {
$count = count($array);
$mean = array_mean($array);
$variance = 0;
foreach($array as $i) {
$variance += pow(($i - $mean), 2);
}
return sqrt($variance / $count);
}
/**
* Calculates the great-circle distance between two points, with the
* Haversine formula.