628 lines
22 KiB
PHP
628 lines
22 KiB
PHP
<?php
|
|
class Gateway extends MY_Controller
|
|
{
|
|
var $db_onedev;
|
|
var $load;
|
|
var $hostname;
|
|
|
|
private $translationAPIUrl = 'https://devone.aplikasi.web.id/one-api/ai-lab-translate/ai_lab/sendToAi';
|
|
|
|
public function index()
|
|
{
|
|
// ini di devcpone
|
|
echo "API BE untuk Gateway AI Lab";
|
|
}
|
|
|
|
public function __construct()
|
|
{
|
|
parent::__construct();
|
|
$this->db_onedev = $this->load->database('onedev', TRUE);
|
|
$this->hostname = "devcpone.aplikasi.web.id";
|
|
}
|
|
|
|
public function translateLabArr($T_OrderHeaderID)
|
|
{
|
|
$sql = "SELECT
|
|
T_OrderDetailID as orderDetailID,
|
|
T_OrderDetailT_TestID as T_TestID,
|
|
T_OrderDetailResult as result,
|
|
T_OrderDetailT_OrderHeaderID as T_OrderHeaderID
|
|
FROM t_orderdetail
|
|
WHERE T_OrderDetailT_OrderHeaderID = ?
|
|
AND T_OrderDetailIsActive = 'Y'
|
|
AND T_OrderDetailValidation = 'Y'
|
|
AND T_OrderDetailResult IS NOT NULL
|
|
";
|
|
|
|
$query = $this->db_onedev->query($sql, array(
|
|
$T_OrderHeaderID
|
|
));
|
|
if (!$query) {
|
|
$message = $this->db_onedev->error();
|
|
$message['qry'] = $this->db_onedev->last_query();
|
|
$this->sys_error($message);
|
|
exit;
|
|
}
|
|
|
|
$array = $query->result_array();
|
|
|
|
$notTranslated = [];
|
|
$translated = [];
|
|
|
|
$allData = [];
|
|
|
|
/* Looping didalam array untuk mengambil order result */
|
|
$inputs = [];
|
|
foreach ($array as $key => $item) {
|
|
/* Kalau pakai isNumeric nanti "0 - 1" tidak terfilter */
|
|
if (!preg_match('/[a-zA-Z]/', $item['result'])) {
|
|
|
|
$notTranslated[] = [
|
|
'orderDetailID' => $item['orderDetailID'],
|
|
'original' => $item['result'],
|
|
'translated' => $item['result'],
|
|
'confidence' => 1
|
|
];
|
|
continue;
|
|
}
|
|
$translated[] = [
|
|
'orderDetailID' => $item['orderDetailID'],
|
|
'original' => $item['result'],
|
|
'translated' => '',
|
|
'confidence' => 0
|
|
];
|
|
|
|
$inputs[] = $item['result'];
|
|
}
|
|
|
|
/* Jika panjang input lebih dari 0 maka lakukan curl */
|
|
if (count($inputs) > 0) {
|
|
$curl = curl_init();
|
|
curl_setopt_array($curl, array(
|
|
CURLOPT_URL => 'https://devone.aplikasi.web.id/one-api/ai-lab-translate/ai_lab/sendToAi',
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
CURLOPT_POST => true,
|
|
CURLOPT_POSTFIELDS => http_build_query(['data' => $inputs]),
|
|
CURLOPT_HTTPHEADER => array(
|
|
'Content-Type: application/x-www-form-urlencoded'
|
|
),
|
|
));
|
|
|
|
$response = curl_exec($curl);
|
|
curl_close($curl);
|
|
$result = json_decode($response, true);
|
|
|
|
$outputs = $result['data']['output'];
|
|
|
|
foreach ($outputs as $key => $output) {
|
|
$translated[$key]['translated'] = $output['translated'];
|
|
$translated[$key]['confidence'] = $output['confidence'];
|
|
}
|
|
}
|
|
|
|
$sqlInsert = "INSERT INTO t_orderdetaillang_ai (
|
|
T_OrderDetailLangAiT_OrderHeaderID,
|
|
T_OrderDetailLangAiT_OrderDetailID,
|
|
T_OrderDetailLangAiNat_LangID,
|
|
T_OrderDetailLangAiResult,
|
|
T_OrderDetailLangAiConfidence
|
|
) VALUES (?,?,?,?,?)
|
|
ON DUPLICATE KEY UPDATE
|
|
T_OrderDetailLangAiResult = VALUES(T_OrderDetailLangAiResult),
|
|
T_OrderDetailLangAiConfidence = VALUES(T_OrderDetailLangAiConfidence),
|
|
T_OrderDetailLangAiLastUpdated = NOW()";
|
|
|
|
$this->db_onedev->trans_start(true); // (true) jika debugging
|
|
|
|
foreach (array_merge($translated, $notTranslated) as $entry) {
|
|
$query = $this->db_onedev->query($sqlInsert, [
|
|
$T_OrderHeaderID,
|
|
$entry['orderDetailID'],
|
|
2,
|
|
$entry['translated'],
|
|
$entry['confidence']
|
|
]);
|
|
|
|
if (!$query) {
|
|
$message = $this->db_onedev->error();
|
|
$message['qry'] = $this->db_onedev->last_query();
|
|
$this->db_onedev->trans_rollback();
|
|
$this->sys_error($message);
|
|
exit;
|
|
}
|
|
}
|
|
|
|
$this->db_onedev->trans_complete();
|
|
$this->sys_ok("OK");
|
|
}
|
|
|
|
public function translateLabStr($T_OrderHeaderID)
|
|
{
|
|
$sql = "SELECT
|
|
T_OrderDetailID as orderDetailID,
|
|
T_OrderDetailT_TestID as T_TestID,
|
|
T_OrderDetailResult as result,
|
|
T_OrderDetailT_OrderHeaderID as T_OrderHeaderID
|
|
FROM t_orderdetail
|
|
WHERE T_OrderDetailT_OrderHeaderID = ?
|
|
AND T_OrderDetailIsActive = 'Y'
|
|
AND T_OrderDetailValidation = 'Y'
|
|
AND T_OrderDetailResult IS NOT NULL ";
|
|
|
|
$query = $this->db_onedev->query($sql, array(
|
|
$T_OrderHeaderID
|
|
));
|
|
if (!$query) {
|
|
$message = $this->db_onedev->error();
|
|
$message['qry'] = $this->db_onedev->last_query();
|
|
$this->sys_error($message);
|
|
exit;
|
|
}
|
|
|
|
$array = $query->result_array();
|
|
|
|
$notTranslated = [];
|
|
$translated = [];
|
|
|
|
$inputs = [];
|
|
foreach ($array as $key => $item) {
|
|
/* Kalau pakai isNumeric nanti "0 - 1" tidak terfilter */
|
|
if (!preg_match('/[a-zA-Z]/', $item['result'])) {
|
|
$notTranslated[] = [
|
|
'orderDetailID' => $item['orderDetailID'],
|
|
'original' => $item['result'],
|
|
'translated' => $item['result'],
|
|
'confidence' => 1
|
|
];
|
|
continue;
|
|
}
|
|
$translated[] = [
|
|
'orderDetailID' => $item['orderDetailID'],
|
|
'original' => $item['result'],
|
|
'translated' => '',
|
|
'confidence' => 0
|
|
];
|
|
|
|
$inputs[] = $item['result'];
|
|
}
|
|
|
|
// Ambil input yang berbeda saja
|
|
$distinctInputs = array_values(array_unique($inputs));
|
|
$distinctInputMap = array_flip($distinctInputs); // Map untuk mengembalikan hasil ke translated
|
|
|
|
$sql = "SELECT Translate_WordTo FROM translate_word
|
|
WHERE Translate_WordNat_LangID = 2
|
|
AND Translate_WordIsActive = 'Y'
|
|
AND Translate_WordFrom = ?";
|
|
|
|
$hasilLookup = [];
|
|
$lookupMap = [];
|
|
|
|
// Lookup ke tabel translate_word, jika sudah ada ambil dari sana
|
|
foreach ($distinctInputs as $key => $item) {
|
|
$query = $this->db_onedev->query($sql, $item);
|
|
if (!$query) {
|
|
$message = $this->db_onedev->error();
|
|
$message['qry'] = $this->db_onedev->last_query();
|
|
$this->sys_error($message);
|
|
exit;
|
|
}
|
|
$result = $query->row_array();
|
|
if ($result) {
|
|
$hasilLookup[] = [
|
|
'original' => $item,
|
|
'translated' => $result['Translate_WordTo'],
|
|
];
|
|
$lookupMap[] = $item;
|
|
unset($distinctInputs[$key]);
|
|
unset($distinctInputMap[$item]);
|
|
}
|
|
}
|
|
|
|
// var_dump($distinctInputs, $distinctInputMap, $hasilLookup);
|
|
|
|
$lookupMap = array_flip($lookupMap);
|
|
// Memasukkan hasil lookup ke translated berdasar lookupMap
|
|
foreach ($translated as &$entry) {
|
|
$original = $entry['original'];
|
|
if (isset($lookupMap[$original])) {
|
|
$entry['translated'] = $hasilLookup[$lookupMap[$original]]['translated'];
|
|
$entry['confidence'] = 1;
|
|
}
|
|
}
|
|
// die(var_dump($hasilLookup, $distinctInputs, $distinctInputMap, $translated));
|
|
|
|
$distictInputsStr = implode(" + ", $distinctInputs);
|
|
|
|
if (count($distinctInputs) > 0) {
|
|
$result = $this->calldevone($distictInputsStr);
|
|
|
|
$outputAi = $result['data']['output'];
|
|
$outputAi = trim($result['data']['output'], '"'); // remove double quote because apis handling
|
|
$outputAiArray = explode(" + ", $outputAi); // separete by +
|
|
$outputAiArray = array_map('trim', $outputAiArray); //remove space
|
|
$outputAiConfidence = $result['data']['confidence'];
|
|
|
|
$distinctInputMap = array_flip($distinctInputs);
|
|
foreach ($translated as &$entry) {
|
|
$original = $entry['original'];
|
|
if (isset($distinctInputMap[$original])) {
|
|
$entry['translated'] = $outputAiArray[$distinctInputMap[$original]];
|
|
$entry['confidence'] = $outputAiConfidence;
|
|
}
|
|
}
|
|
}
|
|
// die(var_dump($outputAi, $translated));
|
|
|
|
$sqlInsert = "INSERT INTO t_orderdetaillang_ai (
|
|
T_OrderDetailLangAiT_OrderHeaderID,
|
|
T_OrderDetailLangAiT_OrderDetailID,
|
|
T_OrderDetailLangAiNat_LangID,
|
|
T_OrderDetailLangAiResult,
|
|
T_OrderDetailLangAiConfidence
|
|
) VALUES (?,?,?,?,?)
|
|
ON DUPLICATE KEY UPDATE
|
|
T_OrderDetailLangAiResult = VALUES(T_OrderDetailLangAiResult),
|
|
T_OrderDetailLangAiConfidence = VALUES(T_OrderDetailLangAiConfidence),
|
|
T_OrderDetailLangAiLastUpdated = NOW()";
|
|
|
|
// $this->db_onedev->trans_start(true);
|
|
$this->db_onedev->trans_start();
|
|
|
|
foreach (array_merge($translated, $notTranslated) as $entry) {
|
|
$query = $this->db_onedev->query($sqlInsert, [
|
|
$T_OrderHeaderID,
|
|
$entry['orderDetailID'],
|
|
2,
|
|
$entry['translated'],
|
|
$entry['confidence']
|
|
]);
|
|
if (!$query) {
|
|
$message = $this->db_onedev->error();
|
|
$message['qry'] = $this->db_onedev->last_query();
|
|
$this->db_onedev->trans_rollback();
|
|
$this->sys_error($message);
|
|
exit;
|
|
}
|
|
}
|
|
$this->db_onedev->trans_complete();
|
|
$this->sys_ok([
|
|
'translated' => $translated,
|
|
'notTranslated' => $notTranslated
|
|
]);
|
|
return true;
|
|
}
|
|
|
|
public function byorderheaderid()
|
|
{
|
|
$startDate = $this->sys_input['headerStartDate'];
|
|
$endDate = $this->sys_input['headerEndDate'];
|
|
|
|
$sql = "SELECT
|
|
T_OrderDetailT_OrderHeaderID as ID,
|
|
T_OrderHeaderDate
|
|
FROM t_orderdetail
|
|
JOIN t_orderheader ON T_OrderDetailT_OrderHeaderID = T_OrderHeaderID
|
|
WHERE T_OrderDetailIsActive = 'Y'
|
|
AND T_OrderDetailValidation = 'Y'
|
|
AND T_OrderHeaderIsActive = 'Y'
|
|
AND T_OrderDetailResult IS NOT NULL
|
|
AND (DATE_FORMAT(T_OrderHeaderDate, '%Y-%m-%d') BETWEEN ? AND ?)
|
|
group by T_OrderDetailT_OrderHeaderID
|
|
";
|
|
|
|
$query = $this->db_onedev->query($sql, [$startDate, $endDate]);
|
|
if (!$query) {
|
|
$message = $this->db_onedev->error();
|
|
$message['qry'] = $this->db_onedev->last_query();
|
|
$this->sys_error($message);
|
|
exit;
|
|
}
|
|
$result = $query->result_array();
|
|
|
|
foreach ($result as $key => $item) {
|
|
$this->translateLabStr($item['ID']);
|
|
}
|
|
$this->sys_ok("Berhasil inject translate ke t_orderdetaillang_ai");
|
|
}
|
|
|
|
public function calldevone($distictInputsStr)
|
|
{
|
|
$curl = curl_init();
|
|
curl_setopt_array($curl, array(
|
|
CURLOPT_URL => 'https://devone.aplikasi.web.id/one-api/ai-lab-translate/ai_lab/sendToAi',
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
CURLOPT_POST => true,
|
|
CURLOPT_POSTFIELDS => json_encode($distictInputsStr), // JSON-encode the string with "data" key
|
|
CURLOPT_HTTPHEADER => array(
|
|
'Content-Type: application/json', // Set content type to JSON
|
|
),
|
|
));
|
|
$response = curl_exec($curl);
|
|
curl_close($curl);
|
|
$result = json_decode($response, true);
|
|
return $result;
|
|
}
|
|
|
|
/*
|
|
*TRIAL*
|
|
*/
|
|
|
|
public function processOrderHeaders($startDate, $endDate, $usingAi = false)
|
|
{
|
|
try {
|
|
if (!isset($startDate) || !isset($endDate)) {
|
|
throw new Exception('Start date and end date are required');
|
|
}
|
|
|
|
$orderHeaders = $this->getOrderHeaders($startDate, $endDate);
|
|
$successOrderHeaders = [];
|
|
$failedOrderHeaders = [];
|
|
|
|
foreach ($orderHeaders as $header) {
|
|
try {
|
|
$this->db_onedev->trans_start();
|
|
// $this->db_onedev->trans_start(true);
|
|
$result = $this->translateLabResults($header['ID'], $usingAi);
|
|
|
|
if ($result['status'] === 'success') {
|
|
$this->db_onedev->trans_complete();
|
|
$successOrderHeaders[] = [
|
|
'orderHeaderId' => $header['ID'],
|
|
'date' => $header['T_OrderHeaderDate'],
|
|
'translationResults' => $result['data']
|
|
];
|
|
} else {
|
|
throw new Exception($result['message']);
|
|
}
|
|
} catch (Exception $e) {
|
|
$this->db_onedev->trans_rollback();
|
|
$failedOrderHeaders[] = [
|
|
'orderHeaderId' => $header['ID'],
|
|
'date' => $header['T_OrderHeaderDate'],
|
|
'error' => $e->getMessage()
|
|
];
|
|
}
|
|
}
|
|
|
|
$resp = [
|
|
'successOrderHeaders' => $successOrderHeaders,
|
|
'failedOrderHeaders' => $failedOrderHeaders,
|
|
'totalProcessed' => count($orderHeaders),
|
|
'totalSuccess' => count($successOrderHeaders),
|
|
'totalFailed' => count($failedOrderHeaders)
|
|
];
|
|
$this->sys_ok($resp);
|
|
} catch (Exception $e) {
|
|
$this->sys_error($e->getMessage());
|
|
}
|
|
}
|
|
|
|
private function getOrderHeaders($startDate, $endDate)
|
|
{
|
|
$sql = "SELECT
|
|
T_OrderDetailT_OrderHeaderID as ID,
|
|
T_OrderHeaderDate
|
|
FROM t_orderdetail
|
|
JOIN t_orderheader ON T_OrderDetailT_OrderHeaderID = T_OrderHeaderID
|
|
WHERE T_OrderDetailIsActive = 'Y'
|
|
AND T_OrderDetailValidation = 'Y'
|
|
AND T_OrderHeaderIsActive = 'Y'
|
|
AND T_OrderDetailResult IS NOT NULL
|
|
AND (DATE_FORMAT(T_OrderHeaderDate, '%Y-%m-%d') BETWEEN ? AND ?)
|
|
group by T_OrderDetailT_OrderHeaderID";
|
|
|
|
$query = $this->db_onedev->query($sql, [$startDate, $endDate]);
|
|
if (!$query) {
|
|
$message = $this->db_onedev->error();
|
|
$message['qry'] = $this->db_onedev->last_query();
|
|
throw new Exception(json_encode($message));
|
|
return;
|
|
}
|
|
return $query->result_array();
|
|
}
|
|
|
|
// translation process
|
|
private function translateLabResults($orderHeaderId, $usingAI)
|
|
{
|
|
try {
|
|
$results = $this->getLabResults($orderHeaderId);
|
|
if (empty($results)) {
|
|
throw new Exception('No lab results found');
|
|
}
|
|
|
|
list($translated, $notTranslated) = $this->processTranslations($results, $usingAI);
|
|
// ? Kalau tidak ditranslate apa tetap insert?
|
|
$this->saveTranslations($orderHeaderId, array_merge($translated, $notTranslated));
|
|
|
|
return [
|
|
'status' => 'success',
|
|
'data' => [
|
|
'translated' => $translated,
|
|
'notTranslated' => $notTranslated
|
|
]
|
|
];
|
|
} catch (Exception $e) {
|
|
return [
|
|
'status' => 'error',
|
|
'message' => $e->getMessage()
|
|
];
|
|
}
|
|
}
|
|
|
|
// get order detail from orderHeader
|
|
private function getLabResults($orderHeaderId)
|
|
{
|
|
return $this->db_onedev
|
|
->select('T_OrderDetailID as orderDetailID, T_OrderDetailT_TestID as T_TestID,
|
|
T_OrderDetailResult as result, T_OrderDetailT_OrderHeaderID as T_OrderHeaderID')
|
|
->from('t_orderdetail')
|
|
->where('T_OrderDetailT_OrderHeaderID', $orderHeaderId)
|
|
->where('T_OrderDetailIsActive', 'Y')
|
|
->where('T_OrderDetailValidation', 'Y')
|
|
->where('T_OrderDetailResult IS NOT NULL')
|
|
->get()
|
|
->result_array();
|
|
}
|
|
|
|
private function processTranslations($results, $usingAI)
|
|
{
|
|
$translated = [];
|
|
$notTranslated = [];
|
|
$distinctInputs = [];
|
|
|
|
foreach ($results as $result) {
|
|
// Skip yang hanya berisi angka
|
|
if (!preg_match('/[a-zA-Z]/', $result['result'])) {
|
|
$notTranslated[] = $this->createTranslationEntry($result, $result['result'], 1);
|
|
continue;
|
|
}
|
|
$distinctInputs[$result['result']] = true;
|
|
$translated[] = $this->createTranslationEntry($result, '', 0);
|
|
}
|
|
|
|
$distinctInputs = array_keys($distinctInputs);
|
|
|
|
// Get existing translations
|
|
$existingTranslations = $this->getExistingTranslations($distinctInputs);
|
|
|
|
if (!empty($existingTranslations)) {
|
|
$translated = $this->applyExistingTranslations($translated, $existingTranslations);
|
|
$distinctInputs = array_diff($distinctInputs, array_keys($existingTranslations));
|
|
}
|
|
|
|
if ($usingAI) {
|
|
// Get new translations from API
|
|
if (!empty($distinctInputs)) {
|
|
$translated = $this->applyNewTranslations($translated, $distinctInputs);
|
|
}
|
|
}
|
|
|
|
return [$translated, $notTranslated];
|
|
}
|
|
|
|
private function createTranslationEntry($result, $translatedText, $confidence)
|
|
{
|
|
return [
|
|
'orderDetailID' => $result['orderDetailID'],
|
|
'original' => $result['result'],
|
|
'translated' => $translatedText,
|
|
'confidence' => $confidence
|
|
];
|
|
}
|
|
|
|
private function getExistingTranslations($inputs)
|
|
{
|
|
$sql = "SELECT Translate_WordTo FROM translate_word
|
|
WHERE Translate_WordNat_LangID = 2
|
|
AND Translate_WordIsActive = 'Y'
|
|
AND Translate_WordFrom = ?";
|
|
|
|
$translations = [];
|
|
foreach ($inputs as $input) {
|
|
|
|
$query = $this->db_onedev->query($sql, $input);
|
|
$result = $query->row_array();
|
|
|
|
if ($result) {
|
|
$translations[$input] = $result['Translate_WordTo'];
|
|
}
|
|
}
|
|
return $translations;
|
|
}
|
|
|
|
private function applyExistingTranslations($translated, $translations)
|
|
{
|
|
foreach ($translated as &$entry) {
|
|
if (isset($translations[$entry['original']])) {
|
|
$entry['translated'] = $translations[$entry['original']];
|
|
$entry['confidence'] = 1;
|
|
}
|
|
}
|
|
return $translated;
|
|
}
|
|
|
|
private function applyNewTranslations($translated, $distinctInputs)
|
|
{
|
|
if (empty($distinctInputs)) {
|
|
return $translated;
|
|
}
|
|
|
|
$inputString = implode(' + ', $distinctInputs);
|
|
$apiResult = $this->callTranslationAPI($inputString);
|
|
|
|
if ($apiResult['status'] === 'success') {
|
|
$translations = array_combine($distinctInputs, $apiResult['translations']);
|
|
|
|
foreach ($translated as &$entry) {
|
|
if (isset($translations[$entry['original']]) && empty($entry['translated'])) {
|
|
$entry['translated'] = $translations[$entry['original']];
|
|
$entry['confidence'] = $apiResult['confidence'];
|
|
}
|
|
}
|
|
}
|
|
|
|
return $translated;
|
|
}
|
|
|
|
private function callTranslationAPI($inputString)
|
|
{
|
|
$curl = curl_init();
|
|
|
|
curl_setopt_array($curl, [
|
|
CURLOPT_URL => $this->translationAPIUrl,
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
CURLOPT_POST => true,
|
|
CURLOPT_POSTFIELDS => json_encode($inputString),
|
|
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
|
|
CURLOPT_TIMEOUT => 30,
|
|
]);
|
|
|
|
$response = curl_exec($curl);
|
|
$error = curl_error($curl);
|
|
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
|
|
|
|
curl_close($curl);
|
|
|
|
if ($error || $httpCode !== 200) {
|
|
return [
|
|
'status' => 'error',
|
|
'message' => "API request failed: $error"
|
|
];
|
|
}
|
|
|
|
$result = json_decode($response, true);
|
|
if (!isset($result['data']['output'])) {
|
|
return [
|
|
'status' => 'error',
|
|
'message' => 'Invalid API response'
|
|
];
|
|
}
|
|
|
|
return [
|
|
'status' => 'success',
|
|
'translations' => explode(' + ', trim($result['data']['output'], '"')),
|
|
'confidence' => $result['data']['confidence'] ?? 0.5
|
|
];
|
|
}
|
|
|
|
private function saveTranslations($orderHeaderId, $translations)
|
|
{
|
|
foreach ($translations as $translation) {
|
|
$data = [
|
|
'T_OrderDetailLangAiT_OrderHeaderID' => $orderHeaderId,
|
|
'T_OrderDetailLangAiT_OrderDetailID' => $translation['orderDetailID'],
|
|
'T_OrderDetailLangAiNat_LangID' => 2,
|
|
'T_OrderDetailLangAiResult' => $translation['translated'],
|
|
'T_OrderDetailLangAiConfidence' => $translation['confidence']
|
|
];
|
|
|
|
$this->db_onedev->replace('t_orderdetaillang_ai', $data);
|
|
|
|
if ($this->db_onedev->error()['code'] !== 0) {
|
|
throw new Exception('Failed to save translation: ' . $this->db_onedev->error()['message']);
|
|
}
|
|
}
|
|
}
|
|
}
|