chg: [decaying:simulation] Support of sightings in the decaying simulation

pull/5032/head
mokaddem 2019-07-16 09:31:49 +02:00
parent 9585c9118d
commit bbab646d01
No known key found for this signature in database
GPG Key ID: 164C473F627A06FA
6 changed files with 216 additions and 104 deletions

View File

@ -351,24 +351,9 @@ class DecayingModelController extends AppController
}
}
public function decayingToolChartSimulation($model_id, $attribute_id)
{
// $model = $this->DecayingModel->checkAuthorisation($this->Auth->user(), $model_id);
// if ($model === false) {
// throw new NotFoundException(_('No Decaying Model with the provided ID exists, or you are not authorised to edit it.'));
// }
// $attribute = $this->User->Event->Attribute->fetchAttributesSimple($this->Auth->user(), array('conditions' => array('id' => $attribute_id)));
// if(empty($attribute)) {
// throw new NotFoundException(__('Invalid attribute id.'));
// } else {
// $attribute = $attribute[0];
// }
// $this->render('chart_simulation');
}
public function decayingToolComputeSimulation($model_id, $attribute_id)
{
return $this->RestResponse->viewData('date,value' . PHP_EOL . '2017-03-12,20' . PHP_EOL . '2018-03-12,10', 'csv');
$score_overtime = $this->RestResponse->viewData($this->DecayingModel->getScoreOvertime($this->Auth->user(), $model_id, $attribute_id), $this->response->type());
return $score_overtime;
}
}

View File

