-
Notifications
You must be signed in to change notification settings - Fork 175
Instrument Scoring
HOME > SETUP > DEVELOPER'S INSTRUMENT GUIDE > HOW TO CODE AN INSTRUMENT > SCORING
If an instrument form should include a scoring algorithm, a scoring script can be coded manually to accompany the form. This scoring script will be executed by Loris automatically when a user saves data entered in the instrument form via their browser. The script should be stored as an executable file in the project/instruments/ directory, and the filename must be called Instrument_name.score
.
It can be coded in any scripting language, but we suggest using our PHP example below. CommentID is passed as the first argument, after which it selects from Instrument_table, performs calculations and updates using the CommentID. To test your scoring script, run it from the tools directory and provide CommentID. Below is a sample scoring script for BMI calculation - this file can be copied from the docs/instruments/ directory:
<?php
/* Test_name Scoring
* Description of the scoring algorithm
* @category Instrument
* @package Test_name
* @author Author
* @license Loris License */
require_once "../tools/generic_includes.php";
require_once "Database.class.inc";
$CommentID = $argv[1];
$db =& Database::singleton();
$query = "SELECT * from 'test_name' WHERE CommentID = :CommentID";
$WhereCriteria = array('CommentID'=>$CommentID);
$result = $db->pselectRow($query, $WhereCriteria);
$scores = array();
//check unit of measurement
if ($result['unit_of_measurement'] == 'standard') {
$query_std = "SELECT bmi_value FROM bmi_standard WHERE height_feet =:hgt_feet AND height_inches=:hgt_inches AND weight=:wgt_pounds";
$Where = array('hgt_feet'=>$result['height_feet'], 'hgt_inches'=>$result['height_inches'],
'wgt_pounds'=>$result['weight_pound']);
$scores['bmi_value'] = $db->pselectOne($query_std, $Where);
} else if ($result['unit_of_measurement'] == metric) {$query_metric = "SELECT bmi_value FROM bmi_metric WHERE height_cms=:hgt_cms'' AND weight_kgs=:wgt_kgs";
$Where = array('hgt_cms'=>$result['height_cms'], 'hgt_kgs'=>$result['weight_kgs']);
$scores['bmi_value'] = $db->pselectOne($query_metric, $Where);}
if ($bmi_value <= 18.5) { $scores['bmi_category'] = 'Underweight';}
else if ($bmi_value > 18.5 && $bmi_value <= 24.9 ) {$scores['bmi_category'] = 'Normal weight';}
else if ($bmi_value >= 25 && $bmi_value <= 29.9) {$scores['bmi_category’] = 'Overweight';}
else if ($bmi_value >= 30) {$scores['bmi_category'] = 'Obesity';}
//save scores
$result = $db->update('test_name', $scores, $WhereCriteria);
If your instrument was coded manually in PHP, then your scoring algorithm can be implemented within your instrument’s PHP file as a function. The function must be named score. Below is an example of what a scoring function typically looks like:
/**
* Example scoring function
*
* @return void
*/
function score() {
if ($this->_determineDataEntryCompletionStatus() == "Incomplete") {
return;
}
$db =& Database::singleton();
//Get raw questions point values
$query = "SELECT * FROM " . $this->table .
" WHERE CommentID='" . $this->getCommentID() . "'";
$record = $db->pselectRow($query, null);
$score = array(
'score_1' => null,
);
$score['score_1'] = true;
if ($record['abc_1'] <> 'yes') {
$score['score_1'] = false;
}
// save the scores
$db->update(
$this->table,
$score,
array('CommentID'=>$this->getCommentID())
);
}
Under construction.
To include instruments in the Reliability module, you must enable ReliabilityInstruments
in the config.xml
in your project directory.
<!-- Instruments for reliability module -->
<ReliabilityInstruments>
<Instrument>
<Testname>bmi</Testname>
<Threshold>0.5</Threshold>
<Displayname>BMI</Displayname>
</Instrument>
</ReliabilityInstruments>
To enable reliability scoring, you must create a reliability instrument called NDB_Reliability_$TESTNAME$_reliability.class.inc
in your project/reliability/ directory.
<?php
/**
* This file contains the NDB_Reliability_bmi
* class
*
* PHP Version 5
*
* @category Reliability_Instrument
* @package BMI
* @author Stella Lee <[email protected]>
* @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3
* @link https://www.github.com/aces/loris/
*/
require_once "NDB_Reliability.class.inc";
/**
* Creates the form elements for the Boston_Diagnostic_Aphasia_Exam instrument
*
* @category Reliability_Instrument
* @package BMI
* @author Stella Lee <[email protected]>
* @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3
* @link https://www.github.com/aces/loris/
*/
class NDB_Reliability_bmi_reliability extends NDB_Reliability
{
function _getAgeInMonths()
{
throw new LorisException("Could not calculate age because don't know if subject or proband");
}
function _getDefaults()
{
$DB =& Database::singleton();
$result = $DB->pselect("SELECT * FROM " . $DB->escape($this->name), array());
//print_r($this->identifier);
if (!empty($this->identifier)) {
// set the bmi_reliability values
$row = $DB->pselectRow(
"SELECT * FROM " . $DB->escape($this->name) . " WHERE CommentID=:cid AND reliability_center_id=:rcid",
array(
'cid' => $this->identifier,
'rcid' => $this->reliability_center_id,
)
);
// Set the defaults
$sites = array(
1 => 'DCC',
2 => 'MTL',
3 => 'OTT',
4 => 'ROM',
);
$initial_table = preg_replace('/_reliability/', '', $this->name);
foreach ($row as $key=>$value) {
$defaults[$key] = $value;
}
$defaults['CommentID'] = "<a href=\"main.php?test_name=reliability_breakdown&instrument=$initial_table&CommentID=$row[CommentID]\">" . $row['CommentID'] . "</a>";
} else {
echo("Identifer empty! No ID has been chosen. Please contact the DCC.<br>");
}
return $defaults;
}
function score()
{
//holds raw question values
$score_record = array();
//holds calculated
$db =& Database::singleton();
// null scores
//$this->_nullScores();
$initial_table = preg_replace('/_reliability/', '', $this->name);
//Get values from both tables (reliability and original)
$query = "SELECT * FROM " . $db->escape($this->name) . " WHERE CommentID=:cid AND reliability_center_id=:rcid";
$query1 = "SELECT * FROM " . $db->escape($initial_table) . " WHERE CommentID=:cid";
$reliability_record = $db->pselectRow($query, array('cid' => $this->identifier, 'rcid' => $this->reliability_center_id));
$instrument_record = $db->pselectRow($query1, array('cid' => $this->identifier));
$mismatches = 0;
$denominator = 0;
$algorithm_mismatches = 0;
$algorithm_denominator = 0;
$algorithm_questions = array(
'height_feet',
'height_inches',
'weight_lbs',
);
foreach ($instrument_record as $key=>$value) {
if (!in_array($key, $algorithm_questions)) {
continue;
}
$rel_key = $key;
$algorithm_question = in_array($key, $algorithm_questions);
$rel_value = $reliability_record[$rel_key];
if (!empty($value) && !empty($rel_value)) {
$denominator++;
if ($algorithm_question) {
$algorithm_denominator++;
}
if ($this->_checkEquivalency($value, $rel_value)) {
continue;
}
$mismatches++;
if ($algorithm_question) {
$algorithm_mismatches++;
}
}
}
// Score the reliability
$reliability['Reliability_score_all_items'] = (($denominator-$mismatches)/$denominator*100);
// Do the update here for the reliability scores - saving the scores
$result = $db->update("{$this->name}", $reliability, array("CommentID" => $this->identifier, 'reliability_center_id' => $this->reliability_center_id));
$result = $db->update("reliability", array("Reliability_score" => round(min($reliability['Reliability_score_all_items'], $reliability['Reliability_score_scored_items']), 3)), array("CommentID" => $this->identifier, "Instrument" => preg_replace("/_reliability$/", "", $this->name), 'reliability_center_id' => $this->reliability_center_id));
// Update the main reliability table with the overall reliability score
$result1 = $db->update("reliability", array('reliability_score' => $reliability['Reliability_score_all_items']), array("CommentID" => $this->identifier));
} // end function score
function _checkEquivalency($val, $rel_val)
{
if ($val === $rel_val) {
return true;
}
return false;
}
function _nullScores()
{
$db =& Database::singleton();
// set the scores to NULL
foreach ($this->scores as $val) {
$scores[$val] =null;
}
// update the scores
$success = $db->update("{$this->name}", $scores, array("CommentID" => $this->identifier, 'reliability_center_id' => $this->reliability_center_id));
return;
}
function bmi_reliability()
{
$this->create_form();
}
function create_form()
{
$this->_addMetadataFields();
$this->form->addElement("static", "reliability_center", "Site of Reliability Test:");
//Scoring header
$this->form->addElement('header', 'instrument_title', "Scoring:");
// $this->form->addElement("static", "Reliability_score_scored_items", "Reliability Score All items(%):");
$this->form->addElement("static", "Reliability_score_all_items", "Reliability Scored items(%):");
$this->form->addElement("static", "CommentID", "CommentID:");
//display test name
$this->form->addElement('header', null, 'BMI');
$this->addNumericElement('height_feet', "Height (feet)");
$this->addNumericElement('height_inches', "Height (inches)");
$this->addNumericElement('weight_lbs', "Weight (lbs)");
} // End function
function _cleanTo3Digits($string)
{
return ereg_replace("[^0-9]", "", substr($string, 0, 3));
}
function _process($values)
{
$DB =& Database::singleton();
$user =& User::singleton();
$row = $DB->pselectRow(
"SELECT * FROM " . $DB->escape($this->name) . " WHERE CommentID=:cid AND reliability_center_id=:rcid",
array(
'cid' => $this->identifier,
'rcid' => $this->reliability_center_id,
)
);
$invalid = $values['invalid'];
$DB->update("reliability", array('invalid' => $invalid), array("CommentID" => $this->identifier, "Instrument" => preg_replace("/_reliability$/", "", $this->name), "reliability_center_id" => $this->reliability_center_id));
unset($values['invalid']);
if ($invalid == "yes") {
$this->form->freeze();
$this->tpl_data['success'] = true;
return;
}
foreach ($values as $key=>$value) {
if ($key == 'Date_taken' || $key == 'DoB_proband') {
$values[$key] = $this->_getDatabaseDate($values[$key]);
}
}
if (empty($row)) {
$values['CommentID'] = $this->identifier;
$values['reliability_center_id'] = $this->reliability_center_id;
// insert the event
$success = $DB->insert($this->name, $values);
} else {
// update the event
$success = $DB->update(
$this->name,
$values,
array(
'CommentID' => $this->identifier,
'reliability_center_id' => $this->reliability_center_id,
)
);
}
$scoreResult = $this->score();
$this->form->freeze();
$this->tpl_data['success'] = true;
}
function _validate($values)
{
$DB =& Database::singleton();
$query = "SELECT count(*) AS counter FROM candidate WHERE CandID=:cid and PSCID=:pid";
$recordsFound = $DB->pselectOne($query, array('cid' => $values['CandID'], 'pid' => $values['PSCID']));
$errors = array();
if ($recordsFound < 1) {
$errors['CandID'] = "Specified DCCID and PSCID do not exist or do not match.";
}
return $errors;
}
function display()
{
if (!$this->form->isFrozen()) {
// hidden values
$this->form->addElement(
'hidden',
'test_name',
$this->name
);
$this->form->addElement(
'hidden',
'subtest',
$this->page
);
$this->form->addElement(
'hidden',
'identifier',
$this->identifier
);
$this->form->addElement(
'hidden',
'reliability_center_id',
$this->reliability_center_id
);
$this->form->addElement(
'submit',
'fire_away',
'Save Data',
'class=button'
);
}
// get the defaults
$localDefaults = $this->_getDefaults();
if (!is_array($localDefaults)) {
$localDefaults = array();
}
// set the quickform object defaults
$this->form->setDefaults(array_merge($this->defaults, $localDefaults));
// trim all values
$this->form->applyFilter('__ALL__', 'trim');
// display the HTML_Quickform object
$smarty = new Smarty_neurodb;
// display the HTML_Quickform object
return $this->form->toHTML();
$renderer =& new HTML_QuickForm_Renderer_Default();
$renderer->setFormTemplate(
"<form{attributes}><table class=\"instrument\">{content}</table></form>"
);
$renderer->setElementTemplate(
"<tr><td class=\"lab\">
<!-- BEGIN required --><span style=\"color: #ff0000\">*</span>
<!-- END required -->{label}</td><td class=\"ele\">
<!-- BEGIN error --><span style=\"color: #ff0000\">{error}</span><br />
<!-- END error -->{element}</td></tr>
"
);
$renderer->setElementTemplate(
"<tr><td class=\"lab\"><i>{label}</i></td>
<td class=\"ele\">\t<i>{element}</i></td></tr>",
"static"
);
$renderer->setHeaderTemplate(
"<tr><th colspan=\"2\"><br><b>{header}</b></th></tr>"
);
$this->form->accept($renderer);
return $renderer->toHtml();
} // end function display
/**
* Wrapper to create a field that only accepts a number, with an
* accompanying status field.
*
* @param string $field The database field in which the response
* will be stored
* @param string $label The question text to display
* @param unknown $options Does not appear to be used?
*
* @return none
*/
function addNumericElement($field, $label, $options = null)
{
$group[] = $this->createText($field, $label);
$this->WrapperNumericElements[$field] = $group[0];
$group[] = $this->createSelect(
$field . "_status",
null,
array(
null => "",
'not_answered' => "Not Answered",
),
array('class' => 'form-control input-sm not-answered')
);
$this->addGroup($group, $field . "_group", $label, null, false);
unset($group);
$this->addGroupRule(
$field . "_group",
array(array(array("Value must be numeric.", 'numeric')))
);
$this->XINRegisterRule(
$field,
array($field . '_status{@}=={@}'),
'This field is required',
$field . '_group'
);
}
}
?>