Coverage for seedboxsync/core/database/models/model.py: 100%
32 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-11 07:37 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-11 07:37 +0000
1#
2# Copyright (C) 2015-2026 Guillaume Kulakowski <guillaume@kulakowski.fr>
3#
4# For the full copyright and license information, please view the LICENSE
5# file that was distributed with this source code.
6#
7"""Peewee model."""
9from abc import abstractmethod
10import datetime
11from typing import Any, cast
12from peewee import Model
15class SeedboxSyncModel(Model):
16 """Basemodel from which all other peewee models are derived."""
19class SeedboxSyncWithStatsModel(SeedboxSyncModel):
20 """Basemodel from which all other peewee models are derived."""
22 @classmethod
23 def _get_trend(cls, current: int, previous: int) -> str:
24 """
25 Calculate the percentage change between two values.
27 Args:
28 current: Current period value.
29 previous: Previous equivalent period value.
31 Returns:
32 Percentage change formatted with an explicit sign.
33 """
34 if previous == 0:
35 return "+∞%" if current > 0 else "0.0%"
37 percentage = ((current - previous) / previous) * 100
38 return f"{percentage:+.1f}%"
40 @classmethod
41 def get_stats(cls) -> dict[str, object]:
42 """
43 Calculate summary download statistics across predefined time periods.
45 Queries finished downloads to calculate total count, total byte size,
46 human-readable file sizes, and the percentage trend compared with the
47 previous equivalent period.
49 Returns:
50 dict[str, dict[str, Any]]: A dictionary mapping each period name
51 ("day", "week", "last24h", "last7d", "last30d", "month", "year")
52 to a dictionary containing:
53 - total (int): Total count of finished downloads in the period.
54 - size (int | None): Total size in bytes.
55 - human_size (str | None): Formatted size string.
56 - trend (str): Percentage change compared with the previous
57 equivalent period (e.g. "+12.5%", "-8.3%", "0.0%").
58 """
59 now = datetime.datetime.now()
60 today = now.replace(hour=0, minute=0, second=0, microsecond=0)
62 periods = {
63 "day": {
64 "start": today,
65 "previous_start": today - datetime.timedelta(days=1),
66 "previous_end": today,
67 },
68 "week": {
69 "start": today - datetime.timedelta(days=today.weekday()),
70 "previous_start": today - datetime.timedelta(days=today.weekday() + 7),
71 "previous_end": today - datetime.timedelta(days=today.weekday()),
72 },
73 "month": {
74 "start": today.replace(day=1),
75 "previous_start": (today.replace(day=1) - datetime.timedelta(days=1)).replace(day=1),
76 "previous_end": today.replace(day=1),
77 },
78 "year": {
79 "start": today.replace(month=1, day=1),
80 "previous_start": today.replace(
81 year=today.year - 1,
82 month=1,
83 day=1,
84 ),
85 "previous_end": today.replace(month=1, day=1),
86 },
87 "last24h": {
88 "start": now - datetime.timedelta(hours=24),
89 "previous_start": now - datetime.timedelta(hours=48),
90 "previous_end": now - datetime.timedelta(hours=24),
91 },
92 "last7d": {
93 "start": now - datetime.timedelta(days=7),
94 "previous_start": now - datetime.timedelta(days=14),
95 "previous_end": now - datetime.timedelta(days=7),
96 },
97 "last30d": {
98 "start": now - datetime.timedelta(days=30),
99 "previous_start": now - datetime.timedelta(days=60),
100 "previous_end": now - datetime.timedelta(days=30),
101 },
102 }
104 data = {}
106 for name, period in periods.items():
107 row = cls._get_period_stats(period["start"], period.get("end", None))
108 previous_row = cls._get_period_stats(period["previous_start"], period["previous_end"])
110 current_total = int(row["total"] or 0)
111 previous_total = int(previous_row["total"] or 0)
112 row["trend_total"] = cls._get_trend(current_total, previous_total)
114 current_size = int(row["size"] or 0)
115 previous_size = int(previous_row["size"] or 0)
116 row["trend_size"] = cls._get_trend(current_size, previous_size)
118 data[name] = row
120 return cast(dict[str, Any], data)
122 @classmethod
123 @abstractmethod
124 def _get_period_stats(cls, start: datetime.datetime, end: datetime.datetime | None) -> dict[str, Any]:
125 """Abstract method to calculate statistics for a given period. Must be implemented by subclasses."""