@ -312,80 +312,8 @@ class SightingsController extends AppController
public function listSightings($id, $context = 'attribute', $org_id = false)
{
$this->loadModel('Event');
$rawId = $id;
$id = $this->Sighting->explodeIdList($id);
if ($context === 'attribute') {
$object = $this->Event->Attribute->fetchAttributes($this->Auth->user(), array('conditions' => array('Attribute.id' => $id, 'Attribute.deleted' => 0), 'flatten' => 1));
} else {
// let's set the context to event here, since we reuse the variable later on for some additional lookups.
// Passing $context = 'org' could have interesting results otherwise...
$context = 'event';
$object = $this->Event->fetchEvent($this->Auth->user(), $options = array('eventid' => $id, 'metadata' => true));
}
if (empty($object)) {
throw new MethodNotAllowedException('Invalid object.');
}
$conditions = array(
'Sighting.' . $context . '_id' => $id
);
if ($org_id) {
$conditions[] = array('Sighting.org_id' => $org_id);
}
$sightings = $this->Sighting->find('all', array(
'conditions' => $conditions,
'recursive' => -1,
'contain' => array('Organisation.name'),
'order' => array('Sighting.date_sighting DESC')
));
if (!empty($sightings) && empty(Configure::read('Plugin.Sightings_policy')) && !$this->_isSiteAdmin()) {
$eventOwnerOrgIdList = array();
foreach ($sightings as $k => $sighting) {
if (empty($eventOwnerOrgIdList[$sighting['Sighting']['event_id']])) {
$temp_event = $this->Event->find('first', array(
'recursive' => -1,
'conditions' => array('Event.id' => $sighting['Sighting']['event_id']),
'fields' => array('Event.id', 'Event.orgc_id')
));
$eventOwnerOrgIdList[$temp_event['Event']['id']] = $temp_event['Event']['orgc_id'];
}
if (empty($eventOwnerOrgIdList[$sighting['Sighting']['event_id']]) || $eventOwnerOrgIdList[$sighting['Sighting']['event_id']] !== $this->Auth->user('org_id')) {
unset($sightings[$k]);
}
}
$sightings = array_values($sightings);
} else if (!empty($sightings) && Configure::read('Plugin.Sightings_policy') == 1 && !$this->_isSiteAdmin()) {
$eventsWithOwnSightings = array();
foreach ($sightings as $k => $sighting) {
if (empty($eventsWithOwnSightings[$sighting['Sighting']['event_id']])) {
$eventsWithOwnSightings[$sighting['Sighting']['event_id']] = false;
$sighting_temp = $this->Sighting->find('first', array(
'recursive' => -1,
'conditions' => array(
'Sighting.event_id' => $sighting['Sighting']['event_id'],
'Sighting.org_id' => $this->Auth->user('org_id')
)
));
if (empty($sighting_temp)) {
$temp_event = $this->Event->find('first', array(
'recursive' => -1,
'conditions' => array(
'Event.id' => $sighting['Sighting']['event_id'],
'Event.orgc_id' => $this->Auth->user('org_id')
),
'fields' => array('Event.id', 'Event.orgc_id')
));
$eventsWithOwnSightings[$sighting['Sighting']['event_id']] = !empty($temp_event);
} else {
$eventsWithOwnSightings[$sighting['Sighting']['event_id']] = true;
}
}
if (!$eventsWithOwnSightings[$sighting['Sighting']['event_id']]) {
unset($sightings[$k]);
}
}
$sightings = array_values($sightings);
}
$sightings = $this->Sighting->listSightings($this->Auth->user(), $id, $context, $org_id);
$this->set('org_id', $org_id);
$this->set('rawId', $rawId);
$this->set('context', $context);

View File

@ -234,4 +234,105 @@ class DecayingModel extends AppModel
);
}
// compute the current score for the provided atribute with the provided model
public function computeCurrentScore($model, $attribute)
{
}
// compute the score at the given timestamp for the provided atribute with the provided model
public function computeScore($model, $attribute, $timestamp, $last_sighting_timestamp)
{
$t = $timestamp - $last_sighting_timestamp;
if ($t < 0) {
return 0;
}
$base_score = 100;
$delta = $model['DecayingModel']['parameters']['delta'];
$tau = $model['DecayingModel']['parameters']['tau']*24*60*60;
$score = $base_score * (1 - pow($t / $tau, 1 / $delta));
// debug($timestamp);
// debug($last_sighting_timestamp);
// debug($tau);
// debug($t);
// debug($score);
return $score < 0 ? 0 : $score;
}
// returns timestamp set to the rounded hour
public function round_timestamp_to_hour($time)
{
$offset = $time % 3600;
return $time - $offset;
}
public function getScoreOvertime($user, $model_id, $attribute_id)
{
$this->Attribute = ClassRegistry::init('Attribute');
$attribute = $this->Attribute->fetchAttributesSimple($user, array(
'conditions' => array('id' => $attribute_id)
));
if (empty($attribute)) {
throw new NotFoundException(__('Attribute not found'));
} else {
$attribute = $attribute[0];
}
$model = $this->checkAuthorisation($user, $model_id, true);
if ($model === false) {
throw new NotFoundException(__('Model not found'));
}
$this->Sighting = ClassRegistry::init('Sighting');
$sightings = $this->Sighting->listSightings($user, $attribute_id, 'attribute', false, 0, false);
if (empty($sightings)) {
$sightings = array(array('Sighting' => array('date_sighting' => $attribute['Attribute']['timestamp']))); // simulate a sighting nonetheless
}
// get start time
$start_time = $attribute['Attribute']['timestamp'];
// $start_time = $attribute['Attribute']['first_seen'] < $start_time ? $attribute['Attribute']['first_seen'] : $start_time;
$start_time = $sightings[0]['Sighting']['date_sighting'] < $start_time ? $sightings[0]['Sighting']['date_sighting'] : $start_time;
$start_time = intval($start_time);
$start_time = $this->round_timestamp_to_hour($start_time);
// get end time
$end_time = $sightings[count($sightings)-1]['Sighting']['date_sighting'] + $model['DecayingModel']['parameters']['tau']*24*60*60;
$end_time = $this->round_timestamp_to_hour($end_time);
// generate time span from oldest timestamp to last decay, resolution is hours
$score_overtime = array();
$rounded_sightings = array();
$sighting_index = 0;
for ($t=$start_time; $t < $end_time; $t+=3600) {
// fetch closest sighting to the current time
$sighting_index = $this->get_closest_sighting($sightings, $t, $sighting_index);
$last_sighting = $this->round_timestamp_to_hour($sightings[$sighting_index]['Sighting']['date_sighting']);
$sightings[$sighting_index]['Sighting']['rounded_timestamp'] = $last_sighting;
$score_overtime[$t] = $this->computeScore($model, $attribute, $t, $last_sighting);
}
$csv = 'date,value' . PHP_EOL;
foreach ($score_overtime as $t => $v) {
$csv .= (new DateTime())->setTimestamp($t)->format('Y-m-d H:i:s') . ',' . $v . PHP_EOL;
}
return array(
'csv' => $csv,
'sightings' => $sightings
);
}
public function get_closest_sighting($sightings, $time, $previous_index)
{
if (count($sightings) <= $previous_index+1) {
return $previous_index;
}
$max_time = $time + 3600;
$next_index = $previous_index+1;
$next_sighting = $sightings[$next_index]['Sighting']['date_sighting'];
while ($next_sighting <= $max_time) {
$next_index++;
if ($next_index >= count($sightings)) {
break;
}
$next_sighting = $sightings[$next_index]['Sighting']['date_sighting'];
}
return $next_index-1;
}
}

View File

