Coverage for jstark / feature_period.py: 87%
70 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-23 22:34 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-23 22:34 +0000
1"""
2Encapsulate the period of a feature, defined by a unit of time
3measure, a start and an end
4"""
6from datetime import date
8from jstark.period_unit_of_measure import PeriodUnitOfMeasure
9from .exceptions import FeaturePeriodEndGreaterThanStartError
12class FeaturePeriod:
13 """
14 Encapsulate the period of a feature, defined by a unit of time
15 measure, a start and an end
16 """
18 def __init__(
19 self, period_unit_of_measure: PeriodUnitOfMeasure, start: int, end: int
20 ) -> None:
21 if not isinstance(period_unit_of_measure, PeriodUnitOfMeasure):
22 raise TypeError(
23 (
24 "period_unit_of_measure needs to be of type "
25 + f"PeriodUnitOfMeasure, not {type(period_unit_of_measure)}"
26 )
27 )
28 if end > start:
29 raise FeaturePeriodEndGreaterThanStartError(start=start, end=end)
30 self.__period_unit_of_measure = period_unit_of_measure
31 self.__start = start
32 self.__end = end
34 @property
35 def start(self) -> int:
36 "Number of periods ago that the FeaturePeriod begins at"
37 return self.__start
39 @property
40 def end(self) -> int:
41 "Number of periods ago that the FeaturePeriod ends at"
42 return self.__end
44 @property
45 def period_unit_of_measure(self) -> PeriodUnitOfMeasure:
46 "Period unit of measure"
47 return self.__period_unit_of_measure
49 @property
50 def mnemonic(self) -> str:
51 "Mnemonic for the feature period"
52 return f"{self.start}{self.period_unit_of_measure.value}{self.end}"
54 @property
55 def description(self) -> str:
56 """Description of the feature period
58 Pretty sure this will change in time, but this initial implementation
59 will do for now
61 Returns:
62 str: description
63 """
64 return (
65 f"Between {self.start} and {self.end} "
66 + f"{self.period_unit_of_measure.name.lower()}s ago"
67 )
69 @property
70 def number_of_periods(self) -> int:
71 "Number of periods between start and end (inclusive)"
72 return self.start - self.end + 1
74 def __str__(self) -> str:
75 return self.description
77 def __repr__(self) -> str:
78 return (
79 f"FeaturePeriod("
80 f"period_unit_of_measure={self.period_unit_of_measure}, "
81 f"start={self.start}, end={self.end})"
82 )
84 def __eq__(self, other: object) -> bool:
85 if not isinstance(other, FeaturePeriod):
86 return False
87 return (
88 self.period_unit_of_measure == other.period_unit_of_measure
89 and self.start == other.start
90 and self.end == other.end
91 )
93 def __hash__(self) -> int:
94 return hash((self.period_unit_of_measure, self.start, self.end))
97TODAY = {
98 "as_at": date.today(),
99 "feature_periods": [FeaturePeriod(PeriodUnitOfMeasure.DAY, 0, 0)],
100}
101YESTERDAY = {
102 "as_at": date.today(),
103 "feature_periods": [FeaturePeriod(PeriodUnitOfMeasure.DAY, 1, 1)],
104}
105THIS_WEEK = {
106 "as_at": date.today(),
107 "feature_periods": [FeaturePeriod(PeriodUnitOfMeasure.WEEK, 0, 0)],
108}
109LAST_WEEK = {
110 "as_at": date.today(),
111 "feature_periods": [FeaturePeriod(PeriodUnitOfMeasure.WEEK, 1, 1)],
112}
113THIS_MONTH = {
114 "as_at": date.today(),
115 "feature_periods": [FeaturePeriod(PeriodUnitOfMeasure.MONTH, 0, 0)],
116}
117LAST_MONTH = {
118 "as_at": date.today(),
119 "feature_periods": [FeaturePeriod(PeriodUnitOfMeasure.MONTH, 1, 1)],
120}
121THIS_QUARTER = {
122 "as_at": date.today(),
123 "feature_periods": [FeaturePeriod(PeriodUnitOfMeasure.QUARTER, 0, 0)],
124}
125LAST_QUARTER = {
126 "as_at": date.today(),
127 "feature_periods": [FeaturePeriod(PeriodUnitOfMeasure.QUARTER, 1, 1)],
128}
129THIS_YEAR = {
130 "as_at": date.today(),
131 "feature_periods": [FeaturePeriod(PeriodUnitOfMeasure.YEAR, 0, 0)],
132}
133LAST_YEAR = {
134 "as_at": date.today(),
135 "feature_periods": [FeaturePeriod(PeriodUnitOfMeasure.YEAR, 1, 1)],
136}
137current_year = date.today().year
138current_month = date.today().month
139match current_month:
140 case 1 | 2 | 3: 140 ↛ 142line 140 didn't jump to line 142 because the pattern on line 140 always matched
141 quarters_this_year = [1]
142 case 4 | 5 | 6:
143 quarters_this_year = [1, 2]
144 case 7 | 8 | 9:
145 quarters_this_year = [1, 2, 3]
146 case _: # all other months:
147 quarters_this_year = [1, 2, 3, 4]
148ALL_MONTHS_LAST_YEAR = {
149 "as_at": date(current_year, 1, 1),
150 "feature_periods": [
151 FeaturePeriod(PeriodUnitOfMeasure.MONTH, i, i) for i in range(1, 13)
152 ],
153}
154ALL_MONTHS_THIS_YEAR = {
155 "as_at": date.today(),
156 "feature_periods": [
157 FeaturePeriod(PeriodUnitOfMeasure.MONTH, i, i) for i in range(current_month)
158 ],
159}
160ALL_QUARTERS_LAST_YEAR = {
161 "as_at": date(current_year, 1, 1),
162 "feature_periods": [
163 FeaturePeriod(PeriodUnitOfMeasure.QUARTER, i, i) for i in range(1, 5)
164 ],
165}
166ALL_QUARTERS_THIS_YEAR = {
167 "as_at": date.today(),
168 "feature_periods": [
169 FeaturePeriod(PeriodUnitOfMeasure.QUARTER, i - 1, i - 1)
170 for i in quarters_this_year
171 ],
172}
173THIS_MONTH_VS_ONE_YEAR_PRIOR = {
174 "as_at": date.today(),
175 "feature_periods": [
176 FeaturePeriod(PeriodUnitOfMeasure.MONTH, 0, 0),
177 FeaturePeriod(PeriodUnitOfMeasure.MONTH, 12, 12),
178 ],
179}
180LAST_MONTH_VS_ONE_YEAR_PRIOR = {
181 "as_at": date.today(),
182 "feature_periods": [
183 FeaturePeriod(PeriodUnitOfMeasure.MONTH, 1, 1),
184 FeaturePeriod(PeriodUnitOfMeasure.MONTH, 13, 13),
185 ],
186}
187THIS_QUARTER_VS_ONE_YEAR_PRIOR = {
188 "as_at": date.today(),
189 "feature_periods": [
190 FeaturePeriod(PeriodUnitOfMeasure.QUARTER, 0, 0),
191 FeaturePeriod(PeriodUnitOfMeasure.QUARTER, 4, 4),
192 ],
193}
195LAST_QUARTER_VS_ONE_YEAR_PRIOR = {
196 "as_at": date.today(),
197 "feature_periods": [
198 FeaturePeriod(PeriodUnitOfMeasure.QUARTER, 1, 1),
199 FeaturePeriod(PeriodUnitOfMeasure.QUARTER, 5, 5),
200 ],
201}
203LAST_FIVE_YEARS = {
204 "as_at": date.today(),
205 "feature_periods": [
206 FeaturePeriod(PeriodUnitOfMeasure.YEAR, i, i) for i in range(1, 6)
207 ],
208}