Normalize OPD autocomplete suggestions and add one-time cleanup command
This commit is contained in:
parent
56a0fb83a4
commit
47423a7d38
208
app/Commands/NormalizeOpdAutocompleteText.php
Normal file
208
app/Commands/NormalizeOpdAutocompleteText.php
Normal 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), ', ');
|
||||
}
|
||||
}
|
||||
|
|
@ -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()) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user