dhruv575 commited on
Commit
471541d
·
1 Parent(s): 35213a7

optional image

Browse files
app/email_new_converter.py CHANGED
@@ -271,6 +271,10 @@ class EmailNewConverter:
271
  elif img_alt == "top news image":
272
  is_top_news_image = True
273
  logger.info(f"Identified top news image by alt text (should preserve aspect ratio): {src[:50]}...")
 
 
 
 
274
  else:
275
  # Check if image is in footer section by looking for footer comment in HTML structure
276
  # Get the HTML string representation of parent elements to check for footer comments
@@ -289,8 +293,8 @@ class EmailNewConverter:
289
  parent = parent.find_parent()
290
  depth += 1
291
 
292
- # Combine checks - if it's footer, top comment, or top news, don't make square
293
- is_non_square_image = is_footer_image or is_top_comment_image or is_top_news_image
294
 
295
  # Check if image is already on Cloudinary
296
  is_cloudinary = "res.cloudinary.com" in src
@@ -342,9 +346,9 @@ class EmailNewConverter:
342
  logger.warning(f"Cloudinary upload succeeded but no URL returned for {image_url[:50]}...")
343
  continue
344
 
345
- # For non-square images (footer, top comment, top news), preserve aspect ratio - no transformations
346
  if is_non_square_image:
347
- image_type = "footer" if is_footer_image else ("top comment" if is_top_comment_image else "top news")
348
  logger.info(f"{image_type.capitalize()} image processed (preserving aspect ratio): {src[:50]}...")
349
  img["src"] = cloudinary_url
350
  else:
 
271
  elif img_alt == "top news image":
272
  is_top_news_image = True
273
  logger.info(f"Identified top news image by alt text (should preserve aspect ratio): {src[:50]}...")
274
+ # Last word images can be identified by alt text
275
+ elif img_alt == "last word image":
276
+ is_last_word_image = True
277
+ logger.info(f"Identified last word image by alt text (should preserve aspect ratio): {src[:50]}...")
278
  else:
279
  # Check if image is in footer section by looking for footer comment in HTML structure
280
  # Get the HTML string representation of parent elements to check for footer comments
 
293
  parent = parent.find_parent()
294
  depth += 1
295
 
296
+ # Combine checks - if it's footer, top comment, top news, or last word, don't make square
297
+ is_non_square_image = is_footer_image or is_top_comment_image or is_top_news_image or is_last_word_image
298
 
299
  # Check if image is already on Cloudinary
300
  is_cloudinary = "res.cloudinary.com" in src
 
346
  logger.warning(f"Cloudinary upload succeeded but no URL returned for {image_url[:50]}...")
347
  continue
348
 
349
+ # For non-square images (footer, top comment, top news, last word), preserve aspect ratio - no transformations
350
  if is_non_square_image:
351
+ image_type = "footer" if is_footer_image else ("top comment" if is_top_comment_image else ("last word" if is_last_word_image else "top news"))
352
  logger.info(f"{image_type.capitalize()} image processed (preserving aspect ratio): {src[:50]}...")
353
  img["src"] = cloudinary_url
354
  else:
app/post_process.py CHANGED
@@ -302,14 +302,19 @@ def fix_cloudinary_image_transformations(html_content: str) -> str:
302
  # Check if it's a top news image (inside top_news_box or alt="Top news image")
303
  if 'alt="Top news image"' in full_tag or 'alt=\'Top news image\'' in full_tag:
304
  is_footer_or_top_news = True
305
- else:
306
- # Check if it's inside a top_news_box by looking for the class in nearby HTML
 
 
 
 
 
307
  img_pos = html_content.find(full_tag)
308
  if img_pos != -1:
309
- # Check if there's a top_news_box before this img tag (within 1000 chars)
310
  before_img = html_content[max(0, img_pos - 1000):img_pos]
311
- # Look for top_news_box class (handle both quoted and unquoted, with/without spaces)
312
- if re.search(r'class\s*=\s*["\']?[^"\'>]*top_news_box', before_img, re.IGNORECASE):
313
  is_footer_or_top_news = True
314
 
315
  # Only add transformations to market images (48px square images)
 
302
  # Check if it's a top news image (inside top_news_box or alt="Top news image")
