From ec58efa20bf158bcff007d5e69a1dd40f51a006d Mon Sep 17 00:00:00 2001 From: Hermes CI Fix Date: Mon, 14 Sep 2026 17:56:26 +0800 Subject: [PATCH] =?UTF-8?q?fix(budget):=20=E5=81=8F=E5=B7=AE=E6=A3=80?= =?UTF-8?q?=E6=9F=A5=E5=90=8C=20KPI=20=E5=A4=9A=E8=AE=A1=E5=88=92=E5=8E=BB?= =?UTF-8?q?=E9=87=8D=EF=BC=88deviation-check=20=E8=A1=A5=20db.flush?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit autoflush=False 下,check_budget_deviation 循环内 existing_alert 去重查询 查不到本次刚 add 未 flush 的行 → 同期间同 KPI 多 active 预算计划会重复告警。 add 后补 db.flush() 使其对下一次循环可见。附回归测试。 Ref: yanxue-20260914-budget-p13p14-activation 遗留②试跑发现的缺陷 --- backend/app/api/budget.py | 3 +++ backend/tests/test_budget.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/backend/app/api/budget.py b/backend/app/api/budget.py index 087aebb8..35f5fa47 100644 --- a/backend/app/api/budget.py +++ b/backend/app/api/budget.py @@ -908,6 +908,9 @@ def check_budget_deviation( ) db.add(alert) alerts_generated += 1 + # 立即 flush:autoflush=False 下,下一次循环的同 KPI 去重查询 + # (existing_alert) 需看到本次已 add 的行,否则同期间同 KPI 多预算计划会重复告警 + db.flush() alerts.append({ "kpi_id": bp.kpi_id, diff --git a/backend/tests/test_budget.py b/backend/tests/test_budget.py index f6916534..55e27569 100644 --- a/backend/tests/test_budget.py +++ b/backend/tests/test_budget.py @@ -461,6 +461,34 @@ class TestBudgetDeviationCheck: json={"period": "2026-06", "threshold": 20}) assert resp.json()["alerts_generated"] == 0 + def test_deviation_check_dedups_same_kpi_multiple_plans(self, client: TestClient, db: Session): + """回归:同期间同 KPI 有多条 active 预算计划时,只应生成 1 条 open 告警 + + 根因:database.py sessionmaker(autoflush=False),端点内循环的 existing_alert + 去重查询查不到本次刚 add 未 flush 的行 → 第二个计划重复 INSERT。 + """ + create_test_user(db) + token = get_token_for_user(client) + kpi = create_test_kpi(db, kpi_code="BUDGET_ALERT_DUP") + for _ in range(2): # 两条同 KPI 计划(模拟数据面重复计划) + db.add(BudgetPlan(kpi_id=kpi.id, period="2026-06", budget_value=100.0, + budget_year=2026, budget_month=6, status="active")) + from app.models import KPIValue + db.add(KPIValue(kpi_id=kpi.id, period="2026-06", actual_value=150.0)) # +50% + db.commit() + + resp = client.post(f"{self.BASE}/deviation-check", headers=auth_header(token), + json={"period": "2026-06", "threshold": 20}) + assert resp.status_code == 200 + assert resp.json()["alerts_generated"] == 1 + from app.models import BudgetDeviationAlert + n = db.query(BudgetDeviationAlert).filter( + BudgetDeviationAlert.kpi_id == kpi.id, + BudgetDeviationAlert.period == "2026-06", + BudgetDeviationAlert.status == "open", + ).count() + assert n == 1, f"同 KPI 重复告警未去重,落库 {n} 条" + class TestBudgetDeviationAlerts: """偏差预警记录查询/更新"""