fix(calibration): 校准幂等+已校准状态显示,解决重复提示
根因:预测准确度卡的偏差来自历史回填项目的预测vs实际,属既成事实, 不会因校准改变;且原 apply 公式 next=current+dev 会累加,反复点越推越高。 修复: - 校准建议/应用均基于未校准原始基准(env/默认)计算,保证幂等 - GET /api/calibration 返回 uncalibratedBase 与 calibrated 标志 - 卡片:已校准时显示「已按当前偏差校准:基准X%」绿色状态,不再出现按钮; 未校准时按钮明示「X% → Y%」 - 补充说明:历史偏差不会因校准改变,校准仅调整后续承接建议阈值
This commit is contained in:
+20
-5
@@ -358,6 +358,18 @@ function targetNetMargin(): number {
|
||||
return Number.isFinite(v) && v > 0 ? v : DEFAULT_TARGET_NET_MARGIN;
|
||||
}
|
||||
|
||||
/** 未校准的原始目标净利率基准(env/默认,忽略校准覆盖)。校准始终基于此基准做一次性补偿,保证幂等。 */
|
||||
function uncalibratedTargetNetMargin(): number {
|
||||
const v = Number(process.env.TARGET_NET_MARGIN);
|
||||
return Number.isFinite(v) && v > 0 ? v : DEFAULT_TARGET_NET_MARGIN;
|
||||
}
|
||||
|
||||
/** 据系统性偏差计算建议的目标净利率基准(基于未校准基准,夹取 [2%,30%])。 */
|
||||
function suggestedTargetBase(deviationPct: number): number {
|
||||
const base = uncalibratedTargetNetMargin();
|
||||
return Math.min(0.3, Math.max(0.02, Math.round((base + deviationPct / 100) * 1000) / 1000));
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/assessments/classify
|
||||
* 输入项目描述,返回业务类型与行业识别结果。
|
||||
@@ -885,6 +897,7 @@ app.get('/api/accuracy', async (c) => {
|
||||
*/
|
||||
app.get('/api/calibration', async (c) => {
|
||||
const current = targetNetMargin();
|
||||
const uncalibrated = uncalibratedTargetNetMargin();
|
||||
let suggested = current;
|
||||
let bias: string | null = null;
|
||||
let deviationPct: number | null = null;
|
||||
@@ -893,16 +906,18 @@ app.get('/api/calibration', async (c) => {
|
||||
bias = acc.bias;
|
||||
deviationPct = acc.avgDeviationPct;
|
||||
if (acc.avgDeviationPct !== null && acc.count > 0) {
|
||||
// 预测偏乐观(dev>0)→ 抬高要求的目标净利率以补偿;偏保守则下调。夹取 [2%,30%]。
|
||||
suggested = Math.min(0.3, Math.max(0.02, Math.round((current + acc.avgDeviationPct / 100) * 1000) / 1000));
|
||||
// 预测偏乐观(dev>0)→ 抬高要求的目标净利率以补偿;偏保守则下调。基于未校准基准,保证幂等。
|
||||
suggested = suggestedTargetBase(acc.avgDeviationPct);
|
||||
}
|
||||
}
|
||||
return c.json({ currentBase: current, suggestedBase: suggested, bias, deviationPct });
|
||||
// 已校准:当前基准已等于(或非常接近)据当前偏差计算的建议基准,无需再次应用。
|
||||
const calibrated = calibratedTargetBase !== null && Math.abs(current - suggested) < 1e-6;
|
||||
return c.json({ currentBase: current, suggestedBase: suggested, uncalibratedBase: uncalibrated, calibrated, bias, deviationPct });
|
||||
});
|
||||
|
||||
/**
|
||||
* POST /api/calibration/apply
|
||||
* 应用校准:将目标净利率基准设为据预测偏差计算的建议值(管理层)。
|
||||
* 应用校准:将目标净利率基准设为据预测偏差计算的建议值(管理层)。基于未校准基准,幂等。
|
||||
*/
|
||||
app.post('/api/calibration/apply', requireRole('管理层'), async (c) => {
|
||||
if (pool === null) return c.json({ error: '未配置数据库' }, 400);
|
||||
@@ -911,7 +926,7 @@ app.post('/api/calibration/apply', requireRole('管理层'), async (c) => {
|
||||
return c.json({ error: '暂无足够的实际值回填数据用于校准' }, 400);
|
||||
}
|
||||
const current = targetNetMargin();
|
||||
const next = Math.min(0.3, Math.max(0.02, Math.round((current + acc.avgDeviationPct / 100) * 1000) / 1000));
|
||||
const next = suggestedTargetBase(acc.avgDeviationPct);
|
||||
await setSetting(pool, 'targetMarginBase', next);
|
||||
calibratedTargetBase = next;
|
||||
return c.json({ appliedBase: next, previousBase: current, deviationPct: acc.avgDeviationPct });
|
||||
|
||||
Reference in New Issue
Block a user