You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django多外键关联下Annotate分组计算KPI分数问题

KPI系统开发问题及实现方案

项目背景

我正在开发一套KPI系统,管理人员可为每位用户设置特定问题,每月对这些问题进行1-10分的评分。系统包含4个部门,每个部门设有4个问题,需计算各部门的得分百分比。

模型定义

# 每月需回答的问题
class Question(models.Model):
    department = models.CharField(max_length=100, choices=USER_TYPE)
    question = models.CharField(max_length=100)

    def __str__(self):
        return f"{self.question}"

# 每月1号通过Celery创建
class Kpi(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="kip_for")
    created_at = models.DateField()

    class Meta:
        unique_together = ("user", "created_at")

    def __str__(self):
        return f"{self.user} for Month {self.created_at.month}"

# 按部门分组答案
class DepartmentsKPI(models.Model):
    department = models.CharField(max_length=100, choices=USER_TYPE)
    kpi = models.ForeignKey(
        Kpi, on_delete=models.CASCADE, related_name="department_kpi"
    )

class Answer(models.Model):
    question = models.ForeignKey(
        Question, on_delete=models.CASCADE, related_name="answer_for"
    )
    score = models.PositiveSmallIntegerField(default=0)
    comment = models.TextField(null=True, blank=True)
    kpi_answers = models.ForeignKey(
        DepartmentsKPI,
        on_delete=models.CASCADE,
        related_name="department_answers",
    )

当前问题与现有实现

在Kpi模型上使用annotate汇总各部门得分时,结果未正确分组,因此需按用户名手动分组。现有实现代码如下:

def get_kpi_range(*, year, month):
    queryset = (
        KPIRepository.filter_kpis(
            created_at__year=year,
            created_at__month=month,
        )
        .prefetch_related("department_kpi__department_answers")
        .annotate(
            score=Sum("department_kpi__department_answers__score")
            / Count("department_kpi__department_answers"),
            department=F("department_kpi__department"),
        )
        .values("id", "score", "department", username=F("user__username"))
    )

    grouped = [
        {"username": username, "kpis": list(instances)}
        for username, instances in itertools.groupby(queryset, lambda x: x["username"])
    ]

    return grouped

该方案存在缺陷:无法处理同一用户的多条记录,查询全年KPI数据时结果混乱;同时无法按月份分组返回用户数据,无法满足生成图表的需求。

当前结果

使用grouped后的结果

[
    {
        "username": "xxxxx",
        "kpis": [
            {
                "department": "HR",
                "score": 5
            },
            {
                "department": "IT",
                "score": 6
            },
            {
                "department": "QUALITY",
                "score": 4
            },
            {
                "department": "WFM",
                "score": 6
            }
        ]
    },
    {
        "username": "ahmed",
        "kpis": [
            {
                "department": "IT",
                "score": 6
            }
        ]
    }
]

未使用grouped的结果

[
    [
        {
            "id": 7,
            "score": 5,
            "department": "HR",
            "username": "xxx"
        },
        {
            "id": 7,
            "score": 6,
            "department": "IT",
            "username": "xxx"
        },
        {
            "id": 7,
            "score": 4,
            "department": "QUALITY",
            "username": "xxx"
        },
        {
            "id": 7,
            "score": 6,
            "department": "WFM",
            "username": "xxx"
        },
        {
            "id": 8,
            "score": 6,
            "department": "IT",
            "username": "ahmed"
        }
    ]
]

内容的提问来源于stack exchange,提问作者Elabbasy00

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 18:13:16