From e51b1986aefec6650481cc6e8cce79de53783ba0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CVeLiTi=E2=80=9D?= <“info@veliti.nl”> Date: Wed, 25 Sep 2024 12:34:12 +0200 Subject: [PATCH 1/5] CM89 - bugfix on ignored list --- contract.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contract.php b/contract.php index d97e2ce..6e27d19 100644 --- a/contract.php +++ b/contract.php @@ -185,7 +185,7 @@ foreach($servicetools as $service_tool){ } //get ignore list $ignored_serialnumbers = ''; -if (!empty($ignore_lists) || $ignore_lists != ''){ +if (!empty($ignore_lists)){ foreach($ignore_lists as $list){ $ignored_serialnumbers .= $list.','; } From c9717cbdf946a9c4ee64301dc6de2963eed076e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CVeLiTi=E2=80=9D?= <“info@veliti.nl”> Date: Mon, 30 Sep 2024 10:20:25 +0200 Subject: [PATCH 2/5] CMXX - Initial ML training --- assets/functions.php | 230 ++++++++++++++++++++++++++++++++++++++---- equipment.php | 5 +- maintenance.php | 42 ++++++-- product.php | 3 + products_versions.php | 34 ++++++- 5 files changed, 283 insertions(+), 31 deletions(-) diff --git a/assets/functions.php b/assets/functions.php index ddb464e..d1e9c25 100644 --- a/assets/functions.php +++ b/assets/functions.php @@ -2239,24 +2239,36 @@ $messages = $stmt->fetchAll(PDO::FETCH_ASSOC); // ML data preparations // +++++++++++++++++++++++++++++++++++++++++++++++++++++++ -function traintotalMeasurement($messages){ +function traintotalMeasurement(){ -//total measurement internal array -$total_measurement = []; + include dirname(__FILE__,2).'/settings/settings.php'; -foreach ($messages as $message){ + //total measurement internal array + $total_measurement = []; + + //Connect to DB + $pdo = dbConnect($dbname); + + //GET DATA + $sql = 'SELECT h.rowID, h.description,h.equipmentid,p.productcode,e.hw_version FROM history h JOIN equipment e ON h.equipmentid = e.rowID JOIN products p ON e.productrowid = p.rowID where type="Maintenance_Test" and description like "%doubletestvalues%"'; + $stmt = $pdo->prepare($sql); + $stmt->execute(); + $messages = $stmt->fetchAll(PDO::FETCH_ASSOC); + + foreach ($messages as $message){ //Cleanup input array $dataset = json_decode($message['description'],true); $dataset = $dataset["doubletestvalues"]; foreach ($dataset as $measure){ //Filter out correct measurements if ($measure['pass'] === true){ - $measurementid = $message['equipmentid'].'-'.$message['rowID']; - $total_measurement[$measure['name']][$measurementid] = $measure['measure']; + $hw_version = (!empty($message['hw_version']))? $message['hw_version'] : 'blank'; + $measurementid = $message['rowID']; + $total_measurement[$message['productcode']][$hw_version][$measure['name']][$measurementid] = $measure['measure']; } } } -return $total_measurement; + return $total_measurement; } function statisticalAnalyses($total_measurement){ @@ -2265,20 +2277,24 @@ function statisticalAnalyses($total_measurement){ $total_results = []; //STATISTICAL ANALYSES INTERNAL ARRAY - foreach ($total_measurement as $key => $value){ - $average = $total_results[$key]['average'] = average($value); - $median = $total_results[$key]['median'] = calculateMedian($value); - $stdev = $total_results[$key]['stdev'] = standDeviation($value); - $total_results[$key]['n'] = count($value); + foreach ($total_measurement as $productcode => $versions){ + foreach ($versions as $version => $ver){ - //GET STDEV -/+ - $total_results[$key]['stdev-1'] = $average - $stdev; - $total_results[$key]['stdev+1'] = $average + $stdev; - $total_results[$key]['stdev-2'] = $average - (2*$stdev); - $total_results[$key]['stdev+2'] = $average + (2*$stdev); - $total_results[$key]['stdev-3'] = $average - (3*$stdev); - $total_results[$key]['stdev+3'] = $average + (3*$stdev); - } + foreach ($ver as $measurement => $value){ + $average = $total_results[$productcode][$version][$measurement]['average'] = average($value); + $median = $total_results[$productcode][$version][$measurement]['median'] = calculateMedian($value); + $stdev = $total_results[$productcode][$version][$measurement]['stdev'] = standDeviation($value); + $total_results[$productcode][$version][$measurement]['n'] = count($value); + //GET STDEV -/+ + $total_results[$productcode][$version][$measurement]['stdev-1'] = $average - $stdev; + $total_results[$productcode][$version][$measurement]['stdev+1'] = $average + $stdev; + $total_results[$productcode][$version][$measurement]['stdev-2'] = $average - (2*$stdev); + $total_results[$productcode][$version][$measurement]['stdev+2'] = $average + (2*$stdev); + $total_results[$productcode][$version][$measurement]['stdev-3'] = $average - (3*$stdev); + $total_results[$productcode][$version][$measurement]['stdev+3'] = $average + (3*$stdev); + } + } +} return $total_results; } @@ -2429,4 +2445,178 @@ foreach ($messages as $message){ } } } +} + +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// store measurement data into product_version ++++++++++ +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +function storeMeasurementProduct($total_results, $token){ + + include dirname(__FILE__,2).'/settings/settings.php'; + + $pdo = dbConnect($dbname); + + //Check if product version exists (else create) => store related measurement + foreach ($total_results as $products => $product){ + + //Product level + foreach ($product as $versions => $version){ + + //Version level + //Check version exist + $sql = 'SELECT p.*, pv.rowID as versionID, pv.productrowid as productID FROM products p JOIN products_versions pv ON p.rowID = pv.productrowid WHERE p.productcode = ? and version = ?;'; + $stmt = $pdo->prepare($sql); + $versions = ($versions != 'blank')? $versions : ''; + $stmt->execute([$products,$versions]); + $output = $stmt->fetchAll(PDO::FETCH_ASSOC); + + //Prep data for api call + $api_url = '/v2/products_versions/'; + $measurement = json_encode($version,JSON_UNESCAPED_UNICODE); + + if (!empty($output)){ + //Update version with latest measurement + $data = json_encode(array("rowID" => $output[0]['versionID'], "productrowid" => $output[0]['productID'], "status" => 1, "version" => $versions, "measurement" => $measurement), JSON_UNESCAPED_UNICODE); + ioApi($api_url,$data,$token); + } + else { + //Insert new version + + //GET PRODUCT ID + $sql = 'SELECT * FROM products WHERE productcode = ?'; + $stmt = $pdo->prepare($sql); + $stmt->execute([$products]); + $output = $stmt->fetchAll(PDO::FETCH_ASSOC); + + //CALL API + $data = json_encode(array("productrowid" => $output[0]['rowID'], "status" => 1, "version" => $versions , "measurement" => $measurement), JSON_UNESCAPED_UNICODE); + ioApi($api_url,$data,$token); + } + } + } +} + +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// store measurement data into equipment data ++++++++++ +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++ +function storeMeasurementEquipment($serialnumber){ + + include dirname(__FILE__,2).'/settings/settings.php'; + + //Connect to DB + $pdo = dbConnect($dbname); + + //CHECK FOR SERIALNUMBER PROVIDED + $clause = (!empty($serialnumber) || $serialnumber !='')? 'e.serialnumber = "'.$serialnumber.'" AND': ''; + + //GET DATA + $sql = 'SELECT h.rowID, h.description,h.equipmentid,p.productcode,e.hw_version FROM history h JOIN equipment e ON h.equipmentid = e.rowID JOIN products p ON e.productrowid = p.rowID where '.$clause.' type="Maintenance_Test" and description like "%doubletestvalues%"'; + $stmt = $pdo->prepare($sql); + $stmt->execute(); + $messages = $stmt->fetchAll(PDO::FETCH_ASSOC); + + $tests[] =''; + foreach ($messages as $message){ + $dataset = json_decode($message['description'],true); + $dataset = $dataset["doubletestvalues"]; + foreach ($dataset as $measure){ + + //Filter out correct measurements + if ($measure['pass'] === true){ + $measurementid = $message['productcode'].'||'.$message['hw_version'].'||'.$message['equipmentid'].'||'.$message['rowID']; + $tests[$measurementid] [$measure['name']]= $measure['measure']; + } + } + } + + //COMPARISON -- CHECK DEVIATIONS FROM STANDARD + //LOOP over all test results + foreach ($tests as $test => $test_values){ + + //GET the productcode and version from Test result + if (str_contains($test,'||')){ + + $identification = explode('||',$test); + + //GET RELATED PRODUCT DATA + $sql = 'SELECT pv.measurement FROM products_versions pv JOIN products p ON pv.productrowid = p.rowID WHERE p.productcode = ? AND pv.version = ?'; + $stmt = $pdo->prepare($sql); + $stmt->execute([$identification[0],$identification[1]]); + $product_measurements = $stmt->fetchAll(PDO::FETCH_ASSOC); + $product_measurements = $product_measurements[0]['measurement']; + + if (!empty($product_measurements)){ + //Only run when there is a product version found + $product_measurements = json_decode($product_measurements,true); + + $equipment_watchlist[] = ''; + + //Filter out non array or non objects + if (is_object($test_values) || is_array($test_values)){ + //get individual test_mesurements + foreach($test_values as $test_measurement => $measured_value){ + //Loop over the related product measurements + foreach($product_measurements as $product_measurement => $product_measured_values){ + //Compare measured test with product measured test + if ($test_measurement == $product_measurement){ + //Get the measured value from test + + if (($measured_value <= $product_measured_values['stdev-3']) && ($measured_value >= $product_measured_values['stdev+3'])){ + $equipment_watchlist[$test][] = array( + "measurement" => $test_measurement, + "value" => $measured_value, + "deviation" => 3 + ); + } + elseif ((($measured_value <= $product_measured_values['stdev-2']) && ($measured_value >= $product_measured_values['stdev-3'])) || (($measured_value >= $product_measured_values['stdev+2']) && ($measured_value <= $product_measured_values['stdev+3']))){ + $equipment_watchlist[$test][] = array( + "measurement" => $test_measurement, + "value" => $measured_value, + "deviation" => 2 + ); + } + elseif ((($measured_value <= $product_measured_values['stdev-1']) && ($measured_value >= $product_measured_values['stdev-2'])) || (($measured_value >= $product_measured_values['stdev+1']) && ($measured_value <= $product_measured_values['stdev+2']))){ + $equipment_watchlist[$test][] = array( + "measurement" => $test_measurement, + "value" => $measured_value, + "deviation" => 1 + ); + } + } + } + } + } + } + } + } + //STORE RESULTS IN EQUIPMENT DATA + foreach ($equipment_watchlist as $equipment => $data){ + //GET the equipmentid and historyid from watchlist + if (str_contains($equipment,'||')){ + $identification = explode('||',$equipment); + //json_encode array + $data = json_encode($data,JSON_UNESCAPED_UNICODE); + + //Check if record exists + $sql = 'SELECT * FROM equipment_data WHERE equipmentid = ? and historyid =?'; + $stmt = $pdo->prepare($sql); + $stmt->execute([$identification[2],$identification[3]]); + $equipment_data = $stmt->fetchAll(PDO::FETCH_ASSOC); + + if (!empty($equipment_data)){ + //EXIST UPDATE + $sql = 'UPDATE equipment_data SET measurement = ? WHERE equipmentid = ? and historyid =?'; + } + else { + //EXIST INSERT + $sql = 'INSERT INTO equipment_data (measurement, equipmentid, historyid) VALUES (?,?,?)'; + } + + //EXECUTE QUERY + $stmt = $pdo->prepare($sql); + $stmt->execute([$data, $identification[2],$identification[3]]); + $equipment_data = $stmt->fetchAll(PDO::FETCH_ASSOC); + } + } } \ No newline at end of file diff --git a/equipment.php b/equipment.php index be50548..eeb40e1 100644 --- a/equipment.php +++ b/equipment.php @@ -24,6 +24,7 @@ $update_allowed = isAllowed($page ,$_SESSION['profile'],$_SESSION['permission'], $update_allowed_edit = isAllowed($page_manage ,$_SESSION['profile'],$_SESSION['permission'],'U'); $delete_allowed = isAllowed($page_manage ,$_SESSION['profile'],$_SESSION['permission'],'D'); $create_allowed = isAllowed($page_manage ,$_SESSION['profile'],$_SESSION['permission'],'C'); +$view_product = isAllowed('product' ,$_SESSION['profile'],$_SESSION['permission'],'R'); //GET Details from URL $GET_VALUES = urlGETdetails($_GET) ?? ''; @@ -142,11 +143,11 @@ $view .= '

