mirror of
https://github.com/beestat/app.git
synced 2025-06-23 15:30:43 -04:00
Updated profile generation; added some metrics.
This commit is contained in:
parent
a210487ca6
commit
154af5d89f
@ -50,7 +50,7 @@ function array_median($array) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Useful function
|
// Useful function
|
||||||
function array_average($array) {
|
function array_mean($array) {
|
||||||
if (count($array) === 0) {
|
if (count($array) === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -177,6 +177,16 @@ class profile extends cora\api {
|
|||||||
'heat' => [],
|
'heat' => [],
|
||||||
'cool' => []
|
'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 = [];
|
$begin_runtime = [];
|
||||||
|
|
||||||
while($current_timestamp <= $end_timestamp) {
|
while($current_timestamp <= $end_timestamp) {
|
||||||
@ -213,22 +223,26 @@ class profile extends cora\api {
|
|||||||
// consistently represented instead of having to do this logic
|
// consistently represented instead of having to do this logic
|
||||||
// throughout the generator.
|
// throughout the generator.
|
||||||
$runtime = [];
|
$runtime = [];
|
||||||
|
$degree_days_date = date('Y-m-d', $current_timestamp);
|
||||||
|
$degree_days_temperatures = [];
|
||||||
while($row = $result->fetch_assoc()) {
|
while($row = $result->fetch_assoc()) {
|
||||||
$timestamp = strtotime($row['timestamp']);
|
$timestamp = strtotime($row['timestamp']);
|
||||||
$hour = date('G', $timestamp);
|
$hour = date('G', $timestamp);
|
||||||
|
$date = date('Y-m-d', $timestamp);
|
||||||
|
|
||||||
if (
|
// Degree days
|
||||||
$ignore_solar_heating === true &&
|
if($date !== $degree_days_date) {
|
||||||
$hour > 6 &&
|
$degree_days[] = (array_mean($degree_days_temperatures) / 10) - $degree_days_baseline;
|
||||||
$hour < 22
|
$degree_days_date = $date;
|
||||||
) {
|
$degree_days_temperatures = [];
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
$degree_days_temperatures[] = $row['outdoor_temperature'];
|
||||||
|
|
||||||
if($first_timestamp === null) {
|
if($first_timestamp === null) {
|
||||||
$first_timestamp = $row['timestamp'];
|
$first_timestamp = $row['timestamp'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Normalizing heating and cooling a bit.
|
||||||
if(
|
if(
|
||||||
$thermostat['system_type']['detected']['heat'] === 'compressor' ||
|
$thermostat['system_type']['detected']['heat'] === 'compressor' ||
|
||||||
$thermostat['system_type']['detected']['heat'] === 'geothermal'
|
$thermostat['system_type']['detected']['heat'] === 'geothermal'
|
||||||
@ -255,6 +269,21 @@ class profile extends cora\api {
|
|||||||
$row['cool_2'] = 0;
|
$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) {
|
if (isset($runtime[$timestamp]) === false) {
|
||||||
$runtime[$timestamp] = [];
|
$runtime[$timestamp] = [];
|
||||||
}
|
}
|
||||||
@ -630,8 +659,6 @@ class profile extends cora\api {
|
|||||||
$current_timestamp += $five_minutes;
|
$current_timestamp += $five_minutes;
|
||||||
}
|
}
|
||||||
|
|
||||||
// print_r($samples);
|
|
||||||
|
|
||||||
// Process the samples
|
// Process the samples
|
||||||
$deltas_raw = [];
|
$deltas_raw = [];
|
||||||
foreach($samples as $sample) {
|
foreach($samples as $sample) {
|
||||||
@ -670,6 +697,18 @@ class profile extends cora\api {
|
|||||||
'heat' => null,
|
'heat' => null,
|
||||||
'cool' => 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' => [
|
'metadata' => [
|
||||||
'generated_at' => date('c'),
|
'generated_at' => date('c'),
|
||||||
'duration' => round((time() - strtotime($first_timestamp)) / 86400),
|
'duration' => round((time() - strtotime($first_timestamp)) / 86400),
|
||||||
@ -730,11 +769,27 @@ class profile extends cora\api {
|
|||||||
|
|
||||||
foreach(['heat', 'cool'] as $type) {
|
foreach(['heat', 'cool'] as $type) {
|
||||||
if(count($setpoints[$type]) > 0) {
|
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]);
|
$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;
|
return $profile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ class thermostat_group extends cora\crud {
|
|||||||
'generate_profiles',
|
'generate_profiles',
|
||||||
'generate_profile',
|
'generate_profile',
|
||||||
'get_scores',
|
'get_scores',
|
||||||
|
'get_metrics',
|
||||||
'update_system_types'
|
'update_system_types'
|
||||||
],
|
],
|
||||||
'public' => []
|
'public' => []
|
||||||
@ -27,7 +28,8 @@ class thermostat_group extends cora\crud {
|
|||||||
'generate_temperature_profiles' => 604800, // 7 Days
|
'generate_temperature_profiles' => 604800, // 7 Days
|
||||||
'generate_profile' => 604800, // 7 Days
|
'generate_profile' => 604800, // 7 Days
|
||||||
'generate_profiles' => 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,
|
'heat' => null,
|
||||||
'cool' => 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' => [
|
'metadata' => [
|
||||||
'generated_at' => date('c'),
|
'generated_at' => date('c'),
|
||||||
'duration' => null,
|
'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>';
|
// echo '<pre>';
|
||||||
@ -497,6 +527,151 @@ class thermostat_group extends cora\crud {
|
|||||||
return $scores;
|
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
|
* Calculates the great-circle distance between two points, with the
|
||||||
* Haversine formula.
|
* Haversine formula.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user