@ -476,6 +476,87 @@ class Sighting extends AppModel
return $sightingsRearranged;
}
public function listSightings($user, $id, $context, $org_id = false, $sightings_type = false, $order_desc = true)
{
$this->Event = ClassRegistry::init('Event');
$id = $this->explodeIdList($id);
if ($context === 'attribute') {
$object = $this->Event->Attribute->fetchAttributes($user, array('conditions' => array('Attribute.id' => $id, 'Attribute.deleted' => 0), 'flatten' => 1));
} else {
// let's set the context to event here, since we reuse the variable later on for some additional lookups.
// Passing $context = 'org' could have interesting results otherwise...
$context = 'event';
$object = $this->Event->fetchEvent($user, $options = array('eventid' => $id, 'metadata' => true));
}
if (empty($object)) {
throw new MethodNotAllowedException('Invalid object.');
}
$conditions = array(
'Sighting.' . $context . '_id' => $id
);
if ($org_id) {
$conditions[] = array('Sighting.org_id' => $org_id);
}
if ($sightings_type !== false) {
$conditions[] = array('Sighting.type' => $sightings_type);
}
$sightings = $this->find('all', array(
'conditions' => $conditions,
'recursive' => -1,
'contain' => array('Organisation.name'),
'order' => array(sprintf('Sighting.date_sighting %s', $order_desc ? 'DESC' : ''))
));
if (!empty($sightings) && empty(Configure::read('Plugin.Sightings_policy')) && !$user['Role']['perm_site_admin']) {
$eventOwnerOrgIdList = array();
foreach ($sightings as $k => $sighting) {
if (empty($eventOwnerOrgIdList[$sighting['Sighting']['event_id']])) {
$temp_event = $this->Event->find('first', array(
'recursive' => -1,
'conditions' => array('Event.id' => $sighting['Sighting']['event_id']),
'fields' => array('Event.id', 'Event.orgc_id')
));
$eventOwnerOrgIdList[$temp_event['Event']['id']] = $temp_event['Event']['orgc_id'];
}
if (empty($eventOwnerOrgIdList[$sighting['Sighting']['event_id']]) || $eventOwnerOrgIdList[$sighting['Sighting']['event_id']] !== $user['org_id']) {
unset($sightings[$k]);
}
}
$sightings = array_values($sightings);
} else if (!empty($sightings) && Configure::read('Plugin.Sightings_policy') == 1 && !$user['Role']['perm_site_admin']) {
$eventsWithOwnSightings = array();
foreach ($sightings as $k => $sighting) {
if (empty($eventsWithOwnSightings[$sighting['Sighting']['event_id']])) {
$eventsWithOwnSightings[$sighting['Sighting']['event_id']] = false;
$sighting_temp = $this->find('first', array(
'recursive' => -1,
'conditions' => array(
'Sighting.event_id' => $sighting['Sighting']['event_id'],
'Sighting.org_id' => $user['org_id']
)
));
if (empty($sighting_temp)) {
$temp_event = $this->Event->find('first', array(
'recursive' => -1,
'conditions' => array(
'Event.id' => $sighting['Sighting']['event_id'],
'Event.orgc_id' => $user['org_id']
),
'fields' => array('Event.id', 'Event.orgc_id')
));
$eventsWithOwnSightings[$sighting['Sighting']['event_id']] = !empty($temp_event);
} else {
$eventsWithOwnSightings[$sighting['Sighting']['event_id']] = true;
}
}
if (!$eventsWithOwnSightings[$sighting['Sighting']['event_id']]) {
unset($sightings[$k]);
}
}
$sightings = array_values($sightings);
}
return $sightings;
}
public function restSearch($user, $returnFormat, $filters)
{
if (!isset($this->validFormats[$returnFormat][1])) {

View File

@ -3,20 +3,20 @@
<div style="height: 40%; display: flex">
<div style="width: 30%; display: flex; flex-direction: column;">
<div class="panel-container" style="display: flex; flex-direction: column; flex-grow: 1">
<select id="select_model_to_simulate" style="width: 100%;">
<select id="select_model_to_simulate" style="width: 100%;" onchange="refreshSimulation()">
<?php foreach ($all_models as $model): ?>
<option value="<?php echo h($model['DecayingModel']['id']) ?>" <?php echo $decaying_model['DecayingModel']['id'] == $model['DecayingModel']['id'] ? 'selected' : '' ?>><?php echo h($model['DecayingModel']['name']); ?></option>
<?php endforeach; ?>
</select>
<ul class="nav nav-tabs" style="margin-right: -5px; margin-bottom: 10px;" id="simulation-tabs">
<ul class="nav nav-tabs" style="margin-right: -5px; margin-bottom: 0px;" id="simulation-tabs">
<li class="active"><a href="#restsearch" data-toggle="tab">RestSearch</a></li>
<li><a href="#specificid" data-toggle="tab">Specific ID</a></li>
</ul>
<div class="tab-content" style="padding: 5px;">
<div class="tab-pane active" id="restsearch">
<div style="display: flex; flex-direction: column;">
<div class="tab-content" style="padding: 5px; height: 100%;">
<div class="tab-pane active" id="restsearch" style="height: 100%;">
<div style="display: flex; flex-direction: column; height: 100%;">
<h3 style="">Attribute RestSearch<span style="vertical-align: top; font-size: x-small;" class="fa fa-question-circle" title="Enforced fields: returnFormat"></span></h3>
<?php
$registered_taxonomies = array_keys($decaying_model['DecayingModel']['parameters']['base_score_config']);
@ -24,7 +24,7 @@
$taxonomy_name = $taxonomy_name . ':%' ;
}
?>
<textarea style="margin-bottom: 0px; margin-left: 4px; width: auto;height: unset !important;" rows="12">
<textarea style="margin-bottom: 0px; margin-left: 4px; flex-grow: 3; width: auto;">
{
"decayingModel": <?php echo h($decaying_model['DecayingModel']['id']); ?>,
"to_ids": 1,
@ -42,7 +42,7 @@
<div style="display: flex;">
<div style="margin-left: 4px; margin-bottom: 0px;" class="input-prepend">
<span class="add-on">ID</span>
<input class="span3" type="text" placeholder="<?php echo __('Attribute ID or UUID') ?>" onkeypress="handle_input_key(event)">
<input type="text" placeholder="<?php echo __('Attribute ID or UUID') ?>" onkeypress="handle_input_key(event)" style="width: auto;">
</div>
<span id="performRestSearchButton" class="btn btn-primary" style="width: fit-content; margin-left: 4px;" role="button" onclick="doSpecificSearch(this)"><?php echo __('Simulate'); ?></span>
</div>
@ -54,7 +54,7 @@
<div style="width: 70%; display: flex;">
<div class="panel-container" style="flex-grow: 1;">
<div id="chart-decay-simulation-container" style="width: 100%; height: 100%; position: relative">
<div id="simulation_chart" style="height: 100%;"></div>
<div id="simulation_chart" style="height: 100%; overflow: hidden;"></div>
</div>
</div>
</div>
@ -127,7 +127,14 @@ function doSimulation(clicked, attribute_id) {
},
type:'get',
cache: false,
dataType: 'json',
url: '/decayingModel/decayingToolComputeSimulation/' + model_id + '/' + attribute_id,
});
}
function refreshSimulation() {
var $row = $('#attribute_div tr.success');
var attribute_id = $row.find('td:first').text();
doSimulation($row, attribute_id);
}
</script>