'.$product_code.'

-

'.$responses->productcode.'

+

'.(($view_product == 1)? ''.$responses->productcode.'':$responses->productcode).'

'.$product_name.'

-

'.$responses->productname.'

+

'.(($view_product == 1)? ''.$responses->productname.'':$responses->productname).'

'; $picture = glob("./assets/images/products/".$responses->productcode.".{jpg,jpeg,png,gif}", GLOB_BRACE); diff --git a/maintenance.php b/maintenance.php index 38e0c06..3d18458 100644 --- a/maintenance.php +++ b/maintenance.php @@ -26,6 +26,16 @@ if ($update_allowed === 1){ //GEOLOCATION UPDATE convertCartest(); } + if (isset($_POST['updateproductmeusurements'])){ + $total_measurement = traintotalMeasurement(); + $total_results = statisticalAnalyses($total_measurement); + storeMeasurementProduct($total_results, $_SESSION['userkey']); + } + if (isset($_POST['updateequipmentmeusurements'])){ + storeMeasurementEquipment(''); + } + + } // Handle success messages @@ -56,21 +66,39 @@ $view .=' $view .= '
'.$general_actions .' + Learning
'; if ($update_allowed === 1){ $view .= '
-
- - - - - - +
+ + +
+
+ + +
+
+ + +
'; } +if ($update_allowed === 1){ + $view .= '
+
+ + +
+
+ + +
+
'; + } $view .= ''; //Output diff --git a/product.php b/product.php index e87f31b..40363b4 100644 --- a/product.php +++ b/product.php @@ -164,6 +164,7 @@ $view .= '
'.$product_version_number.' + '.$product_status.' '.$product_version_version.' '.$product_version_software .' '.$general_actions.' @@ -171,8 +172,10 @@ $view .= '
'; foreach ($product_versions as $version){ + $view .= ' '.$version->rowID.' + '.(($version->status == 1)? ''.$prod_status_1:''.$prod_status_0).' '.$version->version.' '.$version->software.' '.$general_view.' diff --git a/products_versions.php b/products_versions.php index 6f1a69d..503c53e 100644 --- a/products_versions.php +++ b/products_versions.php @@ -132,11 +132,41 @@ $view .= '
'; - if (isset($_GET['rowID']) && $_GET['rowID'] !=''){ + if (isset($_GET['rowID']) && $_GET['rowID'] !='' && !empty($products_versions['measurement'])){ + $measurements = json_decode($products_versions['measurement'],true); + $view .= ' - +
+ + + + + + + + + + + '; + foreach ($measurements as $name => $measurement){ + $view .=' + + + + + + + + '; + } + + + $view .= ' +
TestNAverageMedianSTdev
'.$name.''.$measurement['n'].''.$measurement['average'].''.$measurement['median'].''.$measurement['stdev'].'
+
'; + } $view .= ' From 00f7d4f6baf7514b3e4fe55b7c3515bf6e2c7df8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CVeLiTi=E2=80=9D?= <“info@veliti.nl”> Date: Mon, 30 Sep 2024 15:13:42 +0200 Subject: [PATCH 3/5] CMXX - Add service init Analogvoltage --- api/v1/get/equipment_data.php | 164 +++++++++++++++++++++++++++++++++ api/v2/get/equipment_data.php | 164 +++++++++++++++++++++++++++++++++ settings/systemservicetool.php | 8 +- test.php | 50 ++++------ 4 files changed, 351 insertions(+), 35 deletions(-) create mode 100644 api/v1/get/equipment_data.php create mode 100644 api/v2/get/equipment_data.php diff --git a/api/v1/get/equipment_data.php b/api/v1/get/equipment_data.php new file mode 100644 index 0000000..c982a38 --- /dev/null +++ b/api/v1/get/equipment_data.php @@ -0,0 +1,164 @@ +soldto) || $partner->soldto == ''){$soldto_search = '%';} else {$soldto_search = '-%';} + +//default whereclause +$whereclause = ''; + +switch ($permission) { + case '4': + $whereclause = ''; + break; + case '3': + $whereclause = ''; + break; + case '2': + $condition = '__salesid___'.$partner->salesid.'___soldto___'.substr($partner->soldto, 0, strpos($partner->soldto, "-")).$soldto_search; + $whereclause = 'WHERE e.accounthierarchy like :condition '; + break; + default: + $condition = '__salesid___'.$partner->salesid.'___soldto___'.substr($partner->soldto, 0, strpos($partner->soldto, "-")).$soldto_search.'___shipto___'.substr($partner->shipto, 0, strpos($partner->shipto, "-")).'%___location___'.substr($partner->location, 0, strpos($partner->location, "-")).'%'; + $whereclause = 'WHERE e.accounthierarchy like :condition '; + break; +} +//------------------------------------------ +//NEW ARRAY +//------------------------------------------ +$criterias = []; +$clause = ''; + +//------------------------------------------ +//Check for $_GET variables and build up clause +//------------------------------------------ +if(isset($get_content) && $get_content!=''){ + //GET VARIABLES FROM URL + $requests = explode("&", $get_content); + //Check for keys and values + foreach ($requests as $y){ + $v = explode("=", $y); + //INCLUDE VARIABLES IN ARRAY + $criterias[$v[0]] = $v[1]; + if ($v[0] == 'page' || $v[0] =='p' || $v[0] =='products' || $v[0] =='totals' || $v[0] =='history' || $v[0] =='success_msg' || $v[0] =='download' || $v[0] =='sort'){ + //do nothing + } + elseif ($v[0] == 'serialnumber') { + //build up serialnumber + //check if multiple serialnumbers are provided + if (str_contains($v[1], ',')){ + $inputs = explode(",",$v[1]); + $new_querystring = ''; //empty querystring + $x=0; + foreach($inputs as $input){ + //create key + $new_key = $v[0].'_'.$x; + //inject new key/value to array + $criterias[$new_key] = $input; + $new_querystring .= ':'.$new_key.','; + $x++; + } + //remove obsolete last character from new_querystring + $new_querystring = substr($new_querystring,0, -1); + //add new_querystring to clause + $clause .= ' AND e.serialnumber IN ('.$new_querystring.')'; + //remove original key/value from array + unset($criterias[$v[0]]); + } + else { + $clause .= ' AND e.serialnumber IN (:'.$v[0].')'; + } + } + else {//create clause + $clause .= ' AND '.$v[0].' = :'.$v[0]; + } + } + if ($whereclause == '' && $clause !=''){ + $whereclause = 'WHERE '.substr($clause, 4); + } else { + $whereclause .= $clause; + } +} + + +if (isset($criterias['totals']) && $criterias['totals'] ==''){ +//Request for total rows + $sql = 'SELECT count(*) as count from equipment_data ed '.$whereclause.''; +} +else { + //SQL for Paging + $sql = 'SELECT e.productrowid, e.hw_version, e.serialnumber, ed.rowID, ed.equipmentid, ed.measurement, ed.historyid from equipment e JOIN equipment_data ed ON e.rowID = ed.equipmentid '.$whereclause.' ORDER BY ed.equipmentid, ed.historyid ASC'; +} + +$stmt = $pdo->prepare($sql); + +//------------------------------------------ +//Bind to query +//------------------------------------------ + +if (str_contains($whereclause, ':condition')){ + $stmt->bindValue('condition', $condition, PDO::PARAM_STR); +} + +if (!empty($criterias)){ + foreach ($criterias as $key => $value){ + $key_condition = ':'.$key; + if (str_contains($whereclause, $key_condition)){ + if ($key == 'search'){ + $search_value = '%'.$value.'%'; + $stmt->bindValue($key, $search_value, PDO::PARAM_STR); + } + elseif ($key == 'p'){ + //Do nothing (bug) + } + else { + $stmt->bindValue($key, $value, PDO::PARAM_STR); + } + } + } +} + +//------------------------------------------ +// Debuglog +//------------------------------------------ +if (debug){ + $message = $date.';'.$sql.';'.$username; + debuglog($message); +} +//------------------------------------------ +//Add paging details +//------------------------------------------ +if(isset($criterias['totals']) && $criterias['totals']==''){ + $stmt->execute(); + $messages = $stmt->fetch(); + $messages = $messages[0]; +} +else { + //$current_page = isset($criterias['p']) && is_numeric($criterias['p']) ? (int)$criterias['p'] : 1; + //$stmt->bindValue('page', ($current_page - 1) * $page_rows_equipment, PDO::PARAM_INT); + //$stmt->bindValue('num_products', $page_rows_equipment, PDO::PARAM_INT); + //Excute Query + $stmt->execute(); + //Get results + $messages = $stmt->fetchAll(PDO::FETCH_ASSOC); +} +//------------------------------------------ +//Encrypt results +//------------------------------------------ +$messages = generate_payload($messages); +//------------------------------------------ +//Send results +//------------------------------------------ +echo $messages; + +?> \ No newline at end of file diff --git a/api/v2/get/equipment_data.php b/api/v2/get/equipment_data.php new file mode 100644 index 0000000..0ff3d88 --- /dev/null +++ b/api/v2/get/equipment_data.php @@ -0,0 +1,164 @@ +soldto) || $partner->soldto == ''){$soldto_search = '%';} else {$soldto_search = '-%';} + +//default whereclause +$whereclause = ''; + +switch ($permission) { + case '4': + $whereclause = ''; + break; + case '3': + $whereclause = ''; + break; + case '2': + $condition = '__salesid___'.$partner->salesid.'___soldto___'.substr($partner->soldto, 0, strpos($partner->soldto, "-")).$soldto_search; + $whereclause = 'WHERE e.accounthierarchy like :condition '; + break; + default: + $condition = '__salesid___'.$partner->salesid.'___soldto___'.substr($partner->soldto, 0, strpos($partner->soldto, "-")).$soldto_search.'___shipto___'.substr($partner->shipto, 0, strpos($partner->shipto, "-")).'%___location___'.substr($partner->location, 0, strpos($partner->location, "-")).'%'; + $whereclause = 'WHERE e.accounthierarchy like :condition '; + break; +} +//------------------------------------------ +//NEW ARRAY +//------------------------------------------ +$criterias = []; +$clause = ''; + +//------------------------------------------ +//Check for $_GET variables and build up clause +//------------------------------------------ +if(isset($get_content) && $get_content!=''){ + //GET VARIABLES FROM URL + $requests = explode("&", $get_content); + //Check for keys and values + foreach ($requests as $y){ + $v = explode("=", $y); + //INCLUDE VARIABLES IN ARRAY + $criterias[$v[0]] = $v[1]; + if ($v[0] == 'page' || $v[0] =='p' || $v[0] =='products' || $v[0] =='totals' || $v[0] =='history' || $v[0] =='success_msg' || $v[0] =='download' || $v[0] =='sort'){ + //do nothing + } + elseif ($v[0] == 'serialnumber') { + //build up serialnumber + //check if multiple serialnumbers are provided + if (str_contains($v[1], ',')){ + $inputs = explode(",",$v[1]); + $new_querystring = ''; //empty querystring + $x=0; + foreach($inputs as $input){ + //create key + $new_key = $v[0].'_'.$x; + //inject new key/value to array + $criterias[$new_key] = $input; + $new_querystring .= ':'.$new_key.','; + $x++; + } + //remove obsolete last character from new_querystring + $new_querystring = substr($new_querystring,0, -1); + //add new_querystring to clause + $clause .= ' AND e.serialnumber IN ('.$new_querystring.')'; + //remove original key/value from array + unset($criterias[$v[0]]); + } + else { + $clause .= ' AND e.serialnumber IN (:'.$v[0].')'; + } + } + else {//create clause + $clause .= ' AND '.$v[0].' = :'.$v[0]; + } + } + if ($whereclause == '' && $clause !=''){ + $whereclause = 'WHERE '.substr($clause, 4); + } else { + $whereclause .= $clause; + } +} + + +if (isset($criterias['totals']) && $criterias['totals'] ==''){ +//Request for total rows + $sql = 'SELECT count(*) as count from equipment_data ed '.$whereclause.''; +} +else { + //SQL for Paging + $sql = 'SELECT e.productrowid, e.hw_version, e.serialnumber, ed.rowID, ed.equipmentid, ed.measurement, ed.historyid from equipment e JOIN equipment_data ed ON e.rowID = ed.equipmentid '.$whereclause.' ORDER BY ed.equipmentid, ed.historyid ASC'; +} + +$stmt = $pdo->prepare($sql); + +//------------------------------------------ +//Bind to query +//------------------------------------------ + +if (str_contains($whereclause, ':condition')){ + $stmt->bindValue('condition', $condition, PDO::PARAM_STR); +} + +if (!empty($criterias)){ + foreach ($criterias as $key => $value){ + $key_condition = ':'.$key; + if (str_contains($whereclause, $key_condition)){ + if ($key == 'search'){ + $search_value = '%'.$value.'%'; + $stmt->bindValue($key, $search_value, PDO::PARAM_STR); + } + elseif ($key == 'p'){ + //Do nothing (bug) + } + else { + $stmt->bindValue($key, $value, PDO::PARAM_STR); + } + } + } +} + +//------------------------------------------ +// Debuglog +//------------------------------------------ +if (debug){ + $message = $date.';'.$sql.';'.$username; + debuglog($message); +} +//------------------------------------------ +//Add paging details +//------------------------------------------ +if(isset($criterias['totals']) && $criterias['totals']==''){ + $stmt->execute(); + $messages = $stmt->fetch(); + $messages = $messages[0]; +} +else { + //$current_page = isset($criterias['p']) && is_numeric($criterias['p']) ? (int)$criterias['p'] : 1; + //$stmt->bindValue('page', ($current_page - 1) * $page_rows_equipment, PDO::PARAM_INT); + //$stmt->bindValue('num_products', $page_rows_equipment, PDO::PARAM_INT); + //Excute Query + $stmt->execute(); + //Get results + $messages = $stmt->fetchAll(PDO::FETCH_ASSOC); +} +//------------------------------------------ +//JSON_EnCODE +//------------------------------------------ +$messages = json_encode($messages, JSON_UNESCAPED_UNICODE); +//------------------------------------------ +//Send results +//------------------------------------------ +echo $messages; + +?> \ No newline at end of file diff --git a/settings/systemservicetool.php b/settings/systemservicetool.php index fcea014..5d545e0 100644 --- a/settings/systemservicetool.php +++ b/settings/systemservicetool.php @@ -33,7 +33,9 @@ $init = array( "AnalogVoltageCP_0_NEG_Max"=> -11.5, "AnalogVoltageCP_0_NEG_Min"=> -12.5, "AnalogVoltageCP_2K74_POS_Max"=> 9.2, - "AnalogVoltageCP_2K74_POS_Min"=> 8.7 + "AnalogVoltageCP_2K74_POS_Min"=> 8.7, + "AnalogVoltagePP_EP_Off_Max"=>0.3, + "AnalogVoltagePP_EP_Off_Min"=>-0.5 ), "ATM"=> array( "batteryVoltage_Max"=> 4.9, @@ -51,7 +53,9 @@ $init = array( "AnalogVoltageCP_0_NEG_Max"=> -11.5, "AnalogVoltageCP_0_NEG_Min"=> -12.5, "AnalogVoltageCP_2K74_POS_Max"=> 9.2, - "AnalogVoltageCP_2K74_POS_Min"=> 8.7 + "AnalogVoltageCP_2K74_POS_Min"=> 8.7, + "AnalogVoltagePP_EP_Off_Max"=>0.3, + "AnalogVoltagePP_EP_Off_Min"=>-0.5 ), "ManualURL"=> "https://lms.tss-learning.com/course/view.php?id=60", "termsURL"=> "https://emergency-plug.com/en/terms-and-conditions", diff --git a/test.php b/test.php index 71b71d1..dd0ec71 100644 --- a/test.php +++ b/test.php @@ -9,41 +9,25 @@ include './settings/config.php'; include_once './settings/translations/translations_US.php'; include_once './settings/systemfirmware.php'; -$pdo = dbConnect($dbname); -$sql = 'SELECT * FROM contracts WHERE status = 1'; -$stmt = $pdo->prepare($sql); -$stmt->execute(); -$messages = $stmt->fetchAll(PDO::FETCH_ASSOC); -foreach ($messages as $message){ - //Calculate contract end date - $end_date = date('Y-m-d', strtotime('+'.$message['duration'].' months', strtotime($message['start_date']))); +//define('standard_profile','dashboard,profile,equipments,equipment,histories,history,servicereports,servicereport,firmwaretool,application'); - //Validate if contract end date is in the past change contact status to closed and set users to not active - if (date("Y-m-d") > $end_date){ - //Contract expired -> change status to closed (2) - $sql = 'UPDATE contracts SET status = ? WHERE rowID = ?'; - $stmt = $pdo->prepare($sql); - $stmt->execute([2,$message['rowID']]); - - //CHECK FOR ASSIGNED USER END SET SERVICE TO INACTIVE - foreach (json_decode($message['assigned_users']) as $user_assigned){ +$allviews = ["dashboard","profile","equipments","equipment","equipmentmanage","equipment_item"]; - //CALL TO API FOR General information - $clientsecret = createCommunicationToken($_SESSION['userkey']); - $responses = ioApi('/v2/users/username='.$user_assigned,'',$clientsecret); +$admin_profile = ["dashboard","profile","equipments","equipment"]; - if (!empty($responses)){ - $response = json_decode($responses,true); - //If response is not null update the service flag of the user - if (count($response) != 0){ - $id_exist_user = $response[0]['id']; - $sql = 'UPDATE users SET service = ? WHERE id = ? '; - $stmt = $pdo->prepare($sql); - //Remove serviceflag from user when status is Closed - $stmt->execute(['',$id_exist_user]); - } - } - } +$view = '
'; + +foreach ($allviews as $view){ + + if (in_array($view,$admin_profile)){ + $view .= ''; + } else { + $view .= ''; } -} \ No newline at end of file + +} +$view .= ''; +$view .= '
'; + +echo $view; \ No newline at end of file From 17df4c7bc141a74e525743d7594c85468c824b8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CVeLiTi=E2=80=9D?= <“info@veliti.nl”> Date: Tue, 1 Oct 2024 12:27:40 +0200 Subject: [PATCH 4/5] CMXX - Asset Health --- assets/functions.php | 29 ++++++++++ equipment.php | 68 +++++++++++++++++++++++ settings/translations/translations_DE.php | 6 ++ settings/translations/translations_NL.php | 6 ++ settings/translations/translations_US.php | 6 ++ 5 files changed, 115 insertions(+) diff --git a/assets/functions.php b/assets/functions.php index d1e9c25..7b91872 100644 --- a/assets/functions.php +++ b/assets/functions.php @@ -2619,4 +2619,33 @@ function storeMeasurementEquipment($serialnumber){ $equipment_data = $stmt->fetchAll(PDO::FETCH_ASSOC); } } +} + +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// calculatehealthindex of asset ++++++++++ +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++ +function assetHealthIndex($prof,$pem,$healthdata){ + $healthindex = 100; + //Allowed check + if (isAllowed('equipment_data',$prof,$pem,'R') === 1 && !empty($healthdata)){ + //GET last data + $last_data_measurement = end($healthdata); + foreach (json_decode($last_data_measurement->measurement,true) as $data_measure){ + + switch ($data_measure['deviation']) { + case 1: + $healthindex = $healthindex - $data_measure['deviation']; + break; + case 2: + $healthindex = $healthindex - ($data_measure['deviation']*2); + break; + case 3: + $healthindex = $healthindex - ($data_measure['deviation']*3); + break; + } + } + + } + //Not allowed or no data return 100% health +return $healthindex; } \ No newline at end of file diff --git a/equipment.php b/equipment.php index eeb40e1..37bcfd4 100644 --- a/equipment.php +++ b/equipment.php @@ -25,6 +25,7 @@ $update_allowed_edit = isAllowed($page_manage ,$_SESSION['profile'],$_SESSION['p $delete_allowed = isAllowed($page_manage ,$_SESSION['profile'],$_SESSION['permission'],'D'); $create_allowed = isAllowed($page_manage ,$_SESSION['profile'],$_SESSION['permission'],'C'); $view_product = isAllowed('product' ,$_SESSION['profile'],$_SESSION['permission'],'R'); +$view_history = isAllowed('history' ,$_SESSION['profile'],$_SESSION['permission'],'C'); //GET Details from URL $GET_VALUES = urlGETdetails($_GET) ?? ''; @@ -42,6 +43,14 @@ $history = ioServer($api_url,''); //Decode Payload if (!empty($history)){$history = decode_payload($history);}else{$history = null;} + +//CALL TO API FOR EQUIPMENT DATA +$api_url = '/v1/equipment_data/equipmentid='.$_GET['equipmentID']; +$equipment_data = ioServer($api_url,''); + +//Decode Payload +if (!empty($equipment_data )){$equipment_data = decode_payload($equipment_data );}else{$equipment_data = null;} + //------------------------------ //Variables //------------------------------ @@ -50,6 +59,9 @@ $warrantydate = warrantyStatus($responses->warranty_date); $service_date_due = serviceStatus($responses->service_date); $firmware_status = availableFirmware($responses->sw_version, $responses->hw_version); +//Calculate Asset-Health +$total_score = assetHealthIndex($_SESSION['profile'],$_SESSION['permission'],$equipment_data); + //GetPartnerDetails $partner_data = json_decode($responses->accounthierarchy); $salesid = getPartnerName($partner_data->salesid) ?? $not_specified; @@ -132,6 +144,10 @@ $view .= '

