Normalize OPD autocomplete suggestions and add one-time cleanup command

This commit is contained in:
DevSoftTech Server 2026-04-17 03:31:33 +05:30
parent 56a0fb83a4
commit 47423a7d38
2 changed files with 276 additions and 15 deletions

View File

@ -0,0 +1,208 @@
<?php
namespace App\Commands;
use CodeIgniter\CLI\BaseCommand;
use CodeIgniter\CLI\CLI;
use Config\Database;
class NormalizeOpdAutocompleteText extends BaseCommand
{
protected $group = 'Maintenance';
protected $name = 'opd:normalize-autocomplete-text';
protected $description = 'One-time cleanup: normalize autocomplete text in OPD prescription and keyword master tables.';
protected $usage = 'opd:normalize-autocomplete-text [--apply] [--batch 500]';
protected $options = [
'--apply' => '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), ', ');
}
}

View File

@ -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()) {