From 27ea7e7f530bbfb882c7ea8042ba618b9bec31ec Mon Sep 17 00:00:00 2001 From: fullstackBot Date: Fri, 18 Sep 2026 16:02:57 +0800 Subject: [PATCH] =?UTF-8?q?fix(cma):=20my-dashboard=20KPI=20=E5=8F=96?= =?UTF-8?q?=E6=95=B0=E5=90=8C=E6=9C=9F=E5=86=85=E4=B8=BB=E5=80=BC=E4=BC=98?= =?UTF-8?q?=E5=85=88=EF=BC=88=E9=87=8D=E5=A4=8D=E8=A1=8C=E4=B8=8D=E5=86=8D?= =?UTF-8?q?=E8=A2=AB=E9=80=89=E4=B8=AD=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 同一期间并存两行(主值 ledger/verified 与自动归集重复行 auto_collect/error)时, 原判据按 calculated_at 倒序会取到写入更晚的重复行;今日两行数值一致故不可见, 漂移后卡片会静默显示重复行值。故在月期分支的排序中加「主值优先」: verified > ledger > 其余,置于 period 倒序之后(跨期口径不变)。 范围: backend/app/api/dashboard.py 的 latest_kpi_value() 单函数(含 case 导入)。 无 DB 写入、不动采集器/派生函数/cron/口径定义/前端。 单号: project-20260918-kpi-row-priority --- backend/app/api/dashboard.py | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/backend/app/api/dashboard.py b/backend/app/api/dashboard.py index b894f9c0..60da5620 100644 --- a/backend/app/api/dashboard.py +++ b/backend/app/api/dashboard.py @@ -1,7 +1,7 @@ """驾驶舱 API v2 — 支持时间区间""" from fastapi import APIRouter, Depends, Query, Request, HTTPException from sqlalchemy.orm import Session -from sqlalchemy import func, or_ +from sqlalchemy import case, func, or_ from datetime import datetime, timedelta from typing import Optional from app.database import get_db @@ -580,13 +580,33 @@ def predict_kpis(db: Session = Depends(get_db), entity_id: int = Depends(get_ent # 季度汇总行/半年度预估值(forecast)当成"最新"。 _MONTH_PERIOD_REGEX = r"^[0-9]{4}-(0[1-9]|1[0-2])$" +# 主值优先(2026-09-18 同期间重复行判据收口,项目单 project-20260918-kpi-row-priority) +# 同一期间可能并存两行:①主值(source_type='ledger' / data_status='verified',结账/复核口径) +# ②自动归集重复行(source_type='auto_collect',备注自述「与主值为同一事实重复行」)。 +# 原判据仅看写入时刻:重复行由复采批量写入、calculated_at 更晚 ⇒ 会被选中(实测 entity1 +# 2026-08 的 F_REVENUE/F_NET_PROFIT:2136/2127 auto_collect 写于 09-14,晚于主值 1822/2102)。 +# 今日两行数值一致(Δ=0)故用户不可见;一旦重复行与主值漂移,卡片会静默显示重复行的值。 +# 故在**同一期间内**加「主值优先」:verified(已复核)> ledger(账面主值)> 其余。 +# 注意:跨期间仍以 period 倒序为第一判据(不改变 d33b5e1 的期间口径——当期只有未复核行时, +# 不会回退到上一期已复核行)。 +_MAIN_VALUE_PRIORITY = case( + (KPIValue.data_status == "verified", 0), + (KPIValue.source_type == "ledger", 1), + else_=2, +) + def latest_kpi_value(db: Session, kpi_id: int) -> Optional[KPIValue]: - """该 KPI 的最新可用值:月期间优先,无月期间行时回退 calculated_at 倒序。""" + """该 KPI 的最新可用值:月期间优先(同期内主值优先),无月期间行时回退 calculated_at 倒序。""" monthly = ( db.query(KPIValue) .filter(KPIValue.kpi_id == kpi_id, KPIValue.period.op("REGEXP")(_MONTH_PERIOD_REGEX)) - .order_by(KPIValue.period.desc(), KPIValue.calculated_at.desc(), KPIValue.id.desc()) + .order_by( + KPIValue.period.desc(), + _MAIN_VALUE_PRIORITY, + KPIValue.calculated_at.desc(), + KPIValue.id.desc(), + ) .first() ) if monthly is not None: