Spaces:
Sleeping
Sleeping
dhruv575 commited on
Commit ·
471541d
1
Parent(s): 35213a7
optional image
Browse files- app/email_new_converter.py +8 -4
- app/post_process.py +10 -5
- app/templateify_new_service.py +6 -1
- email3.py +123 -90
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,
|
| 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 |
-
|
| 306 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 1054 |
print("Fetching whale moves...")
|
| 1055 |
|
| 1056 |
-
|
| 1057 |
-
query_id = os.getenv('CLICKHOUSE_QUERY_ID')
|
| 1058 |
-
user = os.getenv('CLICKHOUSE_USER')
|
| 1059 |
-
password = os.getenv('CLICKHOUSE_PASSWORD')
|
| 1060 |
|
| 1061 |
-
|
| 1062 |
-
|
| 1063 |
-
|
| 1064 |
-
|
| 1065 |
-
|
| 1066 |
-
|
|
|
|
|
|
|
| 1067 |
|
| 1068 |
max_retries = 3
|
| 1069 |
-
timeout =
|
| 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 |
-
|
| 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 |
-
|
| 1090 |
-
|
| 1091 |
-
|
| 1092 |
-
print(
|
| 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
|
| 1106 |
-
if not line:
|
| 1107 |
-
continue
|
| 1108 |
-
|
| 1109 |
try:
|
| 1110 |
-
|
|
|
|
|
|
|
|
|
|
| 1111 |
|
| 1112 |
-
|
| 1113 |
-
|
| 1114 |
-
|
| 1115 |
-
|
| 1116 |
-
|
| 1117 |
-
|
| 1118 |
-
|
| 1119 |
-
|
| 1120 |
-
|
| 1121 |
-
|
| 1122 |
-
|
| 1123 |
-
|
| 1124 |
-
|
| 1125 |
-
|
| 1126 |
-
|
| 1127 |
-
|
| 1128 |
-
|
| 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 |
-
|
| 1150 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1151 |
continue
|
| 1152 |
|
|
|
|
| 1153 |
whale_moves.sort(key=lambda x: x['amount'], reverse=True)
|
|
|
|
| 1154 |
|
| 1155 |
if not whale_moves:
|
| 1156 |
-
print(" No whale
|
|
|
|
| 1157 |
return self._placeholder_whale_moves()
|
| 1158 |
|
| 1159 |
-
print(f" Found {len(whale_moves)} whale
|
| 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 |
-
|
| 1170 |
|
| 1171 |
for move in whale_moves:
|
| 1172 |
-
#
|
| 1173 |
-
move
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1174 |
|
| 1175 |
-
#
|
| 1176 |
-
|
| 1177 |
-
if
|
| 1178 |
-
|
| 1179 |
-
|
| 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 |
-
|
| 1191 |
-
move
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1192 |
|
| 1193 |
return whale_moves
|
| 1194 |
|
| 1195 |
def _placeholder_whale_moves(self) -> List[Dict[str, Any]]:
|
| 1196 |
-
"""Return fallback whale move data when
|
| 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
|