bindbox-game/test_lottery_profit.js
邹方成 81e2fb5a75
Some checks failed
Build docker and publish / linux (1.24.5) (push) Failing after 41s
feat(activity): 实现抽奖随机承诺与验证功能
新增随机种子生成与验证逻辑,包括:
1. 添加随机承诺生成接口
2. 实现抽奖执行与验证流程
3. 新增批量用户创建与删除功能
4. 添加抽奖收据记录表
5. 完善配置管理与错误码

新增测试用例验证随机算法正确性
2025-11-15 20:39:13 +08:00

218 lines
6.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 抽奖盈亏计算测试示例
// 基于实际业务逻辑的模拟数据
// 模拟一个抽奖活动的配置
const mockActivity = {
id: 1,
name: "春节抽奖活动",
price_draw: 1000, // 门票价格10元 = 1000分
status: 1
};
// 模拟奖项配置
const mockRewards = [
{
id: 1,
name: "iPhone 15 Pro",
weight: 1,
quantity: 10,
product_id: 101,
cost: 800000 // 成本8000元 = 800000分
},
{
id: 2,
name: "AirPods Pro",
weight: 10,
quantity: 50,
product_id: 102,
cost: 200000 // 成本2000元 = 200000分
},
{
id: 3,
name: "小米手环",
weight: 100,
quantity: 200,
product_id: 103,
cost: 20000 // 成本200元 = 20000分
},
{
id: 4,
name: "优惠券10元",
weight: 1000,
quantity: -1, // -1 表示不限量
product_id: null,
cost: 1000 // 成本10元 = 1000分平台补贴
},
{
id: 5,
name: "谢谢参与",
weight: 5000,
quantity: -1,
product_id: null,
cost: 0 // 无成本
}
];
// 计算理论概率和期望支出
function calculateTheoreticalData(rewards) {
// 筛选有效奖项weight > 0 且 quantity != 0
const validRewards = rewards.filter(r => r.weight > 0 && r.quantity !== 0);
// 计算总权重
const totalWeight = validRewards.reduce((sum, r) => sum + r.weight, 0);
// 计算每个奖项的理论概率和期望支出
const results = validRewards.map(reward => {
const probability = reward.weight / totalWeight;
const expectedCost = probability * reward.cost; // 单次抽奖的期望支出
return {
...reward,
probability,
expectedCost
};
});
// 总期望支出(单次抽奖)
const totalExpectedCost = results.reduce((sum, r) => sum + r.expectedCost, 0);
return {
rewards: results,
totalWeight,
totalExpectedCost
};
}
// 模拟抽奖结果
function simulateDraws(rewards, sampleSize) {
const theoretical = calculateTheoreticalData(rewards);
const results = {};
// 初始化结果统计
theoretical.rewards.forEach(reward => {
results[reward.id] = {
...reward,
count: 0,
simulatedCost: 0
};
});
// 模拟抽奖
for (let i = 0; i < sampleSize; i++) {
const random = Math.random();
let cumulativeProbability = 0;
// 根据概率选择奖项
for (const reward of theoretical.rewards) {
cumulativeProbability += reward.probability;
if (random <= cumulativeProbability) {
results[reward.id].count++;
results[reward.id].simulatedCost += reward.cost;
break;
}
}
}
return {
theoretical,
simulated: Object.values(results),
sampleSize
};
}
// 计算盈亏指标
function calculateProfitMetrics(activity, simulation) {
const { theoretical, simulated, sampleSize } = simulation;
const ticketPrice = activity.price_draw;
// 理论计算
const theoreticalRevenue = ticketPrice * sampleSize;
const theoreticalPayout = theoretical.totalExpectedCost * sampleSize;
const theoreticalProfit = theoreticalRevenue - theoreticalPayout;
const theoreticalMargin = theoreticalProfit / theoreticalRevenue;
// 模拟计算
const simulatedRevenue = ticketPrice * sampleSize;
const simulatedPayout = simulated.reduce((sum, r) => sum + r.simulatedCost, 0);
const simulatedProfit = simulatedRevenue - simulatedPayout;
const simulatedMargin = simulatedProfit / simulatedRevenue;
return {
theoretical: {
revenue: theoreticalRevenue,
payout: theoreticalPayout,
profit: theoreticalProfit,
margin: theoreticalMargin
},
simulated: {
revenue: simulatedRevenue,
payout: simulatedPayout,
profit: simulatedProfit,
margin: simulatedMargin
}
};
}
// 运行测试
function runLotteryProfitTest() {
console.log("=== 抽奖盈亏分析测试 ===");
console.log(`活动: ${mockActivity.name}`);
console.log(`门票价格: ¥${(mockActivity.price_draw / 100).toFixed(2)}`);
console.log("");
// 理论计算
const theoreticalData = calculateTheoreticalData(mockRewards);
console.log("--- 理论概率分析 ---");
console.log(`总权重: ${theoreticalData.totalWeight}`);
console.log(`单次期望支出: ¥${(theoreticalData.totalExpectedCost / 100).toFixed(4)}`);
console.log(`单次期望利润: ¥${((mockActivity.price_draw - theoreticalData.totalExpectedCost) / 100).toFixed(4)}`);
console.log("");
theoreticalData.rewards.forEach(reward => {
console.log(`${reward.name}:`);
console.log(` 权重: ${reward.weight}, 概率: ${(reward.probability * 100).toFixed(4)}%`);
console.log(` 成本: ¥${(reward.cost / 100).toFixed(2)}, 期望支出: ¥${(reward.expectedCost / 100).toFixed(4)}`);
});
console.log("\n--- 模拟结果 (样本数: 10000) ---");
const simulation = simulateDraws(mockRewards, 10000);
const metrics = calculateProfitMetrics(mockActivity, simulation);
console.log("理论值:");
console.log(` 总收入: ¥${(metrics.theoretical.revenue / 100).toFixed(2)}`);
console.log(` 总支出: ¥${(metrics.theoretical.payout / 100).toFixed(2)}`);
console.log(` 总利润: ¥${(metrics.theoretical.profit / 100).toFixed(2)}`);
console.log(` 利润率: ${(metrics.theoretical.margin * 100).toFixed(2)}%`);
console.log("\n模拟值:");
console.log(` 总收入: ¥${(metrics.simulated.revenue / 100).toFixed(2)}`);
console.log(` 总支出: ¥${(metrics.simulated.payout / 100).toFixed(2)}`);
console.log(` 总利润: ¥${(metrics.simulated.profit / 100).toFixed(2)}`);
console.log(` 利润率: ${(metrics.simulated.margin * 100).toFixed(2)}%`);
console.log("\n--- 各奖项模拟结果 ---");
simulation.simulated.forEach(result => {
const simulatedRate = result.count / simulation.sampleSize;
const theoreticalRate = result.probability;
const diff = Math.abs(simulatedRate - theoreticalRate) * 100;
console.log(`${result.name}:`);
console.log(` 模拟次数: ${result.count}, 模拟概率: ${(simulatedRate * 100).toFixed(3)}%`);
console.log(` 理论概率: ${(theoreticalRate * 100).toFixed(3)}%, 差异: ${diff.toFixed(3)}%`);
console.log(` 模拟支出: ¥${(result.simulatedCost / 100).toFixed(2)}`);
});
// 验证大数定律
console.log("\n--- 大数定律验证 ---");
const largeSample = simulateDraws(mockRewards, 100000);
largeSample.simulated.forEach(result => {
const simulatedRate = result.count / largeSample.sampleSize;
const theoreticalRate = result.probability;
const diff = Math.abs(simulatedRate - theoreticalRate) * 100;
console.log(`${result.name}: 模拟概率 ${(simulatedRate * 100).toFixed(4)}% vs 理论概率 ${(theoreticalRate * 100).toFixed(4)}%, 差异 ${diff.toFixed(4)}%`);
});
}
// 运行测试
runLotteryProfitTest();