chg: [decaying] Added list of available formulas and model settings -

WiP
pull/5032/head
mokaddem 2019-08-13 14:21:58 +02:00
parent 878b46811c
commit b415f8ffc7
No known key found for this signature in database
GPG Key ID: 164C473F627A06FA
8 changed files with 208 additions and 56 deletions

View File

@ -144,10 +144,33 @@ class DecayingModelController extends AppController
if ($this->request->is('post') || $this->request->is('put')) {
$this->request->data['DecayingModel']['id'] = $id;
if (!isset($this->request->data['DecayingModel']['formula'])) {
$this->request->data['DecayingModel']['formula'] = 'polynomial';
}
if ($this->request->data['DecayingModel']['formula'] == 'polynomial') {
if (isset($this->request->data['DecayingModel']['parameters']['settings'])) {
$this->request->data['DecayingModel']['parameters']['settings'] = array();
}
} else if (
isset($this->request->data['DecayingModel']['parameters']['settings']) &&
$this->request->data['DecayingModel']['parameters']['settings'] == ''
) {
$this->request->data['DecayingModel']['parameters']['settings'] = '{}';
}
if (!isset($this->request->data['DecayingModel']['parameters'])) {
$this->request->data['DecayingModel']['parameters'] = array();
} else {
if (isset($this->request->data['DecayingModel']['parameters']['settings'])) {
$settings = json_decode($this->request->data['DecayingModel']['parameters']['settings'], true);
if ($settings === null) {
$this->Flash->error(__('Invalid JSON `Settings`.'));
return false;
}
$this->request->data['DecayingModel']['parameters']['settings'] = $settings;
}
if (!isset($this->request->data['DecayingModel']['parameters']['tau'])) {
$this->Flash->error(__('Invalid parameter `tau`.'));
return false;
@ -175,9 +198,9 @@ class DecayingModelController extends AppController
$this->request->data['DecayingModel']['parameters']['base_score_config'] = new stdClass();
}
}
$this->request->data['DecayingModel']['parameters'] = json_encode($this->request->data['DecayingModel']['parameters']);
$fieldList = array('name', 'description', 'parameters');
$this->request->data['DecayingModel']['parameters'] = json_encode($this->request->data['DecayingModel']['parameters']);
$fieldList = array('name', 'description', 'parameters', 'formula');
if ($this->DecayingModel->save($this->request->data, true, $fieldList)) {
if ($this->request->is('ajax')) {
$saved = $this->DecayingModel->checkAuthorisation($this->Auth->user(), $this->DecayingModel->id);
@ -292,7 +315,9 @@ class DecayingModelController extends AppController
$types = array_merge($types, $objectTypes);
ksort($types);
$savedDecayingModels = $this->DecayingModel->fetchAllowedModels($this->Auth->user());
$available_formulas = $this->DecayingModel->listAvailableFormulas();
$this->set('available_formulas', $available_formulas);
$this->set('parameters', $parameters);
$this->set('types', $types);
$this->set('savedModels', $savedDecayingModels);

View File

@ -99,6 +99,8 @@ class DecayingModel extends AppModel
$this->__validateParameters($parameters[$name]);
} else if (is_numeric($value)) {
$parameters[$name] = round($value, 4);
} else if (!empty($value)) {
$parameters[$name] = $value;
} else {
$parameters[$name] = 0;
}
@ -249,20 +251,37 @@ class DecayingModel extends AppModel
);
}
private function __include_formula_file_and_return_instance($filename='polynomial.php')
private function __include_formula_file_and_return_instance($filename='Polynomial.php')
{
$filename_no_extension = str_replace('.php', '', $filename);
$filename = preg_replace('/[^a-zA-Z0-9_]+/', '-', $filename_no_extension) . '.php'; // sanitization
$full_path = APP . 'Model/DecayingModelsFormulas/' . $filename;
$expected_classname = 'DecayingModel' . str_replace('.php', '', $filename);
$expected_classname = $filename_no_extension;
if (is_file($full_path)) {
include $full_path;
$model_class = ClassRegistry::init($expected_classname);
if ($model_class->checkLoading() == 'BONFIRE LIT') {
if ($model_class->checkLoading() === 'BONFIRE LIT') {
return $model_class;
}
}
return false;
}
public function listAvailableFormulas()
{
$path = APP . 'Model/DecayingModelsFormulas/';
$formula_files = array_diff(scandir($path), array('..', '.', 'Base.php'));
$available_formulas = array();
foreach ($formula_files as $formula_file) {
$model_class = $this->__include_formula_file_and_return_instance($formula_file);
if ($model_class === false) {
continue;
}
$available_formulas[get_class($model_class)] = $model_class::EXTENDS_FORMULA;
}
return $available_formulas;
}
public function getModelClass($model)
{
$formula_name = $model['DecayingModel']['formula'] === '' ? 'polynomial' : $model['DecayingModel']['formula'];

View File

@ -1,6 +1,8 @@
<?php
abstract class DecayingModelBase
{
public const EXTENDS_FORMULA = '';
public function checkLoading()
{
return 'BONFIRE LIT';

View File

@ -1,8 +1,11 @@
<?php
include_once 'Base.php';
class DecayingModelPolynomial extends DecayingModelBase
class Polynomial extends DecayingModelBase
{
public const EXTENDS_FORMULA = 'Polynomial';
public function computeScore($model, $attribute, $base_score, $elapsed_time)
{
if ($elapsed_time < 0) {

View File

@ -0,0 +1,26 @@
<?php
include_once 'Base.php';
class PolynomialExtended extends DecayingModelBase
{
public const EXTENDS_FORMULA = 'Polynomial';
public function computeScore($model, $attribute, $base_score, $elapsed_time)
{
if ($elapsed_time < 0) {
return 0;
}
$delta = $model['DecayingModel']['parameters']['delta'];
$tau = $model['DecayingModel']['parameters']['tau']*24*60*60;
$score = $base_score * (1 - pow($elapsed_time / $tau, 1 / $delta));
return $score < 0 ? 0 : $score;
}
public function isDecayed($model, $attribute, $score)
{
$threshold = $model['DecayingModel']['parameters']['threshold'];
return $threshold > $score;
}
}
?>

View File

@ -8,8 +8,9 @@
echo $this->Form->input('description', array(
));
echo $this->Form->input('formula', array(
'value' => isset($this->request->data['DecayingModel']['formula']) ? $this->request->data['DecayingModel']['formula'] : 'default'
'value' => isset($this->request->data['DecayingModel']['formula']) ? $this->request->data['DecayingModel']['formula'] : 'polynomial'
));
echo '<div id="ContainerPolynomialSetting">';
echo $this->Form->input('DecayingModel.parameters.tau', array(
'label' => __('Tau parameter'),
'type' => 'number',
@ -54,6 +55,16 @@
'cols' => '10',
'value' => isset($this->request->data['DecayingModel']['parameters']['base_score_config']) ? json_encode($this->request->data['DecayingModel']['parameters']['base_score_config']) : ''
));
echo '</div>';
echo '<div id="ContainerOtherSetting">';
echo '<div class="clear"></div>';
echo '<label for="DecayingModelOtherSettings">' . __('Model Settings') . '</label>';
echo $this->Form->textarea('DecayingModel.parameters.settings', array(
'class' => 'form-control span6',
'cols' => '10',
'value' => isset($this->request->data['DecayingModel']['parameters']['settings']) ? json_encode($this->request->data['DecayingModel']['parameters']['settings']) : ''
));
echo '</div>';
?>
<div class="clear"></div>
</fieldset>
@ -65,3 +76,18 @@
<?php
echo $this->element('/genericElements/SideMenu/side_menu', array('menuList' => 'decayingModel', 'menuItem' => 'add'));
?>
<script>
toggleOtherSetting();
$(document).ready(function() {
$('#DecayingModelFormula').on('input', function() {
toggleOtherSetting();
})
});
function toggleOtherSetting() {
if ($('#DecayingModelFormula').val() === 'Polynomial') {
$('#ContainerOtherSetting').hide();
} else {
$('#ContainerOtherSetting').show();
}
}
</script>

View File

@ -64,54 +64,66 @@
</div>
</div>
<div class="span12">
<div class="span10" style="border: 1px solid #ddd; border-radius: 4px; margin-bottom: 20px;">
<div id="decayGraph" style="width: 100%;"></div>
</div>
<div class="row">
<div class="span6" style="margin-bottom: 20px;">
<?php foreach ($parameters as $param => $config): ?>
<div class="input-prepend input-append">
<span class="add-on param-name" data-toggle="tooltip" data-placement="left" style="min-width: 100px;" title="<?php echo isset($config['info']) ? h($config['info']) : ''?>">
<?php echo h($config['name']) . (isset($config['greek']) ? ' <strong>'.h($config['greek']).'</strong>' : ''); ?>
</span>
<input id="input_<?php echo h($param); ?>" class="input-mini" type="number" min=0 step=<?php echo h($config['step']); ?> value=<?php echo h($config['value']); ?> oninput="refreshGraph(this);" ></input>
<span class="add-on"><input id="input_<?php echo h($param); ?>_range" type="range" min=0 <?php echo isset($config['max']) ? 'max=' . $config['max'] : '' ?> step=<?php echo h($config['step']); ?> value=<?php echo h($config['value']); ?> oninput="$('#input_<?php echo h($param); ?>').val(this.value).trigger('input');"></input></span>
<?php if (isset($config['unit'])): ?>
<span class="add-on"><?php echo h($config['unit']); ?></span>
<?php endif; ?>
</div>
<div>
<select id="formulaSelectPicker">
<?php foreach ($available_formulas as $formula_name => $extends_smthg): ?>
<option value="<?php echo h($formula_name); ?>" data-extends="<?php echo h($extends_smthg); ?>"><?php echo h($formula_name); ?></option>
<?php endforeach; ?>
<input id="input_default_base_score" value=0 class="hidden"></input>
<div class="input-append" style="margin-bottom: 0px;">
<input id="input_base_score_config" class="hidden" value="[]"></input>
<button class="btn btn-primary" style="border-radius: 4px 0px 0px 4px;" onclick="decayingTool.toggleBasescoreForm()">
<span class="fa fa-tags"> <?php echo __('Adjust base score'); ?></span>
</button>
<span id="summary_base_score_config" class="add-on param-name">
<span class="far fa-square"></span>
</span>
</select>
</div>
<div id="containerFormulaPolynomialSetting">
<div class="span10" style="border: 1px solid #ddd; border-radius: 4px; margin-bottom: 20px;">
<div id="decayGraph" style="width: 100%;"></div>
</div>
<div class="row">
<div class="span6" style="margin-bottom: 20px;">
<?php foreach ($parameters as $param => $config): ?>
<div class="input-prepend input-append">
<span class="add-on param-name" data-toggle="tooltip" data-placement="left" style="min-width: 100px;" title="<?php echo isset($config['info']) ? h($config['info']) : ''?>">
<?php echo h($config['name']) . (isset($config['greek']) ? ' <strong>'.h($config['greek']).'</strong>' : ''); ?>
</span>
<input id="input_<?php echo h($param); ?>" class="input-mini" type="number" min=0 step=<?php echo h($config['step']); ?> value=<?php echo h($config['value']); ?> oninput="refreshGraph(this);" ></input>
<span class="add-on"><input id="input_<?php echo h($param); ?>_range" type="range" min=0 <?php echo isset($config['max']) ? 'max=' . $config['max'] : '' ?> step=<?php echo h($config['step']); ?> value=<?php echo h($config['value']); ?> oninput="$('#input_<?php echo h($param); ?>').val(this.value).trigger('input');"></input></span>
<?php if (isset($config['unit'])): ?>
<span class="add-on"><?php echo h($config['unit']); ?></span>
<?php endif; ?>
</div>
<?php endforeach; ?>
<input id="input_default_base_score" value=0 class="hidden"></input>
<div class="input-append" style="margin-bottom: 0px;">
<input id="input_base_score_config" class="hidden" value="[]"></input>
<button class="btn btn-primary" style="border-radius: 4px 0px 0px 4px;" onclick="decayingTool.toggleBasescoreForm()">
<span class="fa fa-tags"> <?php echo __('Adjust base score'); ?></span>
</button>
<span id="summary_base_score_config" class="add-on param-name">
<span class="far fa-square"></span>
</span>
</div>
<div style="display: inline-block; margin-left: 10px;">
<a id="button-toggle-simulation" target="_blank" class="btn btn-primary" href="" onclick="return !$(this).hasClass('disabled');">
<span class="fa fa-chart-line"> <?php echo __('Simulate this model'); ?></span>
</a>
</div>
</div>
<div style="display: inline-block; margin-left: 10px;">
<a id="button-toggle-simulation" target="_blank" class="btn btn-primary" href="" onclick="return !$(this).hasClass('disabled');">
<span class="fa fa-chart-line"> <?php echo __('Simulate this model'); ?></span>
</a>
<div class="span6">
<table class="table table-striped table-bordered">
<tbody>
<tr>
<td>Expire after (lifetime)</td>
<td id="infoCellExpired"></td>
</tr>
<tr>
<td>Score halved after (Half-life)</td>
<td id="infoCellHalved"></td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="span6">
<table class="table table-striped table-bordered">
<tbody>
<tr>
<td>Expire after (lifetime)</td>
<td id="infoCellExpired"></td>
</tr>
<tr>
<td>Score halved after (Half-life)</td>
<td id="infoCellHalved"></td>
</tr>
</tbody>
</table>
</div>
</div>
<div id="containerFormulaOtherSetting" class="hidden">
<textarea id="textarea_other_settings_formulas" style="width: 430px;" rows="5" placeholder="<?php echo(__('Model\'s Settings')); ?>"></textarea>
</div>
<div class="row">
@ -156,5 +168,23 @@ $(document).ready(function() {
}
$(this).html(parsedJson);
});
$('#formulaSelectPicker').change(function() {
toggleContainer();
})
});
function toggleContainer() {
var $option = $('#formulaSelectPicker').find('option:selected');
if ($option.data('extends') == 'Polynomial') {
$('#containerFormulaPolynomialSetting').show();
} else {
$('#containerFormulaPolynomialSetting').hide();
}
if ($('#formulaSelectPicker').val() == 'Polynomial') {
$('#containerFormulaOtherSetting').hide();
} else {
$('#containerFormulaOtherSetting').show();
}
}
</script>

View File

@ -345,11 +345,16 @@
$('#input_Tau').data('multiplier', $('#input_Tau').val()/this.options.TICK_NUM);
$('#input_Delta, #input_Delta_range').val(model.parameters.delta);
$('#input_Threshold, #input_Threshold_range').val(model.parameters.threshold);
$('#input_base_score_config').val(JSON.stringify(model.parameters.base_score_config));
var base_score_config = model.parameters.base_score_config === undefined ? {} : model.parameters.base_score_config;
$('#input_base_score_config').val(JSON.stringify(base_score_config));
var model_settings = model.parameters.settings === undefined ? {} : model.parameters.settings;
$('#textarea_other_settings_formulas').val(JSON.stringify(model_settings));
$('#input_default_base_score').val(model.parameters.default_base_score);
$('#formulaSelectPicker').val(model.formula);
var $form = $('#saveForm');
$form.find('[name="name"]').val(model.name);
$form.find('[name="description"]').val(model.description);
toggleContainer();
this.refreshInfoCells();
this.redrawGraph();
// highlight attribute types
@ -362,6 +367,7 @@
var data = {};
data.name = $form.find('[name="name"]').val();
data.description = $form.find('[name="description"]').val();
data.formula = $('#formulaSelectPicker').val();
var params = {};
params.tau = parseInt($('#input_Tau').val());
params.delta = parseFloat($('#input_Delta').val());
@ -370,6 +376,9 @@
var base_score_config = $('#input_base_score_config').val();
base_score_config = base_score_config === '' ? '{}' : base_score_config;
params.base_score_config = JSON.parse(base_score_config)
var model_settings = $('#textarea_other_settings_formulas').val();
model_settings = model_settings === '' ? '{}' : model_settings;
params.settings = JSON.parse(model_settings)
data.parameters = params;
return data;
},
@ -409,7 +418,7 @@
var post_url = $form.attr('action');
if (baseurl.includes('decayingModelMapping')) {
that.injectDataAttributeTypes($form, formData);
} else if (action.includes('able')) {
} else if (action.includes('able')) { // if enable/disable model
// do nothing, form filled already
} else {
that.injectDataModel($form, formData);
@ -840,13 +849,15 @@ ModelTable.prototype = {
{name: 'Model Name'},
{name: 'Org ID'},
{name: 'Description'},
{name: 'Formula'},
{name: 'Parameters',
children: [
{name: 'Tau'},
{name: 'Delta'},
{name: 'Threshold'},
{name: 'Default basescore'},
{name: 'Basescore config'}
{name: 'Basescore config'},
{name: 'Settings'}
]
},
{name: '# Types'},
@ -909,11 +920,11 @@ ModelTable.prototype = {
attr += 'data-'+ k + '=\"' + v + '\" ';
});
}
return '<span class="' + td_class + '" ' + attr + '>' + text + '</span>';
return '<span class="' + td_class + '" ' + attr + '>' + (text !== undefined ? text : '') + '</span>';
},
_gen_td_link: function(url, text, td_class) {
td_class = td_class !== undefined ? td_class : '';
return '<span class="' + td_class + '"><a href="' + url + '">' + text + '</a></span>';
return '<span class="' + td_class + '"><a href="' + url + '">' + (text !== undefined ? text : '') + '</a></span>';
},
_gen_td_buttons: function(model) {
var html_button = '<div style="width: max-content">';
@ -932,6 +943,10 @@ ModelTable.prototype = {
if (!Array.isArray(model.DecayingModel.parameters.base_score_config) && typeof model.DecayingModel.parameters.base_score_config === 'object') {
bs_config_html = jsonToNestedTable(model.DecayingModel.parameters.base_score_config, [], ['table', 'table-condensed', 'table-bordered']);
}
var settings_html = '';
if (!Array.isArray(model.DecayingModel.parameters.settings) && typeof model.DecayingModel.parameters.settings === 'object') {
settings_html = jsonToNestedTable(model.DecayingModel.parameters.settings, [], ['table', 'table-condensed', 'table-bordered']);
}
var is_row_selected = $('#saveForm #save-model-button').data('modelid') == model.DecayingModel.id;
return cells_html = [
this._gen_td('<input type="checkbox" onchange="decayingTool.refreshSaveButton()" style="margin:0" ' + (is_row_selected ? 'checked' : 'disabled') + '></input>', 'DMCheckbox'),
@ -939,6 +954,7 @@ ModelTable.prototype = {
this._gen_td(model.DecayingModel.name, 'DMName'),
this._gen_td_link('/organisations/view/'+model.DecayingModel.org_id, model.DecayingModel.org_id, 'DMOrg'),
this._gen_td(model.DecayingModel.description, 'DMNDescription'),
this._gen_td(model.DecayingModel.formula, 'DMFormula'),
this._gen_td(model.DecayingModel.parameters.tau, 'DMParameterTau'),
this._gen_td(model.DecayingModel.parameters.delta, 'DMParameterDelta'),
this._gen_td(model.DecayingModel.parameters.threshold, 'DMParameterThreshold'),
@ -948,6 +964,11 @@ ModelTable.prototype = {
'DMParameterBasescoreConfig',
{'basescoreconfig': btoa(JSON.stringify(model.DecayingModel.parameters.base_score_config))}
),
this._gen_td(
settings_html,
'DMSettings',
{'basescoreconfig': btoa(JSON.stringify(model.DecayingModel.parameters.settings))}
),
this._gen_td(model.DecayingModel.attribute_types.length, 'DMNumType'),
this._gen_td(model.DecayingModel.enabled ? '<i class="fa fa-check"></i>' : '<i class="fa fa-times"></i>', 'DMEnabled'),
this._gen_td_buttons(model)