'.$equipment_label3.'

'.$$status_text.'

+
+
+

'.$view_asset_data_ranking.'

+

'.$total_score.'

'.$equipment_label2.'

@@ -265,6 +281,58 @@ $view .= '
'; } + +//Show equipment_data when available and allowed +if (isAllowed('equipment_data',$_SESSION['profile'],$_SESSION['permission'],'R') === 1 && !empty($equipment_data)){ +$view .= '
+
+ '.($view_asset_data_text ?? '').' +
+
+ + + + + + + + + + '; + foreach ($equipment_data as $data){ + $asset_score = 100; + foreach (json_decode($data->measurement,true) as $data_measure){ + + switch ($data_measure['deviation']) { + case 1: + $asset_score = $asset_score - $data_measure['deviation']; + break; + case 2: + $asset_score = $asset_score - ($data_measure['deviation']*2); + break; + case 3: + $asset_score = $asset_score - ($data_measure['deviation']*3); + break; + } + } + + $view .= ' + + + + + '; + } + $view .= ' + +
'.$view_asset_data_rowID.''.$view_asset_data_historyid.''.$view_asset_data_ranking.''.$general_actions.'
'.$data->rowID.''.(($view_history == 1)? ''.$data->historyid.'':$data->historyid).''.$asset_score.''.$general_view.'
+
+ '; +$view .= ' +
+'; +} + if ($update_allowed === 1){ $view .= '
diff --git a/settings/translations/translations_DE.php b/settings/translations/translations_DE.php index d7b6297..5a4885a 100644 --- a/settings/translations/translations_DE.php +++ b/settings/translations/translations_DE.php @@ -150,6 +150,12 @@ $view_asset_servicereport = 'Serviceberichte'; $view_asset_notes = 'Zusätzliche Notizen'; $view_asset_actions = 'Aktionen'; +$view_asset_data_text = 'Vermögenswerte - analyses'; +$view_asset_data_rowID = 'Analyse nummer'; +$view_asset_data_historyid = 'Reference test'; +$view_asset_data_ranking = 'Vermögenswerte - health'; +$view_asset_data = 'Observations'; + $not_specified = 'Nicht angegeben'; $warranty_status = 'Garantiestatus'; diff --git a/settings/translations/translations_NL.php b/settings/translations/translations_NL.php index 7478880..6a8d6f5 100644 --- a/settings/translations/translations_NL.php +++ b/settings/translations/translations_NL.php @@ -150,6 +150,12 @@ $view_asset_servicereport = 'Service rapporten'; $view_asset_notes = 'Extra notities'; $view_asset_actions = 'Acties'; +$view_asset_data_text = 'Activa - analyses'; +$view_asset_data_rowID = 'Analyse nummer'; +$view_asset_data_historyid = 'Gerelateerde test'; +$view_asset_data_ranking = 'Activa - health'; +$view_asset_data = 'Observaties'; + $not_specified = 'Niet gespecificeerd'; $warranty_status = 'Garantie status'; diff --git a/settings/translations/translations_US.php b/settings/translations/translations_US.php index 9bbf915..0103815 100644 --- a/settings/translations/translations_US.php +++ b/settings/translations/translations_US.php @@ -148,6 +148,12 @@ $view_asset_servicereport = 'Service reports'; $view_asset_notes = 'Additional notes'; $view_asset_actions = 'Actions'; +$view_asset_data_text = 'Asset - Test analyses'; +$view_asset_data_rowID = 'Analyses ID'; +$view_asset_data_historyid = 'Related Test'; +$view_asset_data_ranking = 'Asset - health'; +$view_asset_data = 'Observations'; + $not_specified = 'Not specified'; $warranty_status = 'Warranty status'; From bf567ec42603c4a53c8574359464fe603c4135d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CVeLiTi=E2=80=9D?= <“info@veliti.nl”> Date: Wed, 2 Oct 2024 16:57:01 +0200 Subject: [PATCH 5/5] CM69 - Bugfix graph cartest, CMXX - Healthindex update --- account.php | 7 + api/v1/get/equipment_data.php | 6 +- api/v2/get/equipment_data.php | 7 +- api/v2/get/equipment_healthindex.php | 194 +++++++++++++++++++ assets/functions.php | 110 +++++++---- assets/readdevice.js | 203 ++++++++++++++------ buildtool.php | 1 + equipment.php | 23 +-- equipment_data.php | 114 +++++++++++ report_healthindex.php | 219 ++++++++++++++++++++++ settings/settingsmenu.php | 8 +- settings/translations/translations_DE.php | 3 +- settings/translations/translations_NL.php | 3 +- settings/translations/translations_US.php | 3 +- 14 files changed, 776 insertions(+), 125 deletions(-) create mode 100644 api/v2/get/equipment_healthindex.php create mode 100644 equipment_data.php create mode 100644 report_healthindex.php diff --git a/account.php b/account.php index 212a49c..5959e2e 100644 --- a/account.php +++ b/account.php @@ -23,6 +23,7 @@ if (isAllowed($page,$_SESSION['profile'],$_SESSION['permission'],'R') === 0){ $update_allowed = isAllowed($page ,$_SESSION['profile'],$_SESSION['permission'],'U'); $delete_allowed = isAllowed($page ,$_SESSION['profile'],$_SESSION['permission'],'D'); $create_allowed = isAllowed($page ,$_SESSION['profile'],$_SESSION['permission'],'C'); +$healthindex_allowed = isAllowed('report_healthindex' ,$_SESSION['profile'],$_SESSION['permission'],'R'); //GET Details from URL $GET_VALUES = urlGETdetails($_GET) ?? ''; @@ -248,6 +249,11 @@ if ($_SESSION['permission'] == 3 || $_SESSION['permission'] == 4){ $view_users = ' '.$button_partner_assigned_users.''; } +$healthindex =''; +if ($healthindex_allowed === 1){ + $healthindex = ''.$view_asset_data_ranking.''; +} + $view .= '
'.$view_asset_actions.' @@ -256,6 +262,7 @@ $view .= '
'.$button_partner_assigned_contracts.' '.$view_communication.' '.$view_users.' + '.$healthindex.'
'; //OUTPUT diff --git a/api/v1/get/equipment_data.php b/api/v1/get/equipment_data.php index c982a38..3777193 100644 --- a/api/v1/get/equipment_data.php +++ b/api/v1/get/equipment_data.php @@ -79,6 +79,10 @@ if(isset($get_content) && $get_content!=''){ $clause .= ' AND e.serialnumber IN (:'.$v[0].')'; } } + elseif ($v[0] == 'rowid') { + //build up search + $clause .= ' AND ed.rowID = :'.$v[0]; + } else {//create clause $clause .= ' AND '.$v[0].' = :'.$v[0]; } @@ -97,7 +101,7 @@ if (isset($criterias['totals']) && $criterias['totals'] ==''){ } else { //SQL for Paging - $sql = 'SELECT e.productrowid, e.hw_version, e.serialnumber, ed.rowID, ed.equipmentid, ed.measurement, ed.historyid from equipment e JOIN equipment_data ed ON e.rowID = ed.equipmentid '.$whereclause.' ORDER BY ed.equipmentid, ed.historyid ASC'; + $sql = 'SELECT e.productrowid, e.hw_version, e.serialnumber, ed.* from equipment e JOIN equipment_data ed ON e.rowID = ed.equipmentid '.$whereclause.' ORDER BY ed.equipmentid, ed.historyid ASC'; } $stmt = $pdo->prepare($sql); diff --git a/api/v2/get/equipment_data.php b/api/v2/get/equipment_data.php index 0ff3d88..67df24b 100644 --- a/api/v2/get/equipment_data.php +++ b/api/v2/get/equipment_data.php @@ -79,6 +79,10 @@ if(isset($get_content) && $get_content!=''){ $clause .= ' AND e.serialnumber IN (:'.$v[0].')'; } } + elseif ($v[0] == 'rowid') { + //build up search + $clause .= ' AND ed.rowID = :'.$v[0]; + } else {//create clause $clause .= ' AND '.$v[0].' = :'.$v[0]; } @@ -97,7 +101,7 @@ if (isset($criterias['totals']) && $criterias['totals'] ==''){ } else { //SQL for Paging - $sql = 'SELECT e.productrowid, e.hw_version, e.serialnumber, ed.rowID, ed.equipmentid, ed.measurement, ed.historyid from equipment e JOIN equipment_data ed ON e.rowID = ed.equipmentid '.$whereclause.' ORDER BY ed.equipmentid, ed.historyid ASC'; + $sql = 'SELECT e.productrowid, e.hw_version, e.serialnumber, ed.* from equipment e JOIN equipment_data ed ON e.rowID = ed.equipmentid '.$whereclause.' ORDER BY ed.equipmentid, ed.historyid ASC'; } $stmt = $pdo->prepare($sql); @@ -152,6 +156,7 @@ else { //Get results $messages = $stmt->fetchAll(PDO::FETCH_ASSOC); } + //------------------------------------------ //JSON_EnCODE //------------------------------------------ diff --git a/api/v2/get/equipment_healthindex.php b/api/v2/get/equipment_healthindex.php new file mode 100644 index 0000000..e0601f7 --- /dev/null +++ b/api/v2/get/equipment_healthindex.php @@ -0,0 +1,194 @@ +soldto) || $partner->soldto == ''){$soldto_search = '%';} else {$soldto_search = '-%';} + +//default whereclause +$whereclause = ''; + +switch ($permission) { + case '4': + $whereclause = ''; + break; + case '3': + $whereclause = ''; + break; + case '2': + $condition = '__salesid___'.$partner->salesid.'___soldto___'.substr($partner->soldto, 0, strpos($partner->soldto, "-")).$soldto_search; + $whereclause = 'WHERE e.accounthierarchy like :condition '; + break; + default: + $condition = '__salesid___'.$partner->salesid.'___soldto___'.substr($partner->soldto, 0, strpos($partner->soldto, "-")).$soldto_search.'___shipto___'.substr($partner->shipto, 0, strpos($partner->shipto, "-")).'%___location___'.substr($partner->location, 0, strpos($partner->location, "-")).'%'; + $whereclause = 'WHERE e.accounthierarchy like :condition '; + break; +} +//------------------------------------------ +//NEW ARRAY +//------------------------------------------ +$criterias = []; +$clause = ''; + +//------------------------------------------ +//Check for $_GET variables and build up clause +//------------------------------------------ +if(isset($get_content) && $get_content!=''){ + //GET VARIABLES FROM URL + $requests = explode("&", $get_content); + //Check for keys and values + foreach ($requests as $y){ + $v = explode("=", $y); + //INCLUDE VARIABLES IN ARRAY + $criterias[$v[0]] = $v[1]; + if ($v[0] == 'page' || $v[0] =='p' || $v[0] =='products' || $v[0] =='totals' || $v[0] =='history' || $v[0] =='success_msg' || $v[0] =='download' || $v[0] =='sort'){ + //do nothing + } + elseif ($v[0] == 'serialnumber') { + //build up serialnumber + //check if multiple serialnumbers are provided + if (str_contains($v[1], ',')){ + $inputs = explode(",",$v[1]); + $new_querystring = ''; //empty querystring + $x=0; + foreach($inputs as $input){ + //create key + $new_key = $v[0].'_'.$x; + //inject new key/value to array + $criterias[$new_key] = $input; + $new_querystring .= ':'.$new_key.','; + $x++; + } + //remove obsolete last character from new_querystring + $new_querystring = substr($new_querystring,0, -1); + //add new_querystring to clause + $clause .= ' AND e.serialnumber IN ('.$new_querystring.')'; + //remove original key/value from array + unset($criterias[$v[0]]); + } + else { + $clause .= ' AND e.serialnumber IN (:'.$v[0].')'; + } + } + elseif ($v[0] == 'status') { + //Update status based on status + $clause .= ' AND e.'.$v[0].' = :'.$v[0]; + $status = $v[1]; + } + elseif ($v[0] == 'search') { + //build up search + $clause .= ' AND (e.serialnumber like :'.$v[0].' OR ed1.equipmentid like :'.$v[0].')'; + } + elseif ($v[0] == 'partnerid') { + //build up accounthierarchy + $clause .= ' AND e.accounthierarchy like :'.$v[0]; + } + elseif ($v[0] == 'rowid') { + //build up search + $clause .= ' AND ed.rowID = :'.$v[0]; + } + elseif ($v[0] == 'servicedate') { + //build up service coverage + $clause .= ' AND e.service_date <= :'.$v[0]; + } + elseif ($v[0] == 'warrantydate') { + //build up warranty coverage + $clause .= ' AND e.warranty_date >= :'.$v[0]; + } + else {//create clause + $clause .= ' AND '.$v[0].' = :'.$v[0]; + } + } + if ($whereclause == '' && $clause !=''){ + $whereclause = 'WHERE '.substr($clause, 4); + } else { + $whereclause .= $clause; + } +} + + +if (isset($criterias['totals']) && $criterias['totals'] ==''){ +//Request for total rows + $sql = 'SELECT count(*) as count FROM products p JOIN equipment e ON p.rowID = e.productrowid JOIN equipment_data ed1 ON e.rowID = ed1.equipmentid LEFT JOIN equipment_data ed2 ON (ed1.equipmentid = ed2.equipmentid AND ed1.rowID < ed2.rowID) '.$whereclause.' AND ed2.rowID IS NULL'; +} +else { + //SQL for Paging + $sql ='SELECT p.productcode,p.productname,e.productrowid,e.created,e.status,e.accounthierarchy,e.serialnumber,e.hw_version,e.sw_version,e.service_date,e.warranty_date, e.order_ref,e.geolocation, ed1.* FROM products p JOIN equipment e ON p.rowID = e.productrowid JOIN equipment_data ed1 ON e.rowID = ed1.equipmentid LEFT JOIN equipment_data ed2 ON (ed1.equipmentid = ed2.equipmentid AND ed1.rowID < ed2.rowID) '.$whereclause.' AND ed2.rowID IS NULL ORDER BY e.accounthierarchy LIMIT :page,:num_products'; +} + +$stmt = $pdo->prepare($sql); + +//------------------------------------------ +//Bind to query +//------------------------------------------ + +if (str_contains($whereclause, ':condition')){ + $stmt->bindValue('condition', $condition, PDO::PARAM_STR); +} + +if (!empty($criterias)){ + foreach ($criterias as $key => $value){ + $key_condition = ':'.$key; + if (str_contains($whereclause, $key_condition)){ + if ($key == 'search'){ + $search_value = '%'.$value.'%'; + $stmt->bindValue($key, $search_value, PDO::PARAM_STR); + } + elseif ($key == 'partnerid'){ + $search_value = '%"_"'.$value.'-%'; + $stmt->bindValue($key, $search_value, PDO::PARAM_STR); + } + elseif ($key == 'p'){ + //Do nothing (bug) + } + else { + $stmt->bindValue($key, $value, PDO::PARAM_STR); + } + } + } +} + +//------------------------------------------ +// Debuglog +//------------------------------------------ +if (debug){ + $message = $date.';'.$sql.';'.$username; + debuglog($message); +} +//------------------------------------------ +//Add paging details +//------------------------------------------ +if(isset($criterias['totals']) && $criterias['totals']==''){ + $stmt->execute(); + $messages = $stmt->fetch(); + $messages = $messages[0]; +} +else { + $current_page = isset($criterias['p']) && is_numeric($criterias['p']) ? (int)$criterias['p'] : 1; + $stmt->bindValue('page', ($current_page - 1) * $page_rows_equipment, PDO::PARAM_INT); + $stmt->bindValue('num_products', $page_rows_equipment, PDO::PARAM_INT); + //Excute Query + $stmt->execute(); + //Get results + $messages = $stmt->fetchAll(PDO::FETCH_ASSOC); +} + +//------------------------------------------ +//JSON_EnCODE +//------------------------------------------ +$messages = json_encode($messages, JSON_UNESCAPED_UNICODE); +//------------------------------------------ +//Send results +//------------------------------------------ +echo $messages; + +?> \ No newline at end of file diff --git a/assets/functions.php b/assets/functions.php index 7b91872..de5d765 100644 --- a/assets/functions.php +++ b/assets/functions.php @@ -2591,47 +2591,16 @@ function storeMeasurementEquipment($serialnumber){ } } //STORE RESULTS IN EQUIPMENT DATA - foreach ($equipment_watchlist as $equipment => $data){ + foreach ($equipment_watchlist as $equipment => $data){ + //GET the equipmentid and historyid from watchlist if (str_contains($equipment,'||')){ $identification = explode('||',$equipment); - //json_encode array - $data = json_encode($data,JSON_UNESCAPED_UNICODE); + + //Calculate healthindex + $healthindex = 100; - //Check if record exists - $sql = 'SELECT * FROM equipment_data WHERE equipmentid = ? and historyid =?'; - $stmt = $pdo->prepare($sql); - $stmt->execute([$identification[2],$identification[3]]); - $equipment_data = $stmt->fetchAll(PDO::FETCH_ASSOC); - - if (!empty($equipment_data)){ - //EXIST UPDATE - $sql = 'UPDATE equipment_data SET measurement = ? WHERE equipmentid = ? and historyid =?'; - } - else { - //EXIST INSERT - $sql = 'INSERT INTO equipment_data (measurement, equipmentid, historyid) VALUES (?,?,?)'; - } - - //EXECUTE QUERY - $stmt = $pdo->prepare($sql); - $stmt->execute([$data, $identification[2],$identification[3]]); - $equipment_data = $stmt->fetchAll(PDO::FETCH_ASSOC); - } - } -} - -// +++++++++++++++++++++++++++++++++++++++++++++++++++++++ -// calculatehealthindex of asset ++++++++++ -// +++++++++++++++++++++++++++++++++++++++++++++++++++++++ -function assetHealthIndex($prof,$pem,$healthdata){ - $healthindex = 100; - //Allowed check - if (isAllowed('equipment_data',$prof,$pem,'R') === 1 && !empty($healthdata)){ - //GET last data - $last_data_measurement = end($healthdata); - foreach (json_decode($last_data_measurement->measurement,true) as $data_measure){ - + foreach ($data as $data_measure){ switch ($data_measure['deviation']) { case 1: $healthindex = $healthindex - $data_measure['deviation']; @@ -2643,7 +2612,72 @@ function assetHealthIndex($prof,$pem,$healthdata){ $healthindex = $healthindex - ($data_measure['deviation']*3); break; } - } + } + //json_encode array + $data = json_encode($data,JSON_UNESCAPED_UNICODE); + + //Check if record exists + $sql = 'SELECT * FROM equipment_data WHERE equipmentid = ? and historyid =?'; + $stmt = $pdo->prepare($sql); + $stmt->execute([$identification[2],$identification[3]]); + $equipment_data = $stmt->fetchAll(PDO::FETCH_ASSOC); + + if (!empty($equipment_data)){ + //EXIST UPDATE + $sql = 'UPDATE equipment_data SET measurement = ?, healthindex = ? WHERE equipmentid = ? and historyid = ?'; + } + else { + //EXIST INSERT + $sql = 'INSERT INTO equipment_data (measurement, , healthindex, equipmentid, historyid) VALUES (?,?,?,?)'; + } + + //EXECUTE QUERY + $stmt = $pdo->prepare($sql); + $stmt->execute([$data, $healthindex, $identification[2],$identification[3]]); + $equipment_data = $stmt->fetchAll(PDO::FETCH_ASSOC); + } + } +} + +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// calculatehealthindex of asset ++++++++++ +// +++++++++++++++++++++++++++++++++++++++++++++++++++++++ +function assetHealthIndex($prof,$pem,$healthdata,$type){ + + $healthindex = 100; + //Allowed check + if (isAllowed('equipment_data',$prof,$pem,'R') === 1 && !empty($healthdata)){ + + //GET last data based on type returned + //Type + // 0 = $healthdata per equipmentid + // 1 = $healthdata per rowID of equipment_data + switch ($type) { + case 0: + $last_data_measurement = end($healthdata); + break; + default: + $last_data_measurement = $healthdata; + + break; + } + //Ensure $last_data_measurement is an array + $last_data_measurement = (is_object($last_data_measurement))? (array) $last_data_measurement : $last_data_measurement; + + foreach (json_decode($last_data_measurement['measurement'],true) as $data_measure){ + + switch ($data_measure['deviation']) { + case 1: + $healthindex = $healthindex - $data_measure['deviation']; + break; + case 2: + $healthindex = $healthindex - ($data_measure['deviation']*2); + break; + case 3: + $healthindex = $healthindex - ($data_measure['deviation']*3); + break; + } + } } //Not allowed or no data return 100% health diff --git a/assets/readdevice.js b/assets/readdevice.js index b648312..5fd11b8 100644 --- a/assets/readdevice.js +++ b/assets/readdevice.js @@ -2,15 +2,6 @@ var port, textEncoder, writableStreamClosed, writer, historyIndex = -1; const lineHistory = []; maintenanceRun = 0; -// To read and clean device data -const VCP = []; //for VCP values -const VPP1 = []; //for VPP1 values -const VPP2 = []; //for VPP2 values -const VBAT = []; //for VBAT values -const PWM = []; //for PWM values -const STATE = []; //for STATE values -const xChart = []; //x-axis chart values - function progressBar(percentage, message, color){ var readbar = document.getElementById("readBar"); readBar.style.background = color; @@ -239,26 +230,92 @@ const plug_data = document.getElementById("plug_data"); async function appendToTerminal(newStuff) { serialResultsDiv.innerHTML += newStuff; + + const keyword = '$STATUS;STATE'; + const keyword2 = 'PWM'; + + // To read and clean device data + VCP = []; //for VCP values + VPP1 = []; //for VPP1 values + VPP2 = []; //for VPP2 values + VBAT = []; //for VBAT values + PWM = []; //for PWM values + STATE = []; //for STATE values + xChart = []; //x-axis chart values + + // Input string with multiple status reports + const statusString = serialResultsDiv.innerHTML; + let lineCounter = 0; + + // Split the input by newline to get each status report + let statusLines = statusString.split(/\r?\n/); + + // Process each status line and populate the parameter arrays and counters + statusLines.forEach((line) => { + + if (line.includes(keyword) && line.includes(keyword2) && /\d$/.test(line.trim())) { + + const pairs = line.split(';'); // Split by ';' + + // Loop through each pair and split by '=' + pairs.forEach(pair => { + const [key, value] = pair.split('='); + if (key && value !== undefined && key !== "$STATUS") { // Skip '$STATUS' + // Push the value and counter to the corresponding parameter object + if (key === "STATE") { + let state + switch (value) { + case 'FULL_CONNECT': + state = 5; + break; + case 'REACTIVATE': + state = 3; + break; + case 'WAIT_CONNECT': + state = -5; + break; + case 'DISCONNECTED': + state = 0; + break; + default: + state = 0; + break; + } + STATE.push(state); + } else if (key === "VCP") { + VCP.push(value / 1000000); + } else if (key === "VPP1") { + VPP1.push(value / 1000000); + } else if (key === "VPP2") { + VPP2.push(value / 1000000); + } else if (key === "VBAT") { + VBAT.push(value / 1000000); + } else if (key === "PWM") { + PWM.push(value /10); + } + } + + }); + xChart.push(lineCounter); + lineCounter = lineCounter + 1; + } + }); + //vcp_data.value +=newStuff - if (typeof plug_data !== "undefined"){ + if (typeof plug_data !== "undefined"){ var plugdata = {"VCP":{VCP}, "VPP1":{VPP1},"VPP2":{VPP2},"VBAT":{VBAT}, "PWM":{PWM},"STATE":{STATE}, "xChart":{xChart}} plug_data.value = JSON.stringify(plugdata); } - serialOutput +=newStuff; + //serialOutput +=newStuff; } - - function renderChart(){ - var test = serialResultsDiv.innerHTML.split(";"); - test.forEach(getData); - const xValues = xChart; new Chart("chart_VCP", { type: "line", data: { - labels: xValues, + labels: xChart, datasets: [{ label: "VCP", data: VCP, @@ -300,54 +357,76 @@ function renderChart(){ } //Get the RAW data and turn into object -function getData(item) { +function getData() { + const keyword = '$STATUS;STATE'; + const keyword2 = 'PWM'; - let item_array = item.split("="); - let xaxis = xChart.length + 1; + // To read and clean device data + const VCP = []; //for VCP values + const VPP1 = []; //for VPP1 values + const VPP2 = []; //for VPP2 values + const VBAT = []; //for VBAT values + const PWM = []; //for PWM values + const STATE = []; //for STATE values + const xChart = []; //x-axis chart values + + // Input string with multiple status reports + const statusString = serialResultsDiv.innerHTML; + let lineCounter = 0; + + // Split the input by newline to get each status report + let statusLines = statusString.split(/\r?\n/); + + // Process each status line and populate the parameter arrays and counters + statusLines.forEach((line) => { + + if (line.includes(keyword) && line.includes(keyword2) && /\d$/.test(line.trim())) { + + const pairs = line.split(';'); // Split by ';' - if (item_array[0] == "VCP"){ - let vcp = item_array[1] / 1000000; - VCP.push(vcp) - xChart.push(xaxis); - } - if (item_array[0] == "VPP1"){ - let vpp1 = item_array[1] / 1000000; - VPP1.push(vpp1) - } - if (item_array[0] == "VPP2"){ - let vpp2 = item_array[1] / 1000000; - VPP2.push(vpp2) - } - if (item_array[0] == "VBAT"){ - let vbat = item_array[1] / 1000000; - VBAT.push(vbat) - } - if (item_array[0] == "STATE"){ - let state - switch (item_array[1]) { - case 'FULL_CONNECT': - state = 5; - break; - case 'REACTIVATE': - state = 3; - break; - case 'WAIT_CONNECT': - state = -5; - break; - case 'DISCONNECTED': - state = 0; - break; - } - STATE.push(state) - } - if (item_array[0] == "PWM") - { - var check_return = item_array[1].match(/[^\r\n]+/g); - if (check_return.length == 2){ - let pwm = check_return[0] / 10; - PWM.push(pwm) - } + // Loop through each pair and split by '=' + pairs.forEach(pair => { + const [key, value] = pair.split('='); + if (key && value !== undefined && key !== "$STATUS") { // Skip '$STATUS' + // Push the value and counter to the corresponding parameter object + if (key === "STATE") { + let state + switch (value) { + case 'FULL_CONNECT': + state = 5; + break; + case 'REACTIVATE': + state = 3; + break; + case 'WAIT_CONNECT': + state = -5; + break; + case 'DISCONNECTED': + state = 0; + break; + default: + state = 0; + break; + } + STATE.push(state); + } else if (key === "VCP") { + VCP.push(value / 1000000); + } else if (key === "VPP1") { + VPP1.push(value / 1000000); + } else if (key === "VPP2") { + VPP2.push(value / 1000000); + } else if (key === "VBAT") { + VBAT.push(value / 1000000); + } else if (key === "PWM") { + PWM.push(value /10); + } + } + + }); + xChart.push(lineCounter); + lineCounter = lineCounter + 1; } + }); } async function closePort(){ diff --git a/buildtool.php b/buildtool.php index 8968e48..6308a77 100644 --- a/buildtool.php +++ b/buildtool.php @@ -64,6 +64,7 @@ $view .= '
$view .=' +
diff --git a/equipment.php b/equipment.php index 37bcfd4..44a8305 100644 --- a/equipment.php +++ b/equipment.php @@ -59,8 +59,8 @@ $warrantydate = warrantyStatus($responses->warranty_date); $service_date_due = serviceStatus($responses->service_date); $firmware_status = availableFirmware($responses->sw_version, $responses->hw_version); -//Calculate Asset-Health -$total_score = assetHealthIndex($_SESSION['profile'],$_SESSION['permission'],$equipment_data); +//Calculate Healthindex based on last test +$total_score = assetHealthIndex($_SESSION['profile'],$_SESSION['permission'],$equipment_data,0); //GetPartnerDetails $partner_data = json_decode($responses->accounthierarchy); @@ -300,27 +300,12 @@ $view .= '
'; foreach ($equipment_data as $data){ - $asset_score = 100; - foreach (json_decode($data->measurement,true) as $data_measure){ - - switch ($data_measure['deviation']) { - case 1: - $asset_score = $asset_score - $data_measure['deviation']; - break; - case 2: - $asset_score = $asset_score - ($data_measure['deviation']*2); - break; - case 3: - $asset_score = $asset_score - ($data_measure['deviation']*3); - break; - } - } $view .= ' '.$data->rowID.' '.(($view_history == 1)? ''.$data->historyid.'':$data->historyid).' - '.$asset_score.' - '.$general_view.' + '.$data->healthindex.' + '.$general_view.' '; } $view .= ' diff --git a/equipment_data.php b/equipment_data.php new file mode 100644 index 0000000..80e5577 --- /dev/null +++ b/equipment_data.php @@ -0,0 +1,114 @@ + '', + 'equipmentid' => '', + 'historyid' => '', + 'created' => '', + 'createdby' => $_SESSION['username'], + 'measurement' => '' +]; + +$equipmentID = $_GET['equipmentID']??''; + +if (isset($_GET['rowID'])) { + // ID param exists, edit an existing product + //CALL TO API + $api_url = '/v1/equipment_data/rowID='.$_GET['rowID']; + $responses = ioServer($api_url,''); + + //Decode Payload + if (!empty($responses)){$responses = decode_payload($responses);}else{$responses = null;} + + $equipment_data = json_decode(json_encode($responses[0]), true); +} + + +template_header('Equipment_data', 'equipment_data', 'manage'); + +$view =' +
+
+

'.$view_asset_data_text.'

+ '.$button_cancel.' +'; +$view .= '
'; + +$view .= ' + '; + +$view .= '
+
+ +

'.$equipment_data['rowID'].'

+ +

'.$equipment_data['historyid'].'

+ +

'.$equipment_data['healthindex'].'

+ '; + + if (isset($_GET['rowID']) && $_GET['rowID'] !='' && !empty($equipment_data['measurement'])){ + $measurements = json_decode($equipment_data['measurement'],true); + + $view .= ' + +
+ + + + + + + + + + '; + foreach ($measurements as $name => $measurement){ + + $view .=' + + + + + + '; + } + $view .= ' +
TestMeasurementDeviaton
'.$measurement['measurement'].''.$measurement['value'].''.$measurement['deviation'].'
+
'; + + } +$view .= ' +
+
'; + +$view .= '
+
+ + + + +
+
'; +$view .= '
'; + +//Output +echo $view; +template_footer() +?> \ No newline at end of file diff --git a/report_healthindex.php b/report_healthindex.php new file mode 100644 index 0000000..b4c9493 --- /dev/null +++ b/report_healthindex.php @@ -0,0 +1,219 @@ +'.$button_back.'':''; + +/*Check if allowed +if (isAllowed($page,$_SESSION['profile'],$_SESSION['permission'],'R') === 0){ + header('location: index.php'); + exit; +}*/ + +//GET PARAMETERS && STORE in SESSION for FURTHER USE/NAVIGATION +$pagination_page = $_SESSION['p'] = isset($_GET['p']) ? $_GET['p'] : 1; +$status = $_SESSION['status'] = isset($_GET['status']) ? '&status='.$_GET['status'] : ''; +$sort = $_SESSION['sort'] = isset($_GET['sort']) ? '&sort='.$_GET['sort'] : ''; +$search = $_SESSION['search'] = isset($_GET['search']) ? '&search='.$_GET['search'] : ''; +$software = $_SESSION['firmware'] = isset($_GET['firmware']) ? '&firmware='.$_GET['firmware'] : ''; +$servicedate = $_SESSION['servicedate'] = isset($_GET['servicedate']) ? '&servicedate='.$_GET['servicedate'] : ''; +$warrantydate = $_SESSION['warrantydate'] = isset($_GET['warrantydate']) ? '&warrantydate='.$_GET['warrantydate'] : ''; +$partnerid = $_SESSION['partnerid'] = isset($_GET['partnerid']) ? '&partnerid='.$_GET['partnerid'] : ''; +$productselected = $_SESSION['productcode'] = isset($_GET['productcode']) ? '&productcode='.$_GET['productcode'] : ''; +$serialnumber_input = $_SESSION['serialnumber'] = isset($_GET['serialnumber']) ? '&serialnumber='.$_GET['serialnumber'] : ''; + +//GET PARAMETERS FOR FILTERS +$filter = urlGETdetailsFilter($_GET) ?? ''; + +// Determine the URL +$url = 'index.php?page=report_healthindex'.$status.$search.$software.$servicedate.$warrantydate.$partnerid.$sort.$productselected.$serialnumber_input; +//GET Details from URL +$GET_VALUES = urlGETdetails($_GET) ?? ''; +//CALL TO API +$api_url = '/v2/equipment_healthindex/'.$GET_VALUES; +$responses = ioServer($api_url,''); +//Decode Payload +if (!empty($responses)){$responses = json_decode($responses);}else{$responses = null;} + +//Return QueryTotal from API +$api_url = '/v2/equipment_healthindex/'.$GET_VALUES.'&totals='; +$query_total = ioServer($api_url,''); +//Decode Payload +if (!empty($query_total)){$query_total = $query_total;}else{$query_total = null;} + +//Get Products related to assets +$api_url = '/v1/equipments/'.$GET_VALUES.'&products='; +$query_products = ioServer($api_url,''); +//Decode Payload +if (!empty($query_products)){$query_products = decode_payload($query_products);}else{$query_products = null;} + +//BUILD LIST OF PRODUCTS +$product_list = ''; + +if ($query_products != null){ + $product_list = ' + '; +} + +if (isset($_GET['download'])){ + downloadToExcel('equipment_healthindex',$responses); +} + +template_header('Healthindex', 'healthindex','view'); +$view = ' +
+
+ +
+

'.$view_asset_data_ranking.' ('.$query_total.')

+

+
+
+ '.$back_btn_orgin.' +
'; + + +$view .= ' +
'; + +$view .= ' +
+ '.$filter.' +
+ '.$general_filters.' +
+ + '.$product_list.' + + '.$general_filters_clear.' +
+
+ '; + +//SHOW DOWNLOAD TO EXCELL OPTION ONLY TO ADMIN USERS +if ($_SESSION['permission'] == 3 || $_SESSION['permission'] == 4){ + $view .='
+ +
'; +} +$view .= ' +
+
+'; + +$view .= ' +
+
+ + + + + + + + + + + + + + + + '; + + if (empty($responses)){ + + $view .= ' + + + '; + } + +foreach ($responses as $response){ +//Translate status INT to STR +$status_text = 'status'.$response->status.'_text'; +//GetPartnerDetails +$partner_data = json_decode($response->accounthierarchy); +$soldto = getPartnerName($partner_data->soldto) ?? $not_specified; +$shipto = getPartnerName($partner_data->shipto) ?? $not_specified; + +//Additional items +$picture = glob("./assets/images/products/".$response->productcode.".{jpg,jpeg,png,gif}", GLOB_BRACE); +$indicators = overviewIndicators($response->warranty_date,$response->service_date,$response->sw_version,$response->hw_version); + +$view .= ' + + + + + '; +$view .= ' + + + + + '; + } + $view .= ' + +
'.$view_asset_data_ranking.''.$equipment_label1.''.$equipment_label2.''.$equipment_label3.''.$equipment_label4.''.$general_soldto.''.$general_shipto.''.$view_asset_actions.'
'.$message_no_assets.'
'.$response->healthindex.''.$indicators.''.$response->serialnumber.''.$$status_text.''.$response->productcode.'-'.$response->productname.''; + if (!empty($picture)){ + $view .=' + + '; + } +$view .= ' '.$soldto.''.$shipto.''.$general_view .'
+
+
+'; + +$view.=''; +//OUTPUT +echo $view; + +template_footer(); +?> \ No newline at end of file diff --git a/settings/settingsmenu.php b/settings/settingsmenu.php index af56ca5..a62053f 100644 --- a/settings/settingsmenu.php +++ b/settings/settingsmenu.php @@ -15,7 +15,7 @@ $main_menu = array ('dashboard','sales','build','cartests','marketing','equipmen $equipments_sub = array('equipments','servicereports','histories','firmwaretool','equipments_mass_update'); $sales_sub = array('accounts','contracts'); $admin_sub = array('users','communications','partners'); -$reporting_sub = array('report_build','report_usage'); +$reporting_sub = array('report_build','report_healthindex','report_usage'); $settings_sub = array('config','language','log','maintenance'); //URLS @@ -164,6 +164,12 @@ $urls = array( "icon" => "fa-solid fa-magnifying-glass-chart", "name" => "menu_report_build" ), + "report_healthindex" => array( + "url" => "report_healthindex", + "selected" => "report_healthindex", + "icon" => "fa-solid fa-magnifying-glass-chart", + "name" => "menu_report_healthindex" + ), "report_usage" => array( "url" => "report_usage", "selected" => "report_usage", diff --git a/settings/translations/translations_DE.php b/settings/translations/translations_DE.php index 5a4885a..3a24bf2 100644 --- a/settings/translations/translations_DE.php +++ b/settings/translations/translations_DE.php @@ -23,6 +23,7 @@ $menu_build = 'Bau'; $menu_cartest = 'Fahrzeugdatenbank'; $menu_report_main = 'Berichte'; $menu_report_build = 'Bestand und Produktion'; +$menu_report_healthindex= 'Healthindex'; $menu_report_usage = 'Systemnutzung'; $menu_maintenance = 'Maintenance'; @@ -153,7 +154,7 @@ $view_asset_actions = 'Aktionen'; $view_asset_data_text = 'Vermögenswerte - analyses'; $view_asset_data_rowID = 'Analyse nummer'; $view_asset_data_historyid = 'Reference test'; -$view_asset_data_ranking = 'Vermögenswerte - health'; +$view_asset_data_ranking = 'Healthindex'; $view_asset_data = 'Observations'; $not_specified = 'Nicht angegeben'; diff --git a/settings/translations/translations_NL.php b/settings/translations/translations_NL.php index 6a8d6f5..a6a1669 100644 --- a/settings/translations/translations_NL.php +++ b/settings/translations/translations_NL.php @@ -23,6 +23,7 @@ $menu_build = 'Build'; $menu_cartest = 'Auto database'; $menu_report_main = 'Rapporten'; $menu_report_build = 'Voorraad en Productie'; +$menu_report_healthindex= 'Healthindex'; $menu_report_usage = 'Systeemgebruik'; $menu_maintenance = 'Maintenance'; @@ -153,7 +154,7 @@ $view_asset_actions = 'Acties'; $view_asset_data_text = 'Activa - analyses'; $view_asset_data_rowID = 'Analyse nummer'; $view_asset_data_historyid = 'Gerelateerde test'; -$view_asset_data_ranking = 'Activa - health'; +$view_asset_data_ranking = 'Healthindex'; $view_asset_data = 'Observaties'; $not_specified = 'Niet gespecificeerd'; diff --git a/settings/translations/translations_US.php b/settings/translations/translations_US.php index 0103815..8f19e8a 100644 --- a/settings/translations/translations_US.php +++ b/settings/translations/translations_US.php @@ -23,6 +23,7 @@ $menu_build = 'Build'; $menu_cartest = 'Car database'; $menu_report_main = 'Reports'; $menu_report_build = 'Stock and Production'; +$menu_report_healthindex= 'Healthindex'; $menu_report_usage = 'System usage'; $menu_maintenance = 'Maintenance'; @@ -151,7 +152,7 @@ $view_asset_actions = 'Actions'; $view_asset_data_text = 'Asset - Test analyses'; $view_asset_data_rowID = 'Analyses ID'; $view_asset_data_historyid = 'Related Test'; -$view_asset_data_ranking = 'Asset - health'; +$view_asset_data_ranking = 'Healthindex'; $view_asset_data = 'Observations'; $not_specified = 'Not specified';