94 lines
3.2 KiB
TypeScript
94 lines
3.2 KiB
TypeScript
|
|
import db from '../config/database';
|
|||
|
|
import { AuditService } from './AuditService';
|
|||
|
|
import { PathSimulatorService } from './PathSimulatorService';
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* [BIZ_TRADE_19] 多式联运碳足迹与成本动态博弈模型
|
|||
|
|
* 负责在包裹发运前,基于成本、碳足迹 (ESG) 与时效,进行动态博弈选择最优多式联运路径
|
|||
|
|
*/
|
|||
|
|
export class CarbonCostGameService {
|
|||
|
|
/**
|
|||
|
|
* 运行路径博弈模型
|
|||
|
|
*/
|
|||
|
|
static async runPathGame(
|
|||
|
|
tenantId: string,
|
|||
|
|
orderId: string,
|
|||
|
|
traceId: string
|
|||
|
|
): Promise<{ selectedMode: string; carbon: number; cost: number }> {
|
|||
|
|
// 1. 获取订单基础信息
|
|||
|
|
const order = await db('cf_orders').where({ id: orderId }).first();
|
|||
|
|
if (!order) throw new Error('Order not found');
|
|||
|
|
|
|||
|
|
// 2. 调用 PathSimulatorService 获取不同路径选项 (模拟多维度路径计算)
|
|||
|
|
const options = [
|
|||
|
|
{ mode: 'AIR', cost: 45.0, carbon: 15.2, reliability: 0.99, tsl: 3 },
|
|||
|
|
{ mode: 'SEA', cost: 12.0, carbon: 0.8, reliability: 0.85, tsl: 25 },
|
|||
|
|
{ mode: 'RAIL', cost: 22.0, carbon: 2.5, reliability: 0.92, tsl: 15 },
|
|||
|
|
{ mode: 'TRUCK', cost: 28.0, carbon: 5.4, reliability: 0.90, tsl: 10 }
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
// 3. 博弈评分逻辑 (Game Score Calculation)
|
|||
|
|
// 目标:min(Cost) + min(Carbon) + max(Reliability)
|
|||
|
|
// 权重可根据租户偏好动态调整 (这里假设 4:4:2)
|
|||
|
|
const weights = { cost: 0.4, carbon: 0.4, reliability: 0.2 };
|
|||
|
|
|
|||
|
|
// 归一化计算
|
|||
|
|
const gameResults = options.map(opt => {
|
|||
|
|
// 评分越高越好 (对 Cost 和 Carbon 取倒数)
|
|||
|
|
const score = (1 / opt.cost) * weights.cost +
|
|||
|
|
(1 / opt.carbon) * weights.carbon +
|
|||
|
|
opt.reliability * weights.reliability;
|
|||
|
|
return { ...opt, score };
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 4. 选择得分最高的路径
|
|||
|
|
const bestOption = gameResults.reduce((prev, curr) => curr.score > prev.score ? curr : prev);
|
|||
|
|
|
|||
|
|
await db.transaction(async (trx) => {
|
|||
|
|
// 5. 持久化博弈结果
|
|||
|
|
await trx('cf_carbon_cost_game').insert({
|
|||
|
|
tenant_id: tenantId,
|
|||
|
|
order_id: orderId,
|
|||
|
|
carbon_footprint: bestOption.carbon,
|
|||
|
|
shipping_cost: bestOption.cost,
|
|||
|
|
game_score: bestOption.score,
|
|||
|
|
selected_mode: bestOption.mode,
|
|||
|
|
logic_description: JSON.stringify({
|
|||
|
|
alternatives: gameResults,
|
|||
|
|
weights
|
|||
|
|
})
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 6. 审计记录
|
|||
|
|
await AuditService.log({
|
|||
|
|
tenant_id: tenantId,
|
|||
|
|
action: 'CARBON_COST_GAME_COMPLETED',
|
|||
|
|
target_type: 'LOGISTICS_ROUTE',
|
|||
|
|
target_id: orderId,
|
|||
|
|
trace_id: traceId,
|
|||
|
|
new_data: JSON.stringify({ selectedMode: bestOption.mode, score: bestOption.score }),
|
|||
|
|
metadata: JSON.stringify({ carbon: bestOption.carbon, cost: bestOption.cost })
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
selectedMode: bestOption.mode,
|
|||
|
|
carbon: bestOption.carbon,
|
|||
|
|
cost: bestOption.cost
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取租户的碳足迹汇总
|
|||
|
|
*/
|
|||
|
|
static async getCarbonSummary(tenantId: string) {
|
|||
|
|
const summary = await db('cf_carbon_cost_game')
|
|||
|
|
.where({ tenant_id: tenantId })
|
|||
|
|
.select('selected_mode')
|
|||
|
|
.sum('carbon_footprint as total_carbon')
|
|||
|
|
.count('* as count')
|
|||
|
|
.groupBy('selected_mode');
|
|||
|
|
return summary;
|
|||
|
|
}
|
|||
|
|
}
|