View File

@ -19,7 +19,7 @@
margin: {top: 10, right: 10, bottom: 20, left: 30},
animation_duration: 1000,
animation_short_duration: 250,
time_format: '%Y-%m-%d'
time_format: '%Y-%m-%d %H:%M:%S'
};
this.options = $.extend(true, {}, default_options, options);
this._init();
@ -51,6 +51,7 @@
this.width = this.$container.width() - this.options.margin.left - this.options.margin.right;
this.height = this.$container.height() - this.options.margin.top - this.options.margin.bottom;
this.chart_data = [];
this.sightings = [];
this._parseDataset();
this.x = d3.time.scale()
@ -91,10 +92,14 @@
},
update: function(data) {
var that = this;
this.chart_data = data;
this.chart_data = data.csv;
this.sightings = data.sightings;
this._parseDataset();
this._draw();
},
_draw: function() {
var that = this;
this.x.domain(d3.extent(this.chart_data, function(d) { return d.date; }))
this.y.domain([0, d3.max(this.chart_data, function(d) { return d.value; })])
@ -122,7 +127,7 @@
this.line_guides = this.svg
.select('.d3-line-guides-group')
.selectAll('.d3-line-guides')
.data(this.chart_data);
.data(this.sightings_data);
this.line_guides
.enter()
.append('line')
@ -142,7 +147,7 @@
this.points = this.svg
.selectAll('.d3-line-circle')
.data(this.chart_data);
.data(this.sightings_data);
this.points
.enter()
.append('circle')
@ -186,13 +191,18 @@
var that = this;
if (typeof this.chart_data === 'string') {
this.chart_data = d3.csv.parse(this.chart_data, function(d){
return { date: that.timeFormatter(d.date), value : d.value }
var parsed_date = that.timeFormatter(d.date);
return { timestamp: Math.floor(parsed_date.getTime() / 1000), date: parsed_date, value : parseFloat(d.value) }
});
} else if (Array.isArray(this.chart_data)){
this.chart_data.forEach(function(entry, i) {
that.chart_data[i].date = that.timeFormatter(entry.date);
})
}
this.sightings_data = this.sightings.map(function(d) {
var sighting = d.Sighting;
return { timestamp: sighting.rounded_timestamp, date: new Date(sighting.rounded_timestamp*1000), value : 100.0 };
});
}
}