notify-utils 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Naruto Uzumaki
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,8 @@
1
+ include LICENSE
2
+ include README.md
3
+ recursive-include notify_utils *.py
4
+ exclude main.py
5
+ exclude CLAUDE.md
6
+ prune .vscode
7
+ prune .claude
8
+ prune .venv
@@ -0,0 +1,133 @@
1
+ Metadata-Version: 2.4
2
+ Name: notify-utils
3
+ Version: 0.0.1
4
+ Summary: Biblioteca Python para parsing de preços de scraping, cálculo de descontos e análise estatística de histórico de preços.
5
+ Author-email: Naruto Uzumaki <naruto_uzumaki@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/jefersonAlbara/notify-utils
8
+ Project-URL: Repository, https://github.com/jefersonAlbara/notify-utils
9
+ Project-URL: Issues, https://github.com/jefersonAlbara/notify-utils/issues
10
+ Keywords: price-tracking,discount-calculator,web-scraping,e-commerce,price-history,discount-analysis,promotion-detection,statistics
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
17
+ Classifier: Topic :: Office/Business :: Financial
18
+ Requires-Python: >=3.12
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: discord-webhook>=1.4.1
22
+ Dynamic: license-file
23
+
24
+ # notify-utils
25
+
26
+ Biblioteca Python para parsing de preços de scraping, cálculo de descontos e análise estatística de histórico de preços.
27
+
28
+ ## Funcionalidades
29
+
30
+ - **Parser de Preços**: Normaliza strings de preços de diferentes formatos (BR, US)
31
+ - **Cálculo de Descontos**: Detecta descontos reais vs anunciados usando histórico
32
+ - **Análise Estatística**: Média, mediana, tendências e volatilidade de preços
33
+ - **Validação de Preços**: Sistema inteligente para validar preços antes de adicionar ao histórico
34
+ - **Notificações Discord**: Envio de alertas de preço via webhook (opcional)
35
+
36
+ ## Instalação
37
+
38
+ ```bash
39
+ pip install notify-utils
40
+ ```
41
+
42
+ ## Uso Básico
43
+
44
+ ### Parsing de Preços
45
+
46
+ ```python
47
+ from notify_utils import parse_price
48
+
49
+ preco = parse_price("R$ 1.299,90") # → 1299.90
50
+ preco = parse_price("$1,299.90") # → 1299.90
51
+ ```
52
+
53
+ ### Cálculo de Desconto com Histórico
54
+
55
+ ```python
56
+ from notify_utils import Price, get_discount_info
57
+ from datetime import datetime, timedelta
58
+
59
+ # Histórico de preços
60
+ precos = [
61
+ Price(value=1299.90, date=datetime.now() - timedelta(days=60)),
62
+ Price(value=1199.90, date=datetime.now() - timedelta(days=30)),
63
+ ]
64
+
65
+ # Calcular desconto real baseado no histórico
66
+ info = get_discount_info(
67
+ current_price=899.90,
68
+ price_history=precos,
69
+ period_days=30
70
+ )
71
+
72
+ print(f"Desconto real: {info.discount_percentage:.2f}%")
73
+ print(f"É desconto real? {info.is_real_discount}")
74
+ ```
75
+
76
+ ### Análise de Tendência
77
+
78
+ ```python
79
+ from notify_utils import calculate_price_trend
80
+
81
+ trend = calculate_price_trend(precos, days=30)
82
+
83
+ print(f"Direção: {trend.direction}") # 'increasing', 'decreasing', 'stable'
84
+ print(f"Mudança: {trend.change_percentage:.2f}%")
85
+ print(f"Confiança: {trend.confidence}")
86
+ ```
87
+
88
+ ### Validação de Preços
89
+
90
+ ```python
91
+ from notify_utils import PriceHistory, Price, PriceAdditionStrategy
92
+
93
+ history = PriceHistory(product_id="PROD123", prices=precos)
94
+
95
+ # Validar antes de adicionar
96
+ novo_preco = Price(value=899.90, date=datetime.now())
97
+ result = history.add_price(
98
+ novo_preco,
99
+ strategy=PriceAdditionStrategy.SMART
100
+ )
101
+
102
+ if result.action.value == "added":
103
+ print(f"Preço adicionado: R$ {result.affected_price.value:.2f}")
104
+ ```
105
+
106
+ ### Notificações Discord
107
+
108
+ ```python
109
+ from notify_utils import Product, DiscordEmbedBuilder
110
+
111
+ produto = Product(
112
+ product_id="PROD123",
113
+ name="Notebook Gamer",
114
+ url="https://loja.com/produto"
115
+ )
116
+
117
+ builder = DiscordEmbedBuilder()
118
+ embed = builder.build_embed(produto, info, precos)
119
+ # Enviar via webhook Discord
120
+ ```
121
+
122
+ ## Documentação Completa
123
+
124
+ Para mais detalhes, consulte o arquivo [CLAUDE.md](CLAUDE.md) na raiz do projeto.
125
+
126
+ ## Requisitos
127
+
128
+ - Python >= 3.12
129
+ - discord-webhook >= 1.4.1 (opcional, apenas para notificações)
130
+
131
+ ## Licença
132
+
133
+ MIT
@@ -0,0 +1,110 @@
1
+ # notify-utils
2
+
3
+ Biblioteca Python para parsing de preços de scraping, cálculo de descontos e análise estatística de histórico de preços.
4
+
5
+ ## Funcionalidades
6
+
7
+ - **Parser de Preços**: Normaliza strings de preços de diferentes formatos (BR, US)
8
+ - **Cálculo de Descontos**: Detecta descontos reais vs anunciados usando histórico
9
+ - **Análise Estatística**: Média, mediana, tendências e volatilidade de preços
10
+ - **Validação de Preços**: Sistema inteligente para validar preços antes de adicionar ao histórico
11
+ - **Notificações Discord**: Envio de alertas de preço via webhook (opcional)
12
+
13
+ ## Instalação
14
+
15
+ ```bash
16
+ pip install notify-utils
17
+ ```
18
+
19
+ ## Uso Básico
20
+
21
+ ### Parsing de Preços
22
+
23
+ ```python
24
+ from notify_utils import parse_price
25
+
26
+ preco = parse_price("R$ 1.299,90") # → 1299.90
27
+ preco = parse_price("$1,299.90") # → 1299.90
28
+ ```
29
+
30
+ ### Cálculo de Desconto com Histórico
31
+
32
+ ```python
33
+ from notify_utils import Price, get_discount_info
34
+ from datetime import datetime, timedelta
35
+
36
+ # Histórico de preços
37
+ precos = [
38
+ Price(value=1299.90, date=datetime.now() - timedelta(days=60)),
39
+ Price(value=1199.90, date=datetime.now() - timedelta(days=30)),
40
+ ]
41
+
42
+ # Calcular desconto real baseado no histórico
43
+ info = get_discount_info(
44
+ current_price=899.90,
45
+ price_history=precos,
46
+ period_days=30
47
+ )
48
+
49
+ print(f"Desconto real: {info.discount_percentage:.2f}%")
50
+ print(f"É desconto real? {info.is_real_discount}")
51
+ ```
52
+
53
+ ### Análise de Tendência
54
+
55
+ ```python
56
+ from notify_utils import calculate_price_trend
57
+
58
+ trend = calculate_price_trend(precos, days=30)
59
+
60
+ print(f"Direção: {trend.direction}") # 'increasing', 'decreasing', 'stable'
61
+ print(f"Mudança: {trend.change_percentage:.2f}%")
62
+ print(f"Confiança: {trend.confidence}")
63
+ ```
64
+
65
+ ### Validação de Preços
66
+
67
+ ```python
68
+ from notify_utils import PriceHistory, Price, PriceAdditionStrategy
69
+
70
+ history = PriceHistory(product_id="PROD123", prices=precos)
71
+
72
+ # Validar antes de adicionar
73
+ novo_preco = Price(value=899.90, date=datetime.now())
74
+ result = history.add_price(
75
+ novo_preco,
76
+ strategy=PriceAdditionStrategy.SMART
77
+ )
78
+
79
+ if result.action.value == "added":
80
+ print(f"Preço adicionado: R$ {result.affected_price.value:.2f}")
81
+ ```
82
+
83
+ ### Notificações Discord
84
+
85
+ ```python
86
+ from notify_utils import Product, DiscordEmbedBuilder
87
+
88
+ produto = Product(
89
+ product_id="PROD123",
90
+ name="Notebook Gamer",
91
+ url="https://loja.com/produto"
92
+ )
93
+
94
+ builder = DiscordEmbedBuilder()
95
+ embed = builder.build_embed(produto, info, precos)
96
+ # Enviar via webhook Discord
97
+ ```
98
+
99
+ ## Documentação Completa
100
+
101
+ Para mais detalhes, consulte o arquivo [CLAUDE.md](CLAUDE.md) na raiz do projeto.
102
+
103
+ ## Requisitos
104
+
105
+ - Python >= 3.12
106
+ - discord-webhook >= 1.4.1 (opcional, apenas para notificações)
107
+
108
+ ## Licença
109
+
110
+ MIT
@@ -0,0 +1,90 @@
1
+ """
2
+ notify-utils
3
+
4
+ Biblioteca para parsing de preços, cálculo de descontos e análise estatística de histórico de preços.
5
+ """
6
+
7
+ from .models import (
8
+ Product,
9
+ Price,
10
+ PriceHistory,
11
+ DiscountResult,
12
+ HistoryDiscountResult,
13
+ DiscountInfo,
14
+ PriceTrend,
15
+ PriceComparisonStatus,
16
+ PriceAction,
17
+ PriceAdditionStrategy,
18
+ PriceComparisonResult
19
+ )
20
+ from .parser import parse_price
21
+ from .discount import (
22
+ calculate_discount_percentage,
23
+ calculate_discount_absolute,
24
+ is_real_discount,
25
+ calculate_discount_from_history,
26
+ get_discount_info,
27
+ get_best_discount_from_history
28
+ )
29
+ from .statistics import (
30
+ calculate_mean,
31
+ calculate_median,
32
+ get_min_max,
33
+ filter_by_period,
34
+ filter_by_period_range,
35
+ calculate_volatility,
36
+ calculate_price_trend,
37
+ calculate_price_statistics,
38
+ days_since_most_recent_price,
39
+ get_recommended_period
40
+ )
41
+
42
+ # Import de notifiers
43
+ from .notifiers import (
44
+ DiscordEmbedBuilder,
45
+ format_price,
46
+ format_price_history,
47
+ get_unique_prices
48
+ )
49
+
50
+ __version__ = "0.0.1"
51
+ __all__ = [
52
+ # Models
53
+ "Product",
54
+ "Price",
55
+ "PriceHistory",
56
+ "DiscountResult",
57
+ "HistoryDiscountResult",
58
+ "DiscountInfo",
59
+ "PriceTrend",
60
+ # Price Comparison
61
+ "PriceComparisonStatus",
62
+ "PriceAction",
63
+ "PriceAdditionStrategy",
64
+ "PriceComparisonResult",
65
+ # Parser
66
+ "parse_price",
67
+ # Discount functions
68
+ "calculate_discount_percentage",
69
+ "calculate_discount_absolute",
70
+ "is_real_discount",
71
+ "calculate_discount_from_history",
72
+ "get_discount_info",
73
+ "get_best_discount_from_history",
74
+ # Statistics
75
+ "calculate_mean",
76
+ "calculate_median",
77
+ "get_min_max",
78
+ "filter_by_period",
79
+ "filter_by_period_range",
80
+ "calculate_volatility",
81
+ "calculate_price_trend",
82
+ "calculate_price_statistics",
83
+ "days_since_most_recent_price",
84
+ "get_recommended_period",
85
+ # Notifiers (opcionais)
86
+ "DiscordEmbedBuilder",
87
+ "format_price",
88
+ "format_price_history",
89
+ "get_unique_prices",
90
+ ]
@@ -0,0 +1,335 @@
1
+ """
2
+ Cálculos de desconto e validação de promoções.
3
+ """
4
+
5
+ from typing import Optional, List
6
+ from .models import Price, PriceHistory, HistoryDiscountResult, DiscountInfo
7
+
8
+
9
+ def calculate_discount_percentage(old_price: float, new_price: float) -> float:
10
+ """
11
+ Calcula o percentual de desconto entre dois preços.
12
+
13
+ Formula: ((preço_antigo - preço_novo) / preço_antigo) * 100
14
+
15
+ Args:
16
+ old_price: Preço anterior (original)
17
+ new_price: Preço atual (com desconto)
18
+
19
+ Returns:
20
+ Percentual de desconto (valores positivos indicam desconto, negativos indicam aumento)
21
+
22
+ Raises:
23
+ ValueError: Se algum preço for negativo ou preço antigo for zero
24
+ """
25
+ if old_price < 0 or new_price < 0:
26
+ raise ValueError("Preços não podem ser negativos")
27
+
28
+ if old_price == 0:
29
+ raise ValueError("Preço antigo não pode ser zero")
30
+
31
+ discount = ((old_price - new_price) / old_price) * 100
32
+ return round(discount, 2)
33
+
34
+
35
+ def calculate_discount_absolute(old_price: float, new_price: float) -> float:
36
+ """
37
+ Calcula o valor absoluto do desconto.
38
+
39
+ Args:
40
+ old_price: Preço anterior
41
+ new_price: Preço atual
42
+
43
+ Returns:
44
+ Diferença entre os preços (positivo = desconto, negativo = aumento)
45
+
46
+ Raises:
47
+ ValueError: Se algum preço for negativo
48
+ """
49
+ if old_price < 0 or new_price < 0:
50
+ raise ValueError("Preços não podem ser negativos")
51
+
52
+ return round(old_price - new_price, 2)
53
+
54
+
55
+ def is_real_discount(
56
+ current_price: float,
57
+ advertised_old_price: Optional[float] = None,
58
+ price_history: Optional[List[Price]] = None,
59
+ threshold_percentage: float = 5.0
60
+ ) -> bool:
61
+ """
62
+ Verifica se um desconto anunciado é real.
63
+
64
+ Estratégia:
65
+ - COM histórico: Ignora advertised_old_price, compara preço atual com média histórica
66
+ - SEM histórico: Usa advertised_old_price como referência (primeira vez)
67
+
68
+ Args:
69
+ current_price: Preço atual anunciado
70
+ advertised_old_price: Preço "de" anunciado pela loja (usado apenas sem histórico)
71
+ price_history: Lista de preços históricos (opcional, mas prioritário)
72
+ threshold_percentage: Percentual mínimo para considerar desconto real (padrão: 5%)
73
+
74
+ Returns:
75
+ True se o desconto for considerado real, False caso contrário
76
+
77
+ Raises:
78
+ ValueError: Se não houver histórico nem advertised_old_price
79
+ """
80
+ # Se houver histórico, IGNORA advertised_old_price e usa apenas histórico
81
+ if price_history and len(price_history) > 0:
82
+ # Calcula a média dos preços históricos
83
+ avg_price = sum(p.value for p in price_history) / len(price_history)
84
+
85
+ # Calcula o desconto em relação à média histórica
86
+ discount_pct = ((avg_price - current_price) / avg_price) * 100
87
+
88
+ # Considera desconto real se for >= threshold_percentage
89
+ return discount_pct >= threshold_percentage
90
+
91
+ # Sem histórico: usa advertised_old_price (fallback para primeira vez)
92
+ if advertised_old_price is None:
93
+ raise ValueError("É necessário fornecer advertised_old_price ou price_history")
94
+
95
+ # Verifica se há desconto básico
96
+ return current_price < advertised_old_price
97
+
98
+
99
+ def calculate_discount_from_history(
100
+ current_price: float,
101
+ price_history: List[Price],
102
+ period_days: Optional[int] = 30,
103
+ use_median: bool = False,
104
+ skip_recent_days: int = 0
105
+ ) -> Optional[HistoryDiscountResult]:
106
+ """
107
+ Calcula desconto baseado no histórico de preços (ignora preço "de" anunciado).
108
+
109
+ Compara o preço atual com a média (ou mediana) do histórico de um período.
110
+
111
+ Args:
112
+ current_price: Preço atual do produto
113
+ price_history: Lista de preços históricos
114
+ period_days: Período em dias para calcular a referência (None = todos os preços)
115
+ use_median: Se True usa mediana, se False usa média (padrão)
116
+ skip_recent_days: Ignora os N dias mais recentes (útil para evitar ruído, padrão: 0)
117
+
118
+ Returns:
119
+ HistoryDiscountResult com informações do desconto ou None se não houver histórico suficiente
120
+
121
+ Example:
122
+ >>> # Ignora preços de 0-2 dias, usa apenas 3-30 dias
123
+ >>> result = calculate_discount_from_history(
124
+ ... current_price=899.90,
125
+ ... price_history=prices,
126
+ ... period_days=30,
127
+ ... skip_recent_days=3
128
+ ... )
129
+ """
130
+ if not price_history or len(price_history) == 0:
131
+ return None
132
+
133
+ # Importa funções de statistics
134
+ from .statistics import filter_by_period, filter_by_period_range, calculate_mean, calculate_median
135
+
136
+ # Filtra por período
137
+ if skip_recent_days > 0 and period_days:
138
+ # Usa intervalo: ignora dias recentes
139
+ filtered_prices = filter_by_period_range(price_history, skip_recent_days, period_days)
140
+ elif period_days:
141
+ # Comportamento padrão: de hoje até period_days
142
+ filtered_prices = filter_by_period(price_history, period_days)
143
+ else:
144
+ # Sem filtro de período
145
+ filtered_prices = price_history
146
+
147
+ if not filtered_prices:
148
+ return None
149
+
150
+ # Calcula preço de referência (média ou mediana)
151
+ if use_median:
152
+ reference_price = calculate_median(filtered_prices)
153
+ method = 'median'
154
+ else:
155
+ reference_price = calculate_mean(filtered_prices)
156
+ method = 'mean'
157
+
158
+ if reference_price is None:
159
+ return None
160
+
161
+ # Calcula desconto
162
+ discount_pct = calculate_discount_percentage(reference_price, current_price)
163
+ discount_abs = calculate_discount_absolute(reference_price, current_price)
164
+
165
+ return HistoryDiscountResult(
166
+ current_price=current_price,
167
+ reference_price=reference_price,
168
+ discount_percentage=discount_pct,
169
+ discount_absolute=discount_abs,
170
+ is_real_discount=discount_pct > 0, # Positivo = desconto real
171
+ calculation_method=method,
172
+ period_days=period_days,
173
+ samples_count=len(filtered_prices)
174
+ )
175
+
176
+
177
+ def get_discount_info(
178
+ current_price: float,
179
+ price_history: Optional[List[Price]] = None,
180
+ advertised_old_price: Optional[float] = None,
181
+ period_days: int = 30,
182
+ use_median: bool = False,
183
+ auto_adjust_period: bool = True,
184
+ skip_recent_days: int = 0
185
+ ) -> DiscountInfo:
186
+ """
187
+ Função inteligente que calcula desconto automaticamente escolhendo a melhor estratégia.
188
+
189
+ Estratégia:
190
+ - COM histórico: Usa média/mediana do histórico (ignora advertised_old_price)
191
+ - SEM histórico: Usa advertised_old_price (primeira vez)
192
+
193
+ Args:
194
+ current_price: Preço atual do produto
195
+ price_history: Lista de preços históricos (opcional, mas prioritário)
196
+ advertised_old_price: Preço "de" anunciado (usado apenas sem histórico)
197
+ period_days: Período para cálculo da média histórica (padrão: 30 dias)
198
+ use_median: Se True usa mediana, se False usa média
199
+ auto_adjust_period: Se True, ajusta period_days automaticamente para incluir
200
+ o histórico mais recente (recomendado: True)
201
+ skip_recent_days: Ignora os N dias mais recentes do histórico (padrão: 0)
202
+ Útil para evitar ruído de dados muito recentes
203
+
204
+ Returns:
205
+ DiscountInfo com informações completas do desconto
206
+
207
+ Raises:
208
+ ValueError: Se não houver histórico nem advertised_old_price
209
+
210
+ Example:
211
+ >>> # Histórico de 33 dias atrás, period_days=30
212
+ >>> prices = [Price(1299.90, datetime.now() - timedelta(days=33))]
213
+ >>> info = get_discount_info(899.90, prices, period_days=30, auto_adjust_period=True)
214
+ >>> info.period_days # 30 (solicitado)
215
+ >>> info.adjusted_period_days # 33 (ajustado)
216
+ >>> info.days_since_most_recent # 33
217
+
218
+ >>> # Ignorar preços de 0-2 dias, usar apenas 3-30 dias
219
+ >>> info = get_discount_info(899.90, prices, period_days=30, skip_recent_days=3)
220
+ >>> info.skip_recent_days # 3
221
+ """
222
+ # Estratégia 1: COM histórico - usa apenas histórico
223
+ if price_history and len(price_history) > 0:
224
+ # Importa funções de statistics
225
+ from .statistics import days_since_most_recent_price, get_recommended_period
226
+
227
+ # Calcula dias desde o mais recente
228
+ days_since = days_since_most_recent_price(price_history)
229
+
230
+ # Ajusta período se necessário
231
+ actual_period = period_days
232
+ if auto_adjust_period:
233
+ actual_period = get_recommended_period(price_history, period_days)
234
+
235
+ history_discount = calculate_discount_from_history(
236
+ current_price=current_price,
237
+ price_history=price_history,
238
+ period_days=actual_period,
239
+ use_median=use_median,
240
+ skip_recent_days=skip_recent_days
241
+ )
242
+
243
+ if history_discount:
244
+ return DiscountInfo(
245
+ current_price=history_discount.current_price,
246
+ reference_price=history_discount.reference_price,
247
+ discount_percentage=history_discount.discount_percentage,
248
+ discount_absolute=history_discount.discount_absolute,
249
+ is_real_discount=history_discount.is_real_discount,
250
+ strategy='history',
251
+ has_history=True,
252
+ calculation_method=history_discount.calculation_method,
253
+ period_days=period_days, # Período solicitado
254
+ samples_count=history_discount.samples_count,
255
+ adjusted_period_days=actual_period if auto_adjust_period else None,
256
+ days_since_most_recent=days_since,
257
+ skip_recent_days=skip_recent_days if skip_recent_days > 0 else None
258
+ )
259
+ else:
260
+ # Se nenhum preço no período, mas temos histórico, use todos os preços
261
+ history_discount = calculate_discount_from_history(
262
+ current_price=current_price,
263
+ price_history=price_history,
264
+ period_days=None, # Usa todos os preços disponíveis
265
+ use_median=use_median,
266
+ skip_recent_days=0 # Não pula dias quando usa todos
267
+ )
268
+
269
+ if history_discount:
270
+ return DiscountInfo(
271
+ current_price=history_discount.current_price,
272
+ reference_price=history_discount.reference_price,
273
+ discount_percentage=history_discount.discount_percentage,
274
+ discount_absolute=history_discount.discount_absolute,
275
+ is_real_discount=history_discount.is_real_discount,
276
+ strategy='history',
277
+ has_history=True,
278
+ calculation_method=history_discount.calculation_method,
279
+ period_days=period_days, # Período solicitado originalmente
280
+ samples_count=history_discount.samples_count,
281
+ adjusted_period_days=None, # Usou todos os preços, não apenas período
282
+ days_since_most_recent=days_since,
283
+ skip_recent_days=None # Fallback não usa skip
284
+ )
285
+
286
+ # Estratégia 2: SEM histórico - usa advertised_old_price
287
+ if advertised_old_price is None:
288
+ raise ValueError("É necessário fornecer price_history ou advertised_old_price")
289
+
290
+ discount_pct = calculate_discount_percentage(advertised_old_price, current_price)
291
+ discount_abs = calculate_discount_absolute(advertised_old_price, current_price)
292
+
293
+ return DiscountInfo(
294
+ current_price=current_price,
295
+ reference_price=advertised_old_price,
296
+ discount_percentage=discount_pct,
297
+ discount_absolute=discount_abs,
298
+ is_real_discount=discount_pct > 0, # Assume verdadeiro na primeira vez
299
+ strategy='advertised',
300
+ has_history=False,
301
+ calculation_method='advertised',
302
+ period_days=None,
303
+ samples_count=None,
304
+ adjusted_period_days=None,
305
+ days_since_most_recent=None,
306
+ skip_recent_days=None
307
+ )
308
+
309
+
310
+ def get_best_discount_from_history(price_history: PriceHistory) -> Optional[HistoryDiscountResult]:
311
+ """
312
+ Encontra o maior desconto válido comparando o preço atual com a média histórica.
313
+
314
+ DEPRECATED: Use calculate_discount_from_history() ou get_discount_info()
315
+
316
+ Args:
317
+ price_history: Histórico de preços do produto
318
+
319
+ Returns:
320
+ HistoryDiscountResult com informações do melhor desconto ou None se não houver desconto
321
+ """
322
+ current = price_history.get_current_price()
323
+
324
+ if not current or len(price_history.prices) < 2:
325
+ return None
326
+
327
+ # Usa calculate_discount_from_history internamente
328
+ historical_prices = price_history.prices[1:] # Exclui o preço atual
329
+
330
+ return calculate_discount_from_history(
331
+ current_price=current.value,
332
+ price_history=historical_prices,
333
+ period_days=None, # Usa todos os preços históricos
334
+ use_median=False # Usa média
335
+ )