303
  if 'alt="Top news image"' in full_tag or 'alt=\'Top news image\'' in full_tag:
304
  is_footer_or_top_news = True
305
+
306
+ # Check if it's a last word image (inside last_word_box or alt="Last word image")
307
+ if 'alt="Last word image"' in full_tag or 'alt=\'Last word image\'' in full_tag:
308
+ is_footer_or_top_news = True
309
+
310
+ if not is_footer_or_top_news:
311
+ # Check if it's inside a top_news_box or last_word_box by looking for the class in nearby HTML
312
  img_pos = html_content.find(full_tag)
313
  if img_pos != -1:
314
+ # Check if there's a top_news_box or last_word_box before this img tag (within 1000 chars)
315
  before_img = html_content[max(0, img_pos - 1000):img_pos]
316
+ # Look for top_news_box or last_word_box class (handle both quoted and unquoted, with/without spaces)
317
+ if re.search(r'class\s*=\s*["\']?[^"\'>]*(top_news_box|last_word_box)', before_img, re.IGNORECASE):
318
  is_footer_or_top_news = True
319
 
320
  # Only add transformations to market images (48px square images)
app/templateify_new_service.py CHANGED
@@ -805,9 +805,14 @@ class TemplateifyNewService:
805
  if "market-link" not in parent_link["class"]:
806
  parent_link["class"].append("market-link")
807
 
808
- # Find and tokenize the image
809
  img = node.find("img")
810
  if img:
 
 
 
 
 
811
  img["src"] = "{{LAST_WORD_IMAGE}}"
812
  register("{{LAST_WORD_IMAGE}}", "Last word image URL")
813
 
 
805
  if "market-link" not in parent_link["class"]:
806
  parent_link["class"].append("market-link")
807
 
808
+ # Find and tokenize the image, wrap in conditional
809
  img = node.find("img")
810
  if img:
811
+ # Wrap image in conditional block so it only shows if image URL is provided
812
+ opening_tag = NavigableString("{{#LAST_WORD_IMAGE}}")
813
+ closing_tag = NavigableString("{{/LAST_WORD_IMAGE}}")
814
+ img.insert_before(opening_tag)
815
+ img.insert_after(closing_tag)
816
  img["src"] = "{{LAST_WORD_IMAGE}}"
817
  register("{{LAST_WORD_IMAGE}}", "Last word image URL")
818
 
email3.py CHANGED
@@ -1050,35 +1050,33 @@ class PolymarketEmailGenerator:
1050
  return []
1051
 
1052
  def fetch_whale_moves(self) -> List[Dict[str, Any]]:
1053
- """Fetch whale moves/insider positions from ClickHouse"""
1054
  print("Fetching whale moves...")
1055
 
1056
- # ClickHouse configuration
1057
- query_id = os.getenv('CLICKHOUSE_QUERY_ID')
1058
- user = os.getenv('CLICKHOUSE_USER')
1059
- password = os.getenv('CLICKHOUSE_PASSWORD')
1060
 
1061
- if not all([query_id, user, password]):
1062
- print(" Warning: ClickHouse credentials not configured")
1063
- print(" Falling back to placeholder whale move data")
1064
- return self._placeholder_whale_moves()
1065
-
1066
- url = f'https://queries.clickhouse.cloud/run/{query_id}?format=JSONEachRow'
 
 
1067
 
1068
  max_retries = 3
1069
- timeout = 60
1070
 
1071
  for attempt in range(max_retries):
1072
  try:
1073
  if attempt > 0:
1074
  wait_time = 2 ** attempt
1075
  print(f" Retry {attempt}/{max_retries} after {wait_time}s...")
1076
- import time
1077
  time.sleep(wait_time)
1078
 
1079
  response = requests.get(
1080
  url,
1081
- auth=(user, password),
1082
  headers={'Content-Type': 'application/json'},
1083
  timeout=timeout
1084
  )
@@ -1086,77 +1084,102 @@ class PolymarketEmailGenerator:
1086
  break
1087
 
1088
  except requests.exceptions.RequestException as e:
1089
- error_msg = str(e)
1090
- # Check if it's a permissions error
1091
- if "Not enough privileges" in error_msg or "dim_users" in error_msg:
1092
- print(f" ⚠️ ClickHouse permissions error - the query needs access to core.dim_users table")
1093
- print(f" This may have changed on the ClickHouse side. Contact your admin to fix permissions.")
1094
- print(f" Skipping whale moves section...")
1095
  return self._placeholder_whale_moves()
