diff --git a/app/Commands/NormalizeOpdAutocompleteText.php b/app/Commands/NormalizeOpdAutocompleteText.php new file mode 100644 index 0000000..cc091dd --- /dev/null +++ b/app/Commands/NormalizeOpdAutocompleteText.php @@ -0,0 +1,208 @@ + 'Apply updates. If omitted, command runs in dry-run mode only.', + '--batch' => 'Batch size for scan/update loop (default: 500).', + ]; + + public function run(array $params) + { + $db = Database::connect(); + $apply = CLI::getOption('apply') !== null; + $batchSize = max(100, (int) (CLI::getOption('batch') ?? 500)); + + CLI::write('OPD autocomplete text normalization', 'yellow'); + CLI::write('Mode: ' . ($apply ? 'APPLY' : 'DRY-RUN'), $apply ? 'light_green' : 'light_yellow'); + CLI::write('Batch size: ' . $batchSize, 'cyan'); + + $summary = [ + 'scanned' => 0, + 'changed' => 0, + 'updated' => 0, + 'tables' => 0, + ]; + + $summary = $this->processPrescriptionTable($db, $apply, $batchSize, $summary); + $summary = $this->processKeywordTable($db, $apply, $batchSize, $summary); + + CLI::newLine(); + CLI::write('Summary', 'yellow'); + CLI::write('Tables processed: ' . $summary['tables']); + CLI::write('Rows scanned: ' . $summary['scanned']); + CLI::write('Rows with normalized changes: ' . $summary['changed']); + CLI::write('Rows updated: ' . $summary['updated'], $summary['updated'] > 0 ? 'light_green' : 'white'); + + if (! $apply) { + CLI::newLine(); + CLI::write('No DB rows were modified in dry-run mode.', 'light_yellow'); + CLI::write('Run again with --apply to persist changes.', 'light_yellow'); + } + } + + private function processPrescriptionTable($db, bool $apply, int $batchSize, array $summary): array + { + $table = 'opd_prescription'; + if (! $db->tableExists($table)) { + CLI::write('Skipping ' . $table . ': table not found.', 'light_gray'); + return $summary; + } + + $fields = $db->getFieldNames($table) ?? []; + $idField = in_array('id', $fields, true) ? 'id' : null; + if ($idField === null) { + CLI::write('Skipping ' . $table . ': id column not found.', 'red'); + return $summary; + } + + $targets = array_values(array_intersect( + ['Finding_Examinations', 'complaints', 'Complaint', 'provisional_diagnosis', 'Provisional_diagnosis'], + $fields + )); + + if ($targets === []) { + CLI::write('Skipping ' . $table . ': no target text columns found.', 'light_gray'); + return $summary; + } + + CLI::newLine(); + CLI::write('Processing ' . $table . ' columns: ' . implode(', ', $targets), 'cyan'); + $summary['tables']++; + + $offset = 0; + while (true) { + $rows = $db->table($table) + ->select(implode(', ', array_merge([$idField], $targets))) + ->orderBy($idField, 'ASC') + ->limit($batchSize, $offset) + ->get() + ->getResultArray(); + + if ($rows === []) { + break; + } + + $offset += count($rows); + foreach ($rows as $row) { + $summary['scanned']++; + $update = []; + + foreach ($targets as $column) { + $original = (string) ($row[$column] ?? ''); + if ($original === '') { + continue; + } + + $normalized = $this->normalizeAutocompleteText($original); + if ($normalized !== '' && $normalized !== $original) { + $update[$column] = $normalized; + } + } + + if ($update === []) { + continue; + } + + $summary['changed']++; + if ($apply) { + $db->table($table)->where($idField, $row[$idField])->update($update); + $summary['updated']++; + } + } + } + + return $summary; + } + + private function processKeywordTable($db, bool $apply, int $batchSize, array $summary): array + { + $table = 'opd_autotype_keyword_master'; + if (! $db->tableExists($table)) { + CLI::write('Skipping ' . $table . ': table not found.', 'light_gray'); + return $summary; + } + + $fields = $db->getFieldNames($table) ?? []; + if (! in_array('id', $fields, true) || ! in_array('keyword', $fields, true)) { + CLI::write('Skipping ' . $table . ': required columns id/keyword not found.', 'red'); + return $summary; + } + + $hasSection = in_array('section_key', $fields, true); + $sections = ['complaints', 'provisional_diagnosis', 'finding_examinations']; + + CLI::newLine(); + CLI::write('Processing ' . $table . ' keyword column' . ($hasSection ? ' for selected sections only.' : '.'), 'cyan'); + $summary['tables']++; + + $offset = 0; + while (true) { + $builder = $db->table($table)->select($hasSection ? 'id, keyword, section_key' : 'id, keyword'); + if ($hasSection) { + $builder->whereIn('section_key', $sections); + } + + $rows = $builder + ->orderBy('id', 'ASC') + ->limit($batchSize, $offset) + ->get() + ->getResultArray(); + + if ($rows === []) { + break; + } + + $offset += count($rows); + foreach ($rows as $row) { + $summary['scanned']++; + $original = (string) ($row['keyword'] ?? ''); + if ($original === '') { + continue; + } + + $normalized = $this->normalizeAutocompleteText($original); + if ($normalized === '' || $normalized === $original) { + continue; + } + + $summary['changed']++; + if ($apply) { + $db->table($table)->where('id', $row['id'])->update(['keyword' => $normalized]); + $summary['updated']++; + } + } + } + + return $summary; + } + + private function normalizeAutocompleteText(string $text): string + { + $text = str_replace(["\r\n", "\r", "\n"], ' ', $text); + $text = preg_replace('/\s+/', ' ', $text) ?? $text; + + $parts = preg_split('/\s*,\s*/', $text) ?: []; + $clean = []; + foreach ($parts as $part) { + $part = trim((string) $part); + if ($part === '') { + continue; + } + $clean[] = $part; + } + + $normalized = implode(', ', $clean); + return rtrim(trim($normalized), ', '); + } +} diff --git a/app/Controllers/Opd_prescription.php b/app/Controllers/Opd_prescription.php index a900709..85d4491 100644 --- a/app/Controllers/Opd_prescription.php +++ b/app/Controllers/Opd_prescription.php @@ -3973,28 +3973,40 @@ class Opd_prescription extends BaseController $out = []; $seen = []; + $needle = mb_strtolower($this->normalizeAutocompleteSuggestionText($q)); foreach ($rows as $row) { - $name = trim((string) ($row['name'] ?? '')); + $name = $this->normalizeAutocompleteSuggestionText((string) ($row['name'] ?? '')); if ($name === '') { continue; } - $k = strtoupper($name); + if ($needle !== '' && mb_stripos($name, $needle) === false) { + continue; + } + $k = mb_strtoupper($name); if (isset($seen[$k])) { continue; } $seen[$k] = true; + $row['name'] = $name; $out[] = $row; } foreach ($keywordRows as $keyword) { - $k = strtoupper($keyword); - if ($keyword === '' || isset($seen[$k])) { + $name = $this->normalizeAutocompleteSuggestionText((string) $keyword); + if ($name === '') { + continue; + } + if ($needle !== '' && mb_stripos($name, $needle) === false) { + continue; + } + $k = mb_strtoupper($name); + if (isset($seen[$k])) { continue; } $seen[$k] = true; $out[] = [ 'code' => '', - 'name' => $keyword, + 'name' => $name, 'name_hinglish' => '', 'show_in_short' => 0, 'ai_hint' => '', @@ -4038,12 +4050,16 @@ class Opd_prescription extends BaseController $out = []; $seen = []; + $needle = mb_strtolower($this->normalizeAutocompleteSuggestionText($q)); foreach ($rows as $row) { - $name = trim((string) ($row['name'] ?? '')); + $name = $this->normalizeAutocompleteSuggestionText((string) ($row['name'] ?? '')); if ($name === '') { continue; } - $k = strtoupper($name); + if ($needle !== '' && mb_stripos($name, $needle) === false) { + continue; + } + $k = mb_strtoupper($name); if (isset($seen[$k])) { continue; } @@ -4051,12 +4067,19 @@ class Opd_prescription extends BaseController $out[] = ['name' => $name]; } foreach ($keywordRows as $keyword) { - $k = strtoupper($keyword); - if ($keyword === '' || isset($seen[$k])) { + $name = $this->normalizeAutocompleteSuggestionText((string) $keyword); + if ($name === '') { + continue; + } + if ($needle !== '' && mb_stripos($name, $needle) === false) { + continue; + } + $k = mb_strtoupper($name); + if (isset($seen[$k])) { continue; } $seen[$k] = true; - $out[] = ['name' => $keyword]; + $out[] = ['name' => $name]; } return $this->response->setJSON(['rows' => array_slice($out, 0, 20)]); @@ -4093,12 +4116,16 @@ class Opd_prescription extends BaseController $out = []; $seen = []; + $needle = mb_strtolower($this->normalizeAutocompleteSuggestionText($q)); foreach ($rows as $row) { - $text = trim((string) ($row['finding_examinations'] ?? '')); + $text = $this->normalizeAutocompleteSuggestionText((string) ($row['finding_examinations'] ?? '')); if ($text === '') { continue; } - $k = strtoupper($text); + if ($needle !== '' && mb_stripos($text, $needle) === false) { + continue; + } + $k = mb_strtoupper($text); if (isset($seen[$k])) { continue; } @@ -4106,17 +4133,43 @@ class Opd_prescription extends BaseController $out[] = ['finding_examinations' => $text]; } foreach ($keywordRows as $keyword) { - $k = strtoupper($keyword); - if ($keyword === '' || isset($seen[$k])) { + $text = $this->normalizeAutocompleteSuggestionText((string) $keyword); + if ($text === '') { + continue; + } + if ($needle !== '' && mb_stripos($text, $needle) === false) { + continue; + } + $k = mb_strtoupper($text); + if (isset($seen[$k])) { continue; } $seen[$k] = true; - $out[] = ['finding_examinations' => $keyword]; + $out[] = ['finding_examinations' => $text]; } return $this->response->setJSON(['rows' => array_slice($out, 0, 20)]); } + private function normalizeAutocompleteSuggestionText(string $text): string + { + $text = str_replace(["\r\n", "\r", "\n"], ' ', $text); + $text = preg_replace('/\s+/', ' ', $text) ?? $text; + + $parts = preg_split('/\s*,\s*/', $text) ?: []; + $cleanParts = []; + foreach ($parts as $part) { + $part = trim((string) $part); + if ($part === '') { + continue; + } + $cleanParts[] = $part; + } + + $normalized = implode(', ', $cleanParts); + return rtrim(trim($normalized), ', '); + } + public function autotype_keyword_save() { if (! $this->request->isAJAX()) {