- Added tests to ensure Redis namespace safety for `db_object_t` and `users_o`. - Implemented `MotorApiCachedResultTest` to validate metadata caching behavior. - Introduced configuration classes for `xlvask_automatic_order_attachment_enabled` and `xlvask_automatic_order_creation_enabled`. - Developed `xlvask_automation_service` with supporting features for usage log evaluation, suggestion building, and order automation.
143 lines
5.5 KiB
PHP
143 lines
5.5 KiB
PHP
<?php
|
|
|
|
namespace classes;
|
|
|
|
/**
|
|
* Ensures additive schema for XL Vask usage-log review state.
|
|
*/
|
|
class xlvask_usage_logs_schema_bootstrap
|
|
{
|
|
private static bool $initialized = false;
|
|
|
|
public static function ensureTables(): void
|
|
{
|
|
if (self::$initialized) {
|
|
return;
|
|
}
|
|
|
|
global $db;
|
|
|
|
if (!isset($db) || !is_object($db) || !method_exists($db, 'query')) {
|
|
return;
|
|
}
|
|
|
|
if (!self::tableExists($db, 'xlvask_usage_logs')) {
|
|
return;
|
|
}
|
|
|
|
self::addColumnIfMissing($db, 'xlvask_usage_logs', 'ignored_at', 'DATETIME NULL AFTER WashItems');
|
|
self::addColumnIfMissing($db, 'xlvask_usage_logs', 'ignored_by', 'INT NULL AFTER ignored_at');
|
|
self::addColumnIfMissing($db, 'xlvask_usage_logs', 'ignored_reason', 'TEXT NULL AFTER ignored_by');
|
|
self::ensureAutomationTables($db);
|
|
|
|
self::$initialized = true;
|
|
}
|
|
|
|
private static function ensureAutomationTables(object $db): void
|
|
{
|
|
$db->query(
|
|
"CREATE TABLE IF NOT EXISTS `xlvask_automation_suggestions` (
|
|
`id` INT NOT NULL AUTO_INCREMENT,
|
|
`usage_log_id` INT NOT NULL,
|
|
`wash_id` VARCHAR(128) NOT NULL,
|
|
`signature_hash` CHAR(64) NOT NULL,
|
|
`signature_json` LONGTEXT NULL,
|
|
`action` VARCHAR(32) NOT NULL,
|
|
`status` VARCHAR(32) NOT NULL DEFAULT 'suggested',
|
|
`confidence` DECIMAL(5,4) NOT NULL DEFAULT 0.0000,
|
|
`source` VARCHAR(32) NOT NULL DEFAULT 'deterministic',
|
|
`matched_order_id` INT NULL,
|
|
`created_order_id` INT NULL,
|
|
`proposed_order_json` LONGTEXT NULL,
|
|
`candidate_order_json` LONGTEXT NULL,
|
|
`reason` TEXT NULL,
|
|
`created_by` INT NULL,
|
|
`decided_by` INT NULL,
|
|
`decided_at` DATETIME NULL,
|
|
`executed_at` DATETIME NULL,
|
|
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
PRIMARY KEY (`id`),
|
|
KEY `idx_xlvask_automation_usage` (`usage_log_id`),
|
|
KEY `idx_xlvask_automation_wash` (`wash_id`),
|
|
KEY `idx_xlvask_automation_signature` (`signature_hash`),
|
|
KEY `idx_xlvask_automation_status` (`status`)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"
|
|
);
|
|
|
|
$db->query(
|
|
"CREATE TABLE IF NOT EXISTS `xlvask_automation_feedback` (
|
|
`id` INT NOT NULL AUTO_INCREMENT,
|
|
`usage_log_id` INT NULL,
|
|
`wash_id` VARCHAR(128) NULL,
|
|
`signature_hash` CHAR(64) NOT NULL,
|
|
`signature_json` LONGTEXT NULL,
|
|
`action` VARCHAR(32) NOT NULL,
|
|
`decision` VARCHAR(32) NOT NULL,
|
|
`order_id` INT NULL,
|
|
`reason` TEXT NULL,
|
|
`created_by` INT NULL,
|
|
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
PRIMARY KEY (`id`),
|
|
KEY `idx_xlvask_feedback_signature_action` (`signature_hash`, `action`),
|
|
KEY `idx_xlvask_feedback_usage` (`usage_log_id`),
|
|
KEY `idx_xlvask_feedback_decision` (`decision`)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"
|
|
);
|
|
|
|
$db->query(
|
|
"CREATE TABLE IF NOT EXISTS `xlvask_automation_openai_cache` (
|
|
`id` INT NOT NULL AUTO_INCREMENT,
|
|
`cache_key` CHAR(64) NOT NULL,
|
|
`schema_name` VARCHAR(96) NOT NULL,
|
|
`input_json` LONGTEXT NOT NULL,
|
|
`result_json` LONGTEXT NOT NULL,
|
|
`hits` INT NOT NULL DEFAULT 0,
|
|
`last_hit_at` DATETIME NULL,
|
|
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
PRIMARY KEY (`id`),
|
|
UNIQUE KEY `uniq_xlvask_openai_cache_key` (`cache_key`),
|
|
KEY `idx_xlvask_openai_cache_schema` (`schema_name`)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"
|
|
);
|
|
}
|
|
|
|
private static function addColumnIfMissing(object $db, string $table, string $column, string $definition): void
|
|
{
|
|
if (!self::columnExists($db, $table, $column)) {
|
|
$db->query("ALTER TABLE `{$table}` ADD COLUMN `{$column}` {$definition}");
|
|
}
|
|
}
|
|
|
|
private static function tableExists(object $db, string $table): bool
|
|
{
|
|
$table = self::escapeIdentifier($table);
|
|
$result = $db->query("SHOW TABLES LIKE '{$table}'");
|
|
|
|
if ($result === false || !is_object($result) || !property_exists($result, 'num_rows')) {
|
|
return false;
|
|
}
|
|
|
|
return (int)$result->num_rows > 0;
|
|
}
|
|
|
|
private static function columnExists(object $db, string $table, string $column): bool
|
|
{
|
|
$table = self::escapeIdentifier($table);
|
|
$column = self::escapeIdentifier($column);
|
|
$result = $db->query("SHOW COLUMNS FROM `{$table}` LIKE '{$column}'");
|
|
|
|
if ($result === false || !is_object($result) || !property_exists($result, 'num_rows')) {
|
|
return false;
|
|
}
|
|
|
|
return (int)$result->num_rows > 0;
|
|
}
|
|
|
|
private static function escapeIdentifier(string $value): string
|
|
{
|
|
return str_replace(['\\', "'", '`'], ['\\\\', "\\'", ''], $value);
|
|
}
|
|
}
|