1096
- else:
1097
- print(f" Warning: ClickHouse query attempt {attempt + 1}/{max_retries} failed: {e}")
1098
- if attempt == max_retries - 1:
1099
- print(f" Error: Failed after {max_retries} attempts")
1100
- return self._placeholder_whale_moves()
1101
 
1102
  # Process the successful response
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1103
  whale_moves = []
1104
 
1105
- for line in response.text.strip().split('\n'):
1106
- if not line:
1107
- continue
1108
-
1109
  try:
1110
- data = json.loads(line)
 
 
 
1111
 
1112
- total_value = float(data.get('total_open_value', 0))
1113
- if total_value >= 25000: # Lowered threshold to show more whale moves
1114
- open_values = [float(v) for v in data.get('open_values', [])]
1115
- market_titles = data.get('open_market_titles', [])
1116
- event_titles = data.get('open_event_titles', [])
1117
- outcome_names = data.get('open_outcome_names', [])
1118
-
1119
- if open_values:
1120
- max_idx = open_values.index(max(open_values))
1121
- market_title = market_titles[max_idx] if max_idx < len(market_titles) else ""
1122
- event_title = event_titles[max_idx] if max_idx < len(event_titles) else ""
1123
- outcome = outcome_names[max_idx] if max_idx < len(outcome_names) else ""
1124
-
1125
- # Use event title if market title is empty
1126
- display_market = market_title if market_title else event_title
1127
- if not display_market:
1128
- display_market = "Unknown Market"
1129
-
1130
- slug = event_title.lower().replace(' ', '-').replace('?', '')[:50] if event_title else ""
1131
-
1132
- outcome_str = f" on {outcome}" if outcome else ""
1133
- title = f"${total_value:,.0f} position{outcome_str}"
1134
-
1135
- user_name = data.get('user_name', 'Anonymous')
1136
-
1137
- whale_moves.append({
1138
- "title": title,
1139
- "market": display_market,
1140
- "event": event_title,
1141
- "slug": slug,
1142
- "amount": total_value,
1143
- "user_name": user_name,
1144
- "user_profile_url": user_name,
1145
- "distinct_positions": int(data.get('distinct_positions', 1)),
1146
- "timestamp": datetime.now().isoformat()
1147
- })
1148
 
