2121API_URL = "https://fastapiproject-1-eziw.onrender.com/blue"
2222prices_cache = {}
2323
24+ # Domain configurations
25+ COUNTRY_CONFIG = {
26+ 'ar' : {
27+ 'domain' : 'mercadolibre.com.ar' ,
28+ 'currency' : 'ARS' ,
29+ 'country_name' : 'Argentina' ,
30+ 'exchange_rate_api' : API_URL ,
31+ 'needs_exchange_rate' : True
32+ },
33+ 'br' : {
34+ 'domain' : 'mercadolivre.com.br' ,
35+ 'currency' : 'BRL' ,
36+ 'country_name' : 'Brasil' ,
37+ 'exchange_rate_api' : None ,
38+ 'needs_exchange_rate' : False ,
39+ 'fixed_usd_rate' : 5.0 # Fixed approximate exchange rate for Brazil (1 USD = ~5 BRL)
40+ }
41+ }
42+
43+ # Default exchange rates (will be updated by API calls)
44+ exchange_rates = {
45+ 'ar' : None ,
46+ 'br' : None
47+ }
48+
2449
2550@app .template_filter ("format_number" )
2651def format_number (value ):
@@ -30,29 +55,42 @@ def format_number(value):
3055 return value
3156
3257
33- exchange_rate = None
34-
35-
36- def get_exchange_rate ():
58+ def get_exchange_rate (country_code = 'ar' ):
3759 """Fetch the current exchange rate from the API."""
38- global exchange_rate
39- if exchange_rate is not None :
40- return exchange_rate
60+ global exchange_rates
61+
62+ country_config = COUNTRY_CONFIG [country_code ]
63+
64+ # If we don't need an exchange rate for this country, use the fixed rate
65+ if not country_config ['needs_exchange_rate' ]:
66+ return f"{ country_config ['fixed_usd_rate' ]} { country_config ['currency' ]} "
67+
68+ # If we already have a cached rate, return it
69+ if exchange_rates [country_code ] is not None :
70+ return exchange_rates [country_code ]
71+
72+ # Otherwise fetch from API (for Argentina)
4173 try :
42- response = requests .get (API_URL )
74+ response = requests .get (country_config [ 'exchange_rate_api' ] )
4375 response .raise_for_status ()
44- exchange_rate = response .json ().get ("venta" , None )
45- return exchange_rate
76+ exchange_rates [ country_code ] = response .json ().get ("venta" , None )
77+ return exchange_rates [ country_code ]
4678 except requests .exceptions .RequestException as e :
47- app .logger .error (f"Error fetching exchange rate: { e } " )
48- return None
79+ app .logger .error (f"Error fetching exchange rate for { country_code } : { e } " )
80+ # Return a fallback value if the API fails
81+ return f"1000 { country_config ['currency' ]} "
4982
5083
5184@app .route ("/" , methods = ["GET" , "POST" ])
5285def index ():
5386 if request .method == "POST" :
5487 item = request .form ["item" ].strip ()
5588 number_of_pages = request .form ["number_of_pages" ].strip ()
89+ country_code = request .form .get ("country" , "ar" ) # Default to Argentina if not specified
90+
91+ # Validate country code
92+ if country_code not in COUNTRY_CONFIG :
93+ return render_template ("error.html" , error_message = "Invalid country code." ), 400
5694
5795 # Validate item
5896 if not item or not re .match (r"^[a-zA-Z0-9\-\s]+$" , item ):
@@ -66,7 +104,7 @@ def index():
66104 except ValueError :
67105 return render_template ("error.html" , error_message = "Number of pages must be a valid integer." ), 400
68106
69- return redirect (url_for ("show_plot" , item = item , number_of_pages = number_of_pages ))
107+ return redirect (url_for ("show_plot" , item = item , number_of_pages = number_of_pages , country = country_code ))
70108 return render_template ("index.html" )
71109
72110
@@ -85,17 +123,19 @@ def serve_assetlinks():
85123 return send_file ('assetlinks.json' , mimetype = 'application/json' )
86124
87125
88- def get_prices (item , number_of_pages ):
89- """Fetch the prices of the given item from MercadoLibre."""
90- cache_key = (item , number_of_pages )
126+ def get_prices (item , number_of_pages , country_code = 'ar' ):
127+ """Fetch the prices of the given item from MercadoLibre/MercadoLivre ."""
128+ cache_key = (item , number_of_pages , country_code )
91129 if cache_key in prices_cache :
92130 return prices_cache [cache_key ]
93131
94132 prices_list = []
95133 failed_pages = 0
134+ domain = COUNTRY_CONFIG [country_code ]['domain' ]
135+
96136 for i in range (number_of_pages ):
97137 start_item = i * 50 + 1
98- url = f"https://listado.mercadolibre.com.ar /{ item } _Desde_{ start_item } _NoIndex_True"
138+ url = f"https://listado.{ domain } /{ item } _Desde_{ start_item } _NoIndex_True"
99139 try :
100140 response = requests .get (url )
101141 response .raise_for_status ()
@@ -116,6 +156,11 @@ def get_prices(item, number_of_pages):
116156 app .logger .info ("No results found for the given search." )
117157 return None , None , failed_pages
118158
159+ # For Brazilian prices, convert from centavos to reais if needed
160+ if country_code == 'br' and any (p > 10000 for p in prices_list ):
161+ # Check if the prices seem to be in centavos (very high numbers)
162+ prices_list = [p / 100 for p in prices_list ]
163+
119164 prices_cache [cache_key ] = (prices_list , url , failed_pages )
120165 return prices_list , url , failed_pages
121166
@@ -125,12 +170,21 @@ def format_x(value, tick_number):
125170 return f"{ int (value ):,} "
126171
127172
128- def plot_prices (prices_list , item , url , failed_pages , filter_outliers = True , threshold = 3 ):
129- venta_dolar = get_exchange_rate ()
130- if not venta_dolar :
131- app .logger .error ("Failed to get exchange rate." )
173+ def plot_prices (prices_list , item , url , failed_pages , country_code = 'ar' , filter_outliers = True , threshold = 3 ):
174+ country_config = COUNTRY_CONFIG [country_code ]
175+ currency = country_config ['currency' ]
176+ country_name = country_config ['country_name' ]
177+
178+ venta_dolar_str = get_exchange_rate (country_code )
179+ if not venta_dolar_str :
180+ app .logger .error (f"Failed to get exchange rate for { country_code } ." )
132181 return None
133- venta_dolar = float (venta_dolar .replace (" ARS" , "" ))
182+
183+ try :
184+ venta_dolar = float (venta_dolar_str .replace (f" { currency } " , "" ))
185+ except (ValueError , TypeError ):
186+ app .logger .error (f"Failed to parse exchange rate: { venta_dolar_str } " )
187+ venta_dolar = country_config .get ('fixed_usd_rate' , 1000 ) # Fallback
134188
135189 # Compute statistics on the full dataset
136190 std_dev = np .std (prices_list )
@@ -165,11 +219,11 @@ def plot_prices(prices_list, item, url, failed_pages, filter_outliers=True, thre
165219 else 10000
166220 )
167221
168- plt .xlabel ("Price in ARS " )
222+ plt .xlabel (f "Price in { currency } " )
169223 plt .ylabel ("Frequency" )
170224 current_date = datetime .date .today ().strftime ("%d/%m/%Y" )
171225 plt .title (
172- f'Histogram of { item .replace ("-" , " " ).upper ()} prices in MercadoLibre Argentina ({ current_date } )\n '
226+ f'Histogram of { item .replace ("-" , " " ).upper ()} prices in MercadoLi { "v" if country_code == "br" else "b" } re { country_name } ({ current_date } )\n '
173227 f"Number of items indexed: { len (prices_list )} ({ request .args .get ('number_of_pages' )} pages)\n "
174228 f"URL: { url } \n "
175229 f"Failed to parse { failed_pages } pages."
@@ -180,7 +234,7 @@ def plot_stat_line(stat_value, color, label, linestyle="solid", linewidth=1):
180234 plt .text (
181235 stat_value + x_pos_offset ,
182236 y_position ,
183- f"{ label } : { int (stat_value ):,} ARS ({ int (stat_value / venta_dolar ):,} USD)" ,
237+ f"{ label } : { int (stat_value ):,} { currency } ({ int (stat_value / venta_dolar ):,} USD)" ,
184238 rotation = 90 ,
185239 color = color ,
186240 )
@@ -213,6 +267,11 @@ def plot_stat_line(stat_value, color, label, linestyle="solid", linewidth=1):
213267def show_plot ():
214268 item = request .args .get ("item" , "" ).strip ()
215269 number_of_pages = request .args .get ("number_of_pages" , "" ).strip ()
270+ country_code = request .args .get ("country" , "ar" ) # Default to Argentina if not specified
271+
272+ # Validate country code
273+ if country_code not in COUNTRY_CONFIG :
274+ return render_template ("error.html" , error_message = "Invalid country code." ), 400
216275
217276 # Validate item
218277 if not item or not re .match (r"^[a-zA-Z0-9\-\s]+$" , item ):
@@ -226,7 +285,7 @@ def show_plot():
226285 except ValueError :
227286 return render_template ("error.html" , error_message = "Number of pages must be a valid integer." ), 400
228287
229- prices_list , url , failed_pages = get_prices (item , number_of_pages )
288+ prices_list , url , failed_pages = get_prices (item , number_of_pages , country_code )
230289 if prices_list is None or url is None :
231290 error_message = "Failed to fetch prices. Please try searching fewer pages or check the item name."
232291 return render_template ("error.html" , error_message = error_message ), 500
@@ -238,10 +297,19 @@ def show_plot():
238297 std_dev = float (np .std (prices_list ))
239298 percentile_25 = float (np .percentile (prices_list , 25 ))
240299 current_date = datetime .date .today ().strftime ("%d/%m/%Y" )
241- exchange_rate = float (get_exchange_rate ().replace (" ARS" , "" ))
300+
301+ country_config = COUNTRY_CONFIG [country_code ]
302+ currency = country_config ['currency' ]
303+
304+ # Get exchange rate - use fixed rate for Brazil
305+ if country_config ['needs_exchange_rate' ]:
306+ exchange_rate_str = get_exchange_rate (country_code )
307+ exchange_rate = float (exchange_rate_str .replace (f" { currency } " , "" ))
308+ else :
309+ exchange_rate = country_config ['fixed_usd_rate' ]
242310
243311 # Generate the plot for backward compatibility and image download
244- plot_base64 = plot_prices (prices_list , item , url , failed_pages )
312+ plot_base64 = plot_prices (prices_list , item , url , failed_pages , country_code )
245313 if plot_base64 is None :
246314 error_message = "Failed to generate plot. Please try again later."
247315 return render_template ("error.html" , error_message = error_message ), 500
@@ -278,6 +346,9 @@ def show_plot():
278346 max_price_usd = int (max_price / exchange_rate ),
279347 min_price_usd = int (min_price / exchange_rate ),
280348 failed_pages = failed_pages ,
349+ country_code = country_code ,
350+ currency = country_config ['currency' ],
351+ country_name = country_config ['country_name' ],
281352 )
282353
283354
0 commit comments