808 lines
25 KiB
TypeScript
808 lines
25 KiB
TypeScript
|
|
/**
|
|||
|
|
* [BE-AIAUTO-001~002] AI决策自动化配置服务
|
|||
|
|
* 负责管理AI决策自动执行的配置、阈值和等级
|
|||
|
|
* AI注意: 所有自动执行决策必须通过此服务校验
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
import db from '../config/database';
|
|||
|
|
import { logger } from '../utils/logger';
|
|||
|
|
import { EventBusService } from './EventBusService';
|
|||
|
|
import { RedisService } from './RedisService';
|
|||
|
|
|
|||
|
|
// 自动化等级 (L1-L4)
|
|||
|
|
export type AutomationLevel = 'L1' | 'L2' | 'L3' | 'L4';
|
|||
|
|
|
|||
|
|
// 风险等级
|
|||
|
|
export type RiskLevel = 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
|
|||
|
|
|
|||
|
|
// 决策模块类型
|
|||
|
|
export type DecisionModule =
|
|||
|
|
| 'PRICING' // 定价模块
|
|||
|
|
| 'INVENTORY' // 库存模块
|
|||
|
|
| 'AD_OPTIMIZE' // 广告优化
|
|||
|
|
| 'PRODUCT_SELECT' // 选品模块
|
|||
|
|
| 'LOGISTICS' // 物流模块
|
|||
|
|
| 'RISK_CONTROL' // 风控模块
|
|||
|
|
| 'CUSTOMER_SERVICE' // 客服模块
|
|||
|
|
| 'SETTLEMENT'; // 结算模块
|
|||
|
|
|
|||
|
|
// 自动执行配置实体
|
|||
|
|
export interface AutoExecutionConfig {
|
|||
|
|
id: string;
|
|||
|
|
tenant_id: string;
|
|||
|
|
shop_id: string;
|
|||
|
|
module: DecisionModule;
|
|||
|
|
|
|||
|
|
// 自动化等级
|
|||
|
|
automation_level: AutomationLevel;
|
|||
|
|
|
|||
|
|
// 置信度阈值配置
|
|||
|
|
confidence_thresholds: {
|
|||
|
|
auto_execute: number; // 自动执行阈值
|
|||
|
|
pending_review: number; // 待审核阈值
|
|||
|
|
auto_reject: number; // 自动拒绝阈值
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 风险限制配置
|
|||
|
|
risk_limits: {
|
|||
|
|
max_amount: number; // 最大金额限制
|
|||
|
|
max_quantity: number; // 最大数量限制
|
|||
|
|
allowed_risk_levels: RiskLevel[]; // 允许的风险等级
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 时间限制
|
|||
|
|
time_restrictions: {
|
|||
|
|
allowed_hours: number[]; // 允许执行的小时 (0-23)
|
|||
|
|
excluded_dates: string[]; // 排除的日期 (YYYY-MM-DD)
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 回滚配置
|
|||
|
|
rollback_config: {
|
|||
|
|
enabled: boolean;
|
|||
|
|
max_attempts: number;
|
|||
|
|
cooldown_minutes: number;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 通知配置
|
|||
|
|
notification_config: {
|
|||
|
|
on_execute: boolean;
|
|||
|
|
on_fail: boolean;
|
|||
|
|
on_rollback: boolean;
|
|||
|
|
channels: ('EMAIL' | 'SMS' | 'WEBHOOK')[];
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 统计数据
|
|||
|
|
statistics: {
|
|||
|
|
total_executions: number;
|
|||
|
|
success_count: number;
|
|||
|
|
fail_count: number;
|
|||
|
|
rollback_count: number;
|
|||
|
|
avg_confidence: number;
|
|||
|
|
last_execution_at?: Date;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 状态
|
|||
|
|
status: 'ACTIVE' | 'INACTIVE' | 'SUSPENDED';
|
|||
|
|
|
|||
|
|
// 审计
|
|||
|
|
created_by: string;
|
|||
|
|
updated_by: string;
|
|||
|
|
created_at: Date;
|
|||
|
|
updated_at: Date;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 执行请求
|
|||
|
|
export interface ExecutionRequest {
|
|||
|
|
tenant_id: string;
|
|||
|
|
shop_id: string;
|
|||
|
|
module: DecisionModule;
|
|||
|
|
decision_id: string;
|
|||
|
|
confidence: number;
|
|||
|
|
risk_level: RiskLevel;
|
|||
|
|
estimated_impact: {
|
|||
|
|
amount?: number;
|
|||
|
|
quantity?: number;
|
|||
|
|
};
|
|||
|
|
metadata?: Record<string, any>;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 执行结果
|
|||
|
|
export interface ExecutionResult {
|
|||
|
|
allowed: boolean;
|
|||
|
|
reason: string;
|
|||
|
|
required_action: 'AUTO_EXECUTE' | 'PENDING_REVIEW' | 'AUTO_REJECT';
|
|||
|
|
config_snapshot: Partial<AutoExecutionConfig>;
|
|||
|
|
warnings: string[];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 等级演进记录
|
|||
|
|
export interface LevelEvolutionRecord {
|
|||
|
|
id: string;
|
|||
|
|
tenant_id: string;
|
|||
|
|
shop_id: string;
|
|||
|
|
module: DecisionModule;
|
|||
|
|
from_level: AutomationLevel;
|
|||
|
|
to_level: AutomationLevel;
|
|||
|
|
reason: string;
|
|||
|
|
metrics: {
|
|||
|
|
success_rate: number;
|
|||
|
|
total_executions: number;
|
|||
|
|
avg_confidence: number;
|
|||
|
|
};
|
|||
|
|
approved_by: string;
|
|||
|
|
created_at: Date;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 模块默认配置
|
|||
|
|
const MODULE_DEFAULT_CONFIGS: Record<DecisionModule, Partial<AutoExecutionConfig>> = {
|
|||
|
|
PRICING: {
|
|||
|
|
confidence_thresholds: { auto_execute: 0.85, pending_review: 0.60, auto_reject: 0.40 },
|
|||
|
|
risk_limits: { max_amount: 5000, max_quantity: 100, allowed_risk_levels: ['LOW', 'MEDIUM'] },
|
|||
|
|
},
|
|||
|
|
INVENTORY: {
|
|||
|
|
confidence_thresholds: { auto_execute: 0.90, pending_review: 0.70, auto_reject: 0.50 },
|
|||
|
|
risk_limits: { max_amount: 10000, max_quantity: 500, allowed_risk_levels: ['LOW', 'MEDIUM'] },
|
|||
|
|
},
|
|||
|
|
AD_OPTIMIZE: {
|
|||
|
|
confidence_thresholds: { auto_execute: 0.80, pending_review: 0.55, auto_reject: 0.35 },
|
|||
|
|
risk_limits: { max_amount: 2000, max_quantity: 50, allowed_risk_levels: ['LOW', 'MEDIUM', 'HIGH'] },
|
|||
|
|
},
|
|||
|
|
PRODUCT_SELECT: {
|
|||
|
|
confidence_thresholds: { auto_execute: 0.75, pending_review: 0.50, auto_reject: 0.30 },
|
|||
|
|
risk_limits: { max_amount: 3000, max_quantity: 200, allowed_risk_levels: ['LOW', 'MEDIUM'] },
|
|||
|
|
},
|
|||
|
|
LOGISTICS: {
|
|||
|
|
confidence_thresholds: { auto_execute: 0.88, pending_review: 0.65, auto_reject: 0.45 },
|
|||
|
|
risk_limits: { max_amount: 1000, max_quantity: 100, allowed_risk_levels: ['LOW', 'MEDIUM'] },
|
|||
|
|
},
|
|||
|
|
RISK_CONTROL: {
|
|||
|
|
confidence_thresholds: { auto_execute: 0.95, pending_review: 0.80, auto_reject: 0.60 },
|
|||
|
|
risk_limits: { max_amount: 50000, max_quantity: 1000, allowed_risk_levels: ['LOW'] },
|
|||
|
|
},
|
|||
|
|
CUSTOMER_SERVICE: {
|
|||
|
|
confidence_thresholds: { auto_execute: 0.82, pending_review: 0.60, auto_reject: 0.40 },
|
|||
|
|
risk_limits: { max_amount: 500, max_quantity: 10, allowed_risk_levels: ['LOW', 'MEDIUM'] },
|
|||
|
|
},
|
|||
|
|
SETTLEMENT: {
|
|||
|
|
confidence_thresholds: { auto_execute: 0.98, pending_review: 0.90, auto_reject: 0.70 },
|
|||
|
|
risk_limits: { max_amount: 100000, max_quantity: 100, allowed_risk_levels: ['LOW'] },
|
|||
|
|
},
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 等级能力矩阵
|
|||
|
|
const LEVEL_CAPABILITIES: Record<AutomationLevel, {
|
|||
|
|
max_risk_level: RiskLevel;
|
|||
|
|
max_amount_multiplier: number;
|
|||
|
|
requires_approval: boolean;
|
|||
|
|
description: string;
|
|||
|
|
}> = {
|
|||
|
|
L1: {
|
|||
|
|
max_risk_level: 'LOW',
|
|||
|
|
max_amount_multiplier: 0.5,
|
|||
|
|
requires_approval: true,
|
|||
|
|
description: '仅建议,所有操作需人工确认',
|
|||
|
|
},
|
|||
|
|
L2: {
|
|||
|
|
max_risk_level: 'MEDIUM',
|
|||
|
|
max_amount_multiplier: 1.0,
|
|||
|
|
requires_approval: false,
|
|||
|
|
description: '低风险操作可自动执行,中高风险需审核',
|
|||
|
|
},
|
|||
|
|
L3: {
|
|||
|
|
max_risk_level: 'HIGH',
|
|||
|
|
max_amount_multiplier: 2.0,
|
|||
|
|
requires_approval: false,
|
|||
|
|
description: '大部分操作可自动执行,仅高风险需审核',
|
|||
|
|
},
|
|||
|
|
L4: {
|
|||
|
|
max_risk_level: 'CRITICAL',
|
|||
|
|
max_amount_multiplier: 5.0,
|
|||
|
|
requires_approval: false,
|
|||
|
|
description: '全自动化,包含关键操作,需严格监控',
|
|||
|
|
},
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
export class AutoExecutionConfigService {
|
|||
|
|
private static readonly TABLE_NAME = 'cf_auto_execution_config';
|
|||
|
|
private static readonly EVOLUTION_TABLE = 'cf_automation_level_evolution';
|
|||
|
|
private static readonly CACHE_PREFIX = 'auto_exec_config:';
|
|||
|
|
private static readonly CACHE_TTL = 1800; // 30分钟
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* [BE-AIAUTO-001] 初始化数据库表
|
|||
|
|
*/
|
|||
|
|
static async initTables() {
|
|||
|
|
// 主配置表
|
|||
|
|
const hasTable = await db.schema.hasTable(this.TABLE_NAME);
|
|||
|
|
if (!hasTable) {
|
|||
|
|
logger.info(`Creating ${this.TABLE_NAME} table...`);
|
|||
|
|
await db.schema.createTable(this.TABLE_NAME, (table) => {
|
|||
|
|
table.string('id', 50).primary();
|
|||
|
|
table.string('tenant_id', 50).notNullable().index();
|
|||
|
|
table.string('shop_id', 50).notNullable().index();
|
|||
|
|
table.enum('module', [
|
|||
|
|
'PRICING', 'INVENTORY', 'AD_OPTIMIZE', 'PRODUCT_SELECT',
|
|||
|
|
'LOGISTICS', 'RISK_CONTROL', 'CUSTOMER_SERVICE', 'SETTLEMENT'
|
|||
|
|
]).notNullable();
|
|||
|
|
|
|||
|
|
// 自动化等级
|
|||
|
|
table.enum('automation_level', ['L1', 'L2', 'L3', 'L4']).notNullable().defaultTo('L1');
|
|||
|
|
|
|||
|
|
// JSON配置字段
|
|||
|
|
table.json('confidence_thresholds').notNullable();
|
|||
|
|
table.json('risk_limits').notNullable();
|
|||
|
|
table.json('time_restrictions').notNullable();
|
|||
|
|
table.json('rollback_config').notNullable();
|
|||
|
|
table.json('notification_config').notNullable();
|
|||
|
|
table.json('statistics').notNullable();
|
|||
|
|
|
|||
|
|
// 状态
|
|||
|
|
table.enum('status', ['ACTIVE', 'INACTIVE', 'SUSPENDED']).notNullable().defaultTo('ACTIVE');
|
|||
|
|
|
|||
|
|
// 审计
|
|||
|
|
table.string('created_by', 50).notNullable();
|
|||
|
|
table.string('updated_by', 50).notNullable();
|
|||
|
|
table.timestamp('created_at').notNullable().defaultTo(db.fn.now());
|
|||
|
|
table.timestamp('updated_at').notNullable().defaultTo(db.fn.now());
|
|||
|
|
|
|||
|
|
// 唯一约束
|
|||
|
|
table.unique(['tenant_id', 'shop_id', 'module']);
|
|||
|
|
table.index(['tenant_id', 'status']);
|
|||
|
|
});
|
|||
|
|
logger.info(`${this.TABLE_NAME} table created successfully`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 等级演进记录表
|
|||
|
|
const hasEvolutionTable = await db.schema.hasTable(this.EVOLUTION_TABLE);
|
|||
|
|
if (!hasEvolutionTable) {
|
|||
|
|
logger.info(`Creating ${this.EVOLUTION_TABLE} table...`);
|
|||
|
|
await db.schema.createTable(this.EVOLUTION_TABLE, (table) => {
|
|||
|
|
table.string('id', 50).primary();
|
|||
|
|
table.string('tenant_id', 50).notNullable().index();
|
|||
|
|
table.string('shop_id', 50).notNullable().index();
|
|||
|
|
table.enum('module', [
|
|||
|
|
'PRICING', 'INVENTORY', 'AD_OPTIMIZE', 'PRODUCT_SELECT',
|
|||
|
|
'LOGISTICS', 'RISK_CONTROL', 'CUSTOMER_SERVICE', 'SETTLEMENT'
|
|||
|
|
]).notNullable();
|
|||
|
|
|
|||
|
|
table.enum('from_level', ['L1', 'L2', 'L3', 'L4']).notNullable();
|
|||
|
|
table.enum('to_level', ['L1', 'L2', 'L3', 'L4']).notNullable();
|
|||
|
|
table.text('reason').notNullable();
|
|||
|
|
table.json('metrics').notNullable();
|
|||
|
|
table.string('approved_by', 50).notNullable();
|
|||
|
|
table.timestamp('created_at').notNullable().defaultTo(db.fn.now());
|
|||
|
|
|
|||
|
|
table.index(['tenant_id', 'module', 'created_at']);
|
|||
|
|
});
|
|||
|
|
logger.info(`${this.EVOLUTION_TABLE} table created successfully`);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* [BE-AIAUTO-002] 获取或创建模块配置
|
|||
|
|
*/
|
|||
|
|
static async getOrCreateConfig(
|
|||
|
|
tenantId: string,
|
|||
|
|
shopId: string,
|
|||
|
|
module: DecisionModule,
|
|||
|
|
userId: string
|
|||
|
|
): Promise<AutoExecutionConfig> {
|
|||
|
|
// 尝试从缓存获取
|
|||
|
|
const cacheKey = `${this.CACHE_PREFIX}${tenantId}:${shopId}:${module}`;
|
|||
|
|
const cached = await RedisService.get(cacheKey);
|
|||
|
|
if (cached) {
|
|||
|
|
return JSON.parse(cached);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 查询数据库
|
|||
|
|
let config = await db(this.TABLE_NAME)
|
|||
|
|
.where({ tenant_id: tenantId, shop_id: shopId, module })
|
|||
|
|
.first();
|
|||
|
|
|
|||
|
|
if (!config) {
|
|||
|
|
// 创建默认配置
|
|||
|
|
const defaultConfig = MODULE_DEFAULT_CONFIGS[module];
|
|||
|
|
const id = `AEC-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|||
|
|
|
|||
|
|
const newConfig = {
|
|||
|
|
id,
|
|||
|
|
tenant_id: tenantId,
|
|||
|
|
shop_id: shopId,
|
|||
|
|
module,
|
|||
|
|
automation_level: 'L1' as AutomationLevel,
|
|||
|
|
confidence_thresholds: JSON.stringify(defaultConfig.confidence_thresholds || {
|
|||
|
|
auto_execute: 0.80,
|
|||
|
|
pending_review: 0.50,
|
|||
|
|
auto_reject: 0.30,
|
|||
|
|
}),
|
|||
|
|
risk_limits: JSON.stringify(defaultConfig.risk_limits || {
|
|||
|
|
max_amount: 1000,
|
|||
|
|
max_quantity: 100,
|
|||
|
|
allowed_risk_levels: ['LOW', 'MEDIUM'],
|
|||
|
|
}),
|
|||
|
|
time_restrictions: JSON.stringify({
|
|||
|
|
allowed_hours: Array.from({ length: 24 }, (_, i) => i),
|
|||
|
|
excluded_dates: [],
|
|||
|
|
}),
|
|||
|
|
rollback_config: JSON.stringify({
|
|||
|
|
enabled: true,
|
|||
|
|
max_attempts: 3,
|
|||
|
|
cooldown_minutes: 30,
|
|||
|
|
}),
|
|||
|
|
notification_config: JSON.stringify({
|
|||
|
|
on_execute: true,
|
|||
|
|
on_fail: true,
|
|||
|
|
on_rollback: true,
|
|||
|
|
channels: ['EMAIL'] as ('EMAIL' | 'SMS' | 'WEBHOOK')[],
|
|||
|
|
}),
|
|||
|
|
statistics: JSON.stringify({
|
|||
|
|
total_executions: 0,
|
|||
|
|
success_count: 0,
|
|||
|
|
fail_count: 0,
|
|||
|
|
rollback_count: 0,
|
|||
|
|
avg_confidence: 0,
|
|||
|
|
}),
|
|||
|
|
status: 'ACTIVE',
|
|||
|
|
created_by: userId,
|
|||
|
|
updated_by: userId,
|
|||
|
|
created_at: new Date(),
|
|||
|
|
updated_at: new Date(),
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
await db(this.TABLE_NAME).insert(newConfig);
|
|||
|
|
config = newConfig;
|
|||
|
|
logger.info(`[AutoExecutionConfig] Created default config for ${module}`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const result = this.parseConfigRecord(config);
|
|||
|
|
|
|||
|
|
// 缓存结果
|
|||
|
|
await RedisService.setex(cacheKey, this.CACHE_TTL, JSON.stringify(result));
|
|||
|
|
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* [BE-AIAUTO-002] 校验执行请求
|
|||
|
|
*/
|
|||
|
|
static async validateExecution(request: ExecutionRequest): Promise<ExecutionResult> {
|
|||
|
|
const config = await this.getOrCreateConfig(
|
|||
|
|
request.tenant_id,
|
|||
|
|
request.shop_id,
|
|||
|
|
request.module,
|
|||
|
|
'SYSTEM'
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
const warnings: string[] = [];
|
|||
|
|
let allowed = true;
|
|||
|
|
let reason = '';
|
|||
|
|
let requiredAction: 'AUTO_EXECUTE' | 'PENDING_REVIEW' | 'AUTO_REJECT' = 'AUTO_EXECUTE';
|
|||
|
|
|
|||
|
|
// 检查配置状态
|
|||
|
|
if (config.status !== 'ACTIVE') {
|
|||
|
|
return {
|
|||
|
|
allowed: false,
|
|||
|
|
reason: `配置状态为 ${config.status},不允许自动执行`,
|
|||
|
|
required_action: 'PENDING_REVIEW',
|
|||
|
|
config_snapshot: { status: config.status },
|
|||
|
|
warnings: ['模块配置未激活'],
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查置信度阈值
|
|||
|
|
const { confidence_thresholds } = config;
|
|||
|
|
if (request.confidence >= confidence_thresholds.auto_execute) {
|
|||
|
|
requiredAction = 'AUTO_EXECUTE';
|
|||
|
|
reason = '置信度达到自动执行阈值';
|
|||
|
|
} else if (request.confidence >= confidence_thresholds.pending_review) {
|
|||
|
|
requiredAction = 'PENDING_REVIEW';
|
|||
|
|
reason = '置信度处于待审核区间';
|
|||
|
|
allowed = false;
|
|||
|
|
} else {
|
|||
|
|
requiredAction = 'AUTO_REJECT';
|
|||
|
|
reason = '置信度低于最低阈值,自动拒绝';
|
|||
|
|
allowed = false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查风险等级
|
|||
|
|
const { risk_limits } = config;
|
|||
|
|
if (!risk_limits.allowed_risk_levels.includes(request.risk_level)) {
|
|||
|
|
warnings.push(`风险等级 ${request.risk_level} 不在允许范围内`);
|
|||
|
|
if (request.risk_level === 'CRITICAL' || request.risk_level === 'HIGH') {
|
|||
|
|
requiredAction = 'PENDING_REVIEW';
|
|||
|
|
allowed = false;
|
|||
|
|
reason = '风险等级过高,需要人工审核';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查等级能力限制
|
|||
|
|
const levelCapability = LEVEL_CAPABILITIES[config.automation_level];
|
|||
|
|
const riskLevelOrder: RiskLevel[] = ['LOW', 'MEDIUM', 'HIGH', 'CRITICAL'];
|
|||
|
|
const requestRiskIndex = riskLevelOrder.indexOf(request.risk_level);
|
|||
|
|
const maxRiskIndex = riskLevelOrder.indexOf(levelCapability.max_risk_level);
|
|||
|
|
|
|||
|
|
if (requestRiskIndex > maxRiskIndex) {
|
|||
|
|
warnings.push(`当前等级 ${config.automation_level} 不支持风险等级 ${request.risk_level}`);
|
|||
|
|
requiredAction = 'PENDING_REVIEW';
|
|||
|
|
allowed = false;
|
|||
|
|
reason = '超出当前自动化等级的风险承受能力';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查金额/数量限制
|
|||
|
|
const maxAmount = risk_limits.max_amount * levelCapability.max_amount_multiplier;
|
|||
|
|
if (request.estimated_impact.amount && request.estimated_impact.amount > maxAmount) {
|
|||
|
|
warnings.push(`预估金额 ${request.estimated_impact.amount} 超出限制 ${maxAmount}`);
|
|||
|
|
requiredAction = 'PENDING_REVIEW';
|
|||
|
|
allowed = false;
|
|||
|
|
reason = '预估影响金额超出当前等级限制';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (request.estimated_impact.quantity && request.estimated_impact.quantity > risk_limits.max_quantity) {
|
|||
|
|
warnings.push(`预估数量 ${request.estimated_impact.quantity} 超出限制 ${risk_limits.max_quantity}`);
|
|||
|
|
requiredAction = 'PENDING_REVIEW';
|
|||
|
|
allowed = false;
|
|||
|
|
reason = '预估影响数量超出限制';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查时间限制
|
|||
|
|
const { time_restrictions } = config;
|
|||
|
|
const now = new Date();
|
|||
|
|
const currentHour = now.getHours();
|
|||
|
|
const currentDate = now.toISOString().split('T')[0];
|
|||
|
|
|
|||
|
|
if (!time_restrictions.allowed_hours.includes(currentHour)) {
|
|||
|
|
warnings.push(`当前时间 ${currentHour}:00 不在允许执行的时间段内`);
|
|||
|
|
requiredAction = 'PENDING_REVIEW';
|
|||
|
|
allowed = false;
|
|||
|
|
reason = '不在允许执行的时间段';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (time_restrictions.excluded_dates.includes(currentDate)) {
|
|||
|
|
warnings.push(`当前日期 ${currentDate} 被排除执行`);
|
|||
|
|
requiredAction = 'PENDING_REVIEW';
|
|||
|
|
allowed = false;
|
|||
|
|
reason = '当前日期不允许自动执行';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// L1等级始终需要审核
|
|||
|
|
if (config.automation_level === 'L1' && requiredAction === 'AUTO_EXECUTE') {
|
|||
|
|
requiredAction = 'PENDING_REVIEW';
|
|||
|
|
allowed = false;
|
|||
|
|
reason = 'L1等级所有操作都需要人工确认';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
allowed,
|
|||
|
|
reason,
|
|||
|
|
required_action: requiredAction,
|
|||
|
|
config_snapshot: {
|
|||
|
|
automation_level: config.automation_level,
|
|||
|
|
confidence_thresholds: config.confidence_thresholds,
|
|||
|
|
risk_limits: config.risk_limits,
|
|||
|
|
},
|
|||
|
|
warnings,
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* [BE-AIAUTO-002] 更新配置
|
|||
|
|
*/
|
|||
|
|
static async updateConfig(
|
|||
|
|
configId: string,
|
|||
|
|
updates: Partial<AutoExecutionConfig>,
|
|||
|
|
userId: string
|
|||
|
|
): Promise<AutoExecutionConfig> {
|
|||
|
|
const updateData: any = {
|
|||
|
|
updated_by: userId,
|
|||
|
|
updated_at: new Date(),
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 处理JSON字段
|
|||
|
|
if (updates.confidence_thresholds) {
|
|||
|
|
updateData.confidence_thresholds = JSON.stringify(updates.confidence_thresholds);
|
|||
|
|
}
|
|||
|
|
if (updates.risk_limits) {
|
|||
|
|
updateData.risk_limits = JSON.stringify(updates.risk_limits);
|
|||
|
|
}
|
|||
|
|
if (updates.time_restrictions) {
|
|||
|
|
updateData.time_restrictions = JSON.stringify(updates.time_restrictions);
|
|||
|
|
}
|
|||
|
|
if (updates.rollback_config) {
|
|||
|
|
updateData.rollback_config = JSON.stringify(updates.rollback_config);
|
|||
|
|
}
|
|||
|
|
if (updates.notification_config) {
|
|||
|
|
updateData.notification_config = JSON.stringify(updates.notification_config);
|
|||
|
|
}
|
|||
|
|
if (updates.automation_level) {
|
|||
|
|
updateData.automation_level = updates.automation_level;
|
|||
|
|
}
|
|||
|
|
if (updates.status) {
|
|||
|
|
updateData.status = updates.status;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
await db(this.TABLE_NAME)
|
|||
|
|
.where('id', configId)
|
|||
|
|
.update(updateData);
|
|||
|
|
|
|||
|
|
// 清除缓存
|
|||
|
|
const config = await db(this.TABLE_NAME).where('id', configId).first();
|
|||
|
|
if (config) {
|
|||
|
|
const cacheKey = `${this.CACHE_PREFIX}${config.tenant_id}:${config.shop_id}:${config.module}`;
|
|||
|
|
await RedisService.del(cacheKey);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 发布事件
|
|||
|
|
await EventBusService.publish('auto_execution.config_updated', {
|
|||
|
|
configId,
|
|||
|
|
updates,
|
|||
|
|
userId,
|
|||
|
|
timestamp: new Date(),
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
logger.info(`[AutoExecutionConfig] Updated config ${configId}`);
|
|||
|
|
return this.parseConfigRecord(config);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* [BE-AIAUTO-002] 记录执行结果并更新统计
|
|||
|
|
*/
|
|||
|
|
static async recordExecution(
|
|||
|
|
tenantId: string,
|
|||
|
|
shopId: string,
|
|||
|
|
module: DecisionModule,
|
|||
|
|
success: boolean,
|
|||
|
|
confidence: number,
|
|||
|
|
rolledBack: boolean = false
|
|||
|
|
): Promise<void> {
|
|||
|
|
const config = await this.getOrCreateConfig(tenantId, shopId, module, 'SYSTEM');
|
|||
|
|
|
|||
|
|
const stats = config.statistics;
|
|||
|
|
stats.total_executions += 1;
|
|||
|
|
if (success) {
|
|||
|
|
stats.success_count += 1;
|
|||
|
|
} else {
|
|||
|
|
stats.fail_count += 1;
|
|||
|
|
}
|
|||
|
|
if (rolledBack) {
|
|||
|
|
stats.rollback_count += 1;
|
|||
|
|
}
|
|||
|
|
stats.avg_confidence = ((stats.avg_confidence * (stats.total_executions - 1)) + confidence) / stats.total_executions;
|
|||
|
|
stats.last_execution_at = new Date();
|
|||
|
|
|
|||
|
|
await db(this.TABLE_NAME)
|
|||
|
|
.where('id', config.id)
|
|||
|
|
.update({
|
|||
|
|
statistics: JSON.stringify(stats),
|
|||
|
|
updated_at: new Date(),
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 清除缓存
|
|||
|
|
const cacheKey = `${this.CACHE_PREFIX}${tenantId}:${shopId}:${module}`;
|
|||
|
|
await RedisService.del(cacheKey);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* [BE-AIAUTO-002] 升级自动化等级
|
|||
|
|
*/
|
|||
|
|
static async upgradeLevel(
|
|||
|
|
tenantId: string,
|
|||
|
|
shopId: string,
|
|||
|
|
module: DecisionModule,
|
|||
|
|
targetLevel: AutomationLevel,
|
|||
|
|
reason: string,
|
|||
|
|
approvedBy: string
|
|||
|
|
): Promise<LevelEvolutionRecord> {
|
|||
|
|
const config = await this.getOrCreateConfig(tenantId, shopId, module, approvedBy);
|
|||
|
|
const currentLevel = config.automation_level;
|
|||
|
|
|
|||
|
|
// 验证等级演进顺序
|
|||
|
|
const levelOrder: AutomationLevel[] = ['L1', 'L2', 'L3', 'L4'];
|
|||
|
|
const currentIndex = levelOrder.indexOf(currentLevel);
|
|||
|
|
const targetIndex = levelOrder.indexOf(targetLevel);
|
|||
|
|
|
|||
|
|
if (targetIndex !== currentIndex + 1) {
|
|||
|
|
throw new Error(`只能逐级升级,当前等级 ${currentLevel},目标等级 ${targetLevel}`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查升级条件
|
|||
|
|
const stats = config.statistics;
|
|||
|
|
if (stats.total_executions < 100) {
|
|||
|
|
throw new Error(`执行次数不足100次,当前 ${stats.total_executions} 次`);
|
|||
|
|
}
|
|||
|
|
const successRate = stats.success_count / stats.total_executions;
|
|||
|
|
if (successRate < 0.95) {
|
|||
|
|
throw new Error(`成功率不足95%,当前 ${(successRate * 100).toFixed(1)}%`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新配置
|
|||
|
|
await this.updateConfig(config.id, { automation_level: targetLevel }, approvedBy);
|
|||
|
|
|
|||
|
|
// 记录演进
|
|||
|
|
const evolutionId = `EVO-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|||
|
|
const evolution: LevelEvolutionRecord = {
|
|||
|
|
id: evolutionId,
|
|||
|
|
tenant_id: tenantId,
|
|||
|
|
shop_id: shopId,
|
|||
|
|
module,
|
|||
|
|
from_level: currentLevel,
|
|||
|
|
to_level: targetLevel,
|
|||
|
|
reason,
|
|||
|
|
metrics: {
|
|||
|
|
success_rate: successRate,
|
|||
|
|
total_executions: stats.total_executions,
|
|||
|
|
avg_confidence: stats.avg_confidence,
|
|||
|
|
},
|
|||
|
|
approved_by: approvedBy,
|
|||
|
|
created_at: new Date(),
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
await db(this.EVOLUTION_TABLE).insert({
|
|||
|
|
...evolution,
|
|||
|
|
metrics: JSON.stringify(evolution.metrics),
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 发布事件
|
|||
|
|
await EventBusService.publish('auto_execution.level_upgraded', {
|
|||
|
|
tenantId,
|
|||
|
|
shopId,
|
|||
|
|
module,
|
|||
|
|
fromLevel: currentLevel,
|
|||
|
|
toLevel: targetLevel,
|
|||
|
|
approvedBy,
|
|||
|
|
timestamp: new Date(),
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
logger.info(`[AutoExecutionConfig] Upgraded ${module} from ${currentLevel} to ${targetLevel}`);
|
|||
|
|
return evolution;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* [BE-AIAUTO-002] 降级自动化等级
|
|||
|
|
*/
|
|||
|
|
static async downgradeLevel(
|
|||
|
|
tenantId: string,
|
|||
|
|
shopId: string,
|
|||
|
|
module: DecisionModule,
|
|||
|
|
targetLevel: AutomationLevel,
|
|||
|
|
reason: string,
|
|||
|
|
approvedBy: string
|
|||
|
|
): Promise<LevelEvolutionRecord> {
|
|||
|
|
const config = await this.getOrCreateConfig(tenantId, shopId, module, approvedBy);
|
|||
|
|
const currentLevel = config.automation_level;
|
|||
|
|
|
|||
|
|
// 验证等级演进顺序
|
|||
|
|
const levelOrder: AutomationLevel[] = ['L1', 'L2', 'L3', 'L4'];
|
|||
|
|
const currentIndex = levelOrder.indexOf(currentLevel);
|
|||
|
|
const targetIndex = levelOrder.indexOf(targetLevel);
|
|||
|
|
|
|||
|
|
if (targetIndex >= currentIndex) {
|
|||
|
|
throw new Error(`降级目标等级必须低于当前等级`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新配置
|
|||
|
|
await this.updateConfig(config.id, { automation_level: targetLevel }, approvedBy);
|
|||
|
|
|
|||
|
|
// 记录演进
|
|||
|
|
const evolutionId = `EVO-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|||
|
|
const stats = config.statistics;
|
|||
|
|
const successRate = stats.total_executions > 0 ? stats.success_count / stats.total_executions : 0;
|
|||
|
|
|
|||
|
|
const evolution: LevelEvolutionRecord = {
|
|||
|
|
id: evolutionId,
|
|||
|
|
tenant_id: tenantId,
|
|||
|
|
shop_id: shopId,
|
|||
|
|
module,
|
|||
|
|
from_level: currentLevel,
|
|||
|
|
to_level: targetLevel,
|
|||
|
|
reason,
|
|||
|
|
metrics: {
|
|||
|
|
success_rate: successRate,
|
|||
|
|
total_executions: stats.total_executions,
|
|||
|
|
avg_confidence: stats.avg_confidence,
|
|||
|
|
},
|
|||
|
|
approved_by: approvedBy,
|
|||
|
|
created_at: new Date(),
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
await db(this.EVOLUTION_TABLE).insert({
|
|||
|
|
...evolution,
|
|||
|
|
metrics: JSON.stringify(evolution.metrics),
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 发布事件
|
|||
|
|
await EventBusService.publish('auto_execution.level_downgraded', {
|
|||
|
|
tenantId,
|
|||
|
|
shopId,
|
|||
|
|
module,
|
|||
|
|
fromLevel: currentLevel,
|
|||
|
|
toLevel: targetLevel,
|
|||
|
|
approvedBy,
|
|||
|
|
reason,
|
|||
|
|
timestamp: new Date(),
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
logger.info(`[AutoExecutionConfig] Downgraded ${module} from ${currentLevel} to ${targetLevel}`);
|
|||
|
|
return evolution;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* [BE-AIAUTO-002] 获取所有模块配置
|
|||
|
|
*/
|
|||
|
|
static async getAllConfigs(
|
|||
|
|
tenantId: string,
|
|||
|
|
shopId?: string
|
|||
|
|
): Promise<AutoExecutionConfig[]> {
|
|||
|
|
let query = db(this.TABLE_NAME).where('tenant_id', tenantId);
|
|||
|
|
if (shopId) {
|
|||
|
|
query = query.where('shop_id', shopId);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const records = await query.orderBy('module', 'asc');
|
|||
|
|
return records.map(this.parseConfigRecord);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* [BE-AIAUTO-002] 获取等级演进历史
|
|||
|
|
*/
|
|||
|
|
static async getEvolutionHistory(
|
|||
|
|
tenantId: string,
|
|||
|
|
shopId?: string,
|
|||
|
|
module?: DecisionModule
|
|||
|
|
): Promise<LevelEvolutionRecord[]> {
|
|||
|
|
let query = db(this.EVOLUTION_TABLE).where('tenant_id', tenantId);
|
|||
|
|
if (shopId) {
|
|||
|
|
query = query.where('shop_id', shopId);
|
|||
|
|
}
|
|||
|
|
if (module) {
|
|||
|
|
query = query.where('module', module);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const records = await query.orderBy('created_at', 'desc');
|
|||
|
|
return records.map(r => ({
|
|||
|
|
...r,
|
|||
|
|
metrics: typeof r.metrics === 'string' ? JSON.parse(r.metrics) : r.metrics,
|
|||
|
|
}));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* [BE-AIAUTO-002] 获取等级能力说明
|
|||
|
|
*/
|
|||
|
|
static getLevelCapabilities(): Record<AutomationLevel, typeof LEVEL_CAPABILITIES[AutomationLevel]> {
|
|||
|
|
return LEVEL_CAPABILITIES;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* [BE-AIAUTO-002] 获取模块默认配置
|
|||
|
|
*/
|
|||
|
|
static getModuleDefaults(): Record<DecisionModule, Partial<AutoExecutionConfig>> {
|
|||
|
|
return MODULE_DEFAULT_CONFIGS;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 解析数据库记录
|
|||
|
|
*/
|
|||
|
|
private static parseConfigRecord(record: any): AutoExecutionConfig {
|
|||
|
|
return {
|
|||
|
|
...record,
|
|||
|
|
confidence_thresholds: typeof record.confidence_thresholds === 'string'
|
|||
|
|
? JSON.parse(record.confidence_thresholds)
|
|||
|
|
: record.confidence_thresholds,
|
|||
|
|
risk_limits: typeof record.risk_limits === 'string'
|
|||
|
|
? JSON.parse(record.risk_limits)
|
|||
|
|
: record.risk_limits,
|
|||
|
|
time_restrictions: typeof record.time_restrictions === 'string'
|
|||
|
|
? JSON.parse(record.time_restrictions)
|
|||
|
|
: record.time_restrictions,
|
|||
|
|
rollback_config: typeof record.rollback_config === 'string'
|
|||
|
|
? JSON.parse(record.rollback_config)
|
|||
|
|
: record.rollback_config,
|
|||
|
|
notification_config: typeof record.notification_config === 'string'
|
|||
|
|
? JSON.parse(record.notification_config)
|
|||
|
|
: record.notification_config,
|
|||
|
|
statistics: typeof record.statistics === 'string'
|
|||
|
|
? JSON.parse(record.statistics)
|
|||
|
|
: record.statistics,
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
}
|