1149
- except (json.JSONDecodeError, KeyError, ValueError) as e:
1150
- print(f" Warning: Failed to parse insider data: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1151
  continue
1152
 
 
1153
  whale_moves.sort(key=lambda x: x['amount'], reverse=True)
 
1154
 
1155
  if not whale_moves:
1156
- print(" No whale positions found, injecting placeholder data")
 
1157
  return self._placeholder_whale_moves()
1158
 
1159
- print(f" Found {len(whale_moves)} whale positions")
1160
 
1161
  # Enrich with market images and user images
1162
  whale_moves = self._enrich_whale_moves_with_images(whale_moves)
@@ -1166,34 +1189,44 @@ class PolymarketEmailGenerator:
1166
  def _enrich_whale_moves_with_images(self, whale_moves: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
1167
  """Enrich whale moves with market images and user profile images."""
1168
  print(" Fetching market images for whale moves...")
1169
- user_image_url = "https://i.ibb.co/23VYpRcK/polywhale.png"
1170
 
1171
  for move in whale_moves:
1172
- # Add user image (static for now)
1173
- move['user_image'] = user_image_url
 
 
 
 
1174
 
1175
- # Fetch market image using slug
1176
- slug = move.get('slug', '')
1177
- if slug:
1178
- try:
1179
- details = self.fetch_market_details(slug)
1180
- market_image = details.get('image', '')
1181
- move['market_image'] = market_image
1182
- if market_image:
1183
- print(f" ✓ Found image for {slug[:50]}")
1184
- else:
1185
- print(f" ⚠ No image found for {slug[:50]}")
1186
- except Exception as e:
1187
- print(f" ⚠ Error fetching image for {slug[:50]}: {e}")
1188
- move['market_image'] = ''
1189
  else:
1190
- print(f" ⚠ No slug available for market: {move.get('market', 'Unknown')}")
1191
- move['market_image'] = ''
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1192
 
1193
  return whale_moves
1194
 
1195
  def _placeholder_whale_moves(self) -> List[Dict[str, Any]]:
1196
- """Return fallback whale move data when ClickHouse is unavailable."""
1197
  placeholder_moves = _fresh_placeholder_whales()
1198
  print(f" Using {len(placeholder_moves)} placeholder whale entries")
1199
  # Enrich placeholder moves with images too
 
1050
  return []
1051
 
1052
  def fetch_whale_moves(self) -> List[Dict[str, Any]]:
1053
+ """Fetch whale moves from Polymarket trades API"""
1054
  print("Fetching whale moves...")
1055
 
1056
+ import time
 
 
 
1057
 
1058
+ # Polymarket trades API endpoint
1059
+ url = 'https://data-api.polymarket.com/trades'
1060
+ params = {
1061
+ 'limit': 100,
1062
+ 'takerOnly': 'true',
1063
+ 'filterType': 'CASH',
1064
+ 'filterAmount': 50000
1065
+ }
1066
 
1067
  max_retries = 3
1068
+ timeout = 30
1069
 
1070
  for attempt in range(max_retries):
1071
  try:
1072
  if attempt > 0:
1073
  wait_time = 2 ** attempt
1074
  print(f" Retry {attempt}/{max_retries} after {wait_time}s...")
 
1075
  time.sleep(wait_time)
1076
 
1077
  response = requests.get(
1078
  url,
1079
+ params=params,
1080
  headers={'Content-Type': 'application/json'},
1081
  timeout=timeout
1082
  )
 
1084
  break
1085
 
1086
  except requests.exceptions.RequestException as e:
1087
+ print(f" Warning: API request attempt {attempt + 1}/{max_retries} failed: {e}")
1088
+ if attempt == max_retries - 1:
1089
+ print(f" Error: Failed after {max_retries} attempts")
1090
+ print(" Falling back to placeholder whale move data")
 
 
1091
  return self._placeholder_whale_moves()
 
 
 
 
 
1092
 
1093
  # Process the successful response
1094
+ try:
1095
+ trades_data = response.json()
1096
+ except json.JSONDecodeError as e:
1097
+ print(f" Error: Failed to parse API response: {e}")
1098
+ print(" Falling back to placeholder whale move data")
1099
+ return self._placeholder_whale_moves()
1100
+
1101
+ if not isinstance(trades_data, list):
1102
+ print(f" Error: Expected array response, got {type(trades_data)}")
1103
+ print(" Falling back to placeholder whale move data")
1104
+ return self._placeholder_whale_moves()
1105
+
1106
+ # Get current Unix timestamp
1107
+ current_timestamp = int(time.time())
1108
+ # 24 hours in seconds
1109
+ twenty_four_hours_ago = current_timestamp - (24 * 60 * 60)
1110
+
1111
+ # Sports slugs to filter out
1112
+ sports_keywords = ['nba', 'nhl', 'epl', 'ucl', 'nfl']
1113
+
1114
+ # Filter and process trades
1115
  whale_moves = []
1116
 
1117
+ for trade in trades_data:
 
 
 
1118
  try:
1119
+ # Filter out trades older than 24 hours
1120
+ trade_timestamp = trade.get('timestamp', 0)
1121
+ if trade_timestamp < twenty_four_hours_ago:
1122
+ continue
1123
 
1124
+ # Filter out sports-related slugs
1125
+ slug = trade.get('slug', '').lower()
1126
+ if any(keyword in slug for keyword in sports_keywords):
1127
+ continue
1128
+
1129
+ # Extract data
1130
+ size = float(trade.get('size', 0))
1131
+ price = float(trade.get('price', 0))
1132
+ amount = size * price # Calculate total amount
1133
+
1134
+ title_text = trade.get('title', 'Unknown Market')
1135
+ outcome = trade.get('outcome', '')
1136
+ event_slug = trade.get('eventSlug', '')
1137
+
1138
+ # Build title with outcome
1139
+ outcome_str = f" on {outcome}" if outcome else ""
1140
+ title = f"${amount:,.0f} position{outcome_str}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1141
 
1142
+ # Use slug from trade, or generate from title
1143
+ if event_slug:
1144
+ slug_for_display = event_slug
1145
+ else:
1146
+ slug_for_display = title_text.lower().replace(' ', '-').replace('?', '').replace("'", '')[:50]
1147
+
1148
+ # Get user information
1149
+ user_name = trade.get('name', trade.get('pseudonym', 'Anonymous'))
1150
+ profile_image = trade.get('profileImageOptimized') or trade.get('profileImage', '')
1151
+
1152
+ # Get market icon if available
1153
+ market_icon = trade.get('icon', '')
1154
+
1155
+ whale_moves.append({
1156
+ "title": title,
1157
+ "market": title_text,
1158
+ "event": title_text,
1159
+ "slug": slug_for_display,
1160
+ "amount": amount,
1161
+ "user_name": user_name,
1162
+ "user_profile_url": user_name,
1163
+ "distinct_positions": 1,
1164
+ "timestamp": datetime.now().isoformat(),
1165
+ "profile_image": profile_image, # Store for enrichment
1166
+ "market_icon": market_icon # Store for enrichment
1167
+ })
1168
+
1169
+ except (KeyError, ValueError, TypeError) as e:
1170
+ print(f" Warning: Failed to process trade data: {e}")
1171
  continue
1172
 
1173
+ # Sort by amount (descending) and limit to top 10
1174
  whale_moves.sort(key=lambda x: x['amount'], reverse=True)
1175
+ whale_moves = whale_moves[:10]
1176
 
1177
  if not whale_moves:
1178
+ print(" No whale moves found after filtering")
1179
+ print(" Falling back to placeholder whale move data")
1180
  return self._placeholder_whale_moves()
1181
 
1182
+ print(f" Found {len(whale_moves)} whale moves after filtering")
1183
 
1184
  # Enrich with market images and user images
1185
  whale_moves = self._enrich_whale_moves_with_images(whale_moves)
 
1189
  def _enrich_whale_moves_with_images(self, whale_moves: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
1190
  """Enrich whale moves with market images and user profile images."""
1191
  print(" Fetching market images for whale moves...")
1192
+ default_user_image = "https://res.cloudinary.com/db1zelfhi/image/upload/v1765869030/polygraph/images/jhvsxfndu0boigqz3kjw.png"
1193
 
1194
  for move in whale_moves:
1195
+ # Use profile image from API if available, otherwise use default
1196
+ profile_image = move.pop('profile_image', '') # Remove from dict after getting
1197
+ if profile_image and profile_image.strip():
1198
+ move['user_image'] = profile_image
1199
+ else:
1200
+ move['user_image'] = default_user_image
1201
 
1202
+ # Use market icon from API if available, otherwise fetch using slug
1203
+ market_icon = move.pop('market_icon', '') # Remove from dict after getting
1204
+ if market_icon and market_icon.strip():
1205
+ move['market_image'] = market_icon
1206
+ print(f" ✓ Using icon from API for {move.get('market', 'Unknown')[:50]}")
 
 
 
 
 
 
 
 
 
1207
  else:
1208
+ # Fetch market image using slug as fallback
1209
+ slug = move.get('slug', '')
1210
+ if slug:
1211
+ try:
1212
+ details = self.fetch_market_details(slug)
1213
+ market_image = details.get('image', '')
1214
+ move['market_image'] = market_image
1215
+ if market_image:
1216
+ print(f" ✓ Found image for {slug[:50]}")
1217
+ else:
1218
+ print(f" ⚠ No image found for {slug[:50]}")
1219
+ except Exception as e:
1220
+ print(f" ⚠ Error fetching image for {slug[:50]}: {e}")
1221
+ move['market_image'] = ''
1222
+ else:
1223
+ print(f" ⚠ No slug available for market: {move.get('market', 'Unknown')}")
1224
+ move['market_image'] = ''
1225
 
1226
  return whale_moves
1227
 
1228
  def _placeholder_whale_moves(self) -> List[Dict[str, Any]]:
1229
+ """Return fallback whale move data when Polymarket API is unavailable."""
1230
  placeholder_moves = _fresh_placeholder_whales()
1231
  print(f" Using {len(placeholder_moves)} placeholder whale entries")
1232
  # Enrich placeholder moves with images too