Coverage for baibu/metrics/definitions.py: 100%

17 statements  

« prev     ^ index     » next       coverage.py v7.10.7, created at 2026-10-06 13:07 +0000

1"""The metrics the platform counts, with how each one adds up over days. 

2 

3The exact definitions are in ``docs/developer/metrics.md``; keep the two in 

4step. Every value is an integer count (or a whole number of milliseconds). 

5""" 

6 

7from dataclasses import dataclass 

8 

9from django.utils.translation import gettext_lazy as _ 

10 

11# How a metric combines over several days. 

12SUM = "sum" # events: add the daily values 

13DISTINCT = "distinct" # distinct users: days cannot be added; read the value on the last day 

14SNAPSHOT = "snapshot" # a state at the end of the day: read the value on the last day 

15DURATION = "duration" # a median or percentile in milliseconds: shown per day only 

16COHORT = "cohort" # retention: dated by the cohort week, not by a day of activity 

17 

18 

19@dataclass(frozen=True) 

20class Metric: 

21 name: str 

22 label: str 

23 kind: str 

24 # What the dimension holds for a breakdown; empty for a plain total. 

25 dimension: str = "" 

26 # Breakdowns that could point at a few people (by language or consent 

27 # tier) show small values as "fewer than METRICS_MIN_GROUP_SIZE". 

28 suppress_small: bool = False 

29 

30 

31METRICS: dict[str, Metric] = { 

32 metric.name: metric 

33 for metric in ( 

34 Metric("users_new", _("New users"), SUM), 

35 Metric("users_total", _("Users"), SNAPSHOT), 

36 Metric("active_users", _("Active users"), DISTINCT), 

37 Metric("active_users_7d", _("Active users, 7 days"), DISTINCT), 

38 Metric("active_users_30d", _("Active users, 30 days"), DISTINCT), 

39 Metric("chat_users", _("Users who chatted"), DISTINCT), 

40 Metric("contributors", _("Users who contributed"), DISTINCT), 

41 Metric("conversations", _("Conversations started"), SUM), 

42 Metric("conversations_by_language", _("Conversations by language"), SUM, _("Language"), suppress_small=True), 

43 Metric("messages_user", _("Messages sent"), SUM), 

44 Metric("messages_assistant", _("Assistant replies"), SUM), 

45 Metric("runs", _("Reply attempts"), SUM, _("Status")), 

46 Metric("reply_time_median_ms", _("Reply time, median (ms)"), DURATION), 

47 Metric("reply_time_p90_ms", _("Reply time, 90th percentile (ms)"), DURATION), 

48 Metric("tool_calls", _("Tool calls"), SUM, _("Status")), 

49 Metric("submissions", _("Submissions"), SUM), 

50 Metric("submissions_by_status", _("Submissions by status"), SUM, _("Status")), 

51 Metric("submissions_by_language", _("Submissions by language"), SUM, _("Language"), suppress_small=True), 

52 Metric("consent", _("Current consent"), SNAPSHOT, _("Scope and tier"), suppress_small=True), 

53 Metric("retention_cohort_size", _("Cohort size"), COHORT), 

54 Metric("retention_active", _("Cohort users active"), COHORT, _("Weeks after joining")), 

55 ) 

56} 

57 

58# Metrics stored under the day they describe (everything except retention). 

59DAILY_METRICS = tuple(name for name, metric in METRICS.items() if metric.kind != COHORT) 

60RETENTION_METRICS = tuple(name for name, metric in METRICS.items() if metric.kind == COHORT) 

61 

62 

63def is_suppressed(name: str, dimension: str, value: int, min_group_size: int) -> bool: 

64 """True if ``value`` is a small, non-zero count in a sensitive breakdown.""" 

65 metric = METRICS.get(name) 

66 return bool(metric and metric.suppress_small and dimension and 0 < value